Merge pull request #597 from MostafaAmin07/gitlab-fix-and-optimization

Gitlab fix and optimization
This commit is contained in:
Johannes Millan 2020-10-22 21:43:00 +02:00 committed by GitHub
commit 4d15e274d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 185 additions and 29 deletions

View file

@ -19,5 +19,13 @@ export interface IssueServiceInterface {
issue: IssueData
} | null>;
refreshIssues?(tasks: Task[],
isNotifySuccess: boolean,
isNotifyNoUpdateRequired: boolean): Promise<{
task: Task,
taskChanges: Partial<Task>,
issue: IssueData
}[]>;
getMappedAttachments?(issueDataIN: IssueData): TaskAttachment[];
}

View file

@ -96,6 +96,50 @@ export class IssueService {
}
}
async refreshIssues(
tasks: Task[],
isNotifySuccess: boolean = true,
isNotifyNoUpdateRequired: boolean = false
): Promise<void> {
// dynamic map that has a list of tasks for every entry where the entry is an issue type
const tasksIssueIdsByIssueType: any = {};
const tasksWithoutIssueId = [];
const tasksWithoutMethod = [];
for (const task of tasks) {
if (!task.issueId || !task.issueType) {
tasksWithoutIssueId.push(task);
} else if (!this.ISSUE_SERVICE_MAP[task.issueType].refreshIssues) {
tasksWithoutMethod.push(task);
}
else if (!tasksIssueIdsByIssueType[task.issueType]){
tasksIssueIdsByIssueType[task.issueType] = [];
tasksIssueIdsByIssueType[task.issueType].push(task);
} else {
tasksIssueIdsByIssueType[task.issueType].push(task);
}
}
for (const issuesType of Object.keys(tasksIssueIdsByIssueType)) {
const updates = await (this.ISSUE_SERVICE_MAP[issuesType].refreshIssues as any)(tasksIssueIdsByIssueType[issuesType], isNotifySuccess, isNotifyNoUpdateRequired);
if (updates) {
for (const update of updates) {
if (this.ISSUE_REFRESH_MAP[issuesType][update.task.issueId]) {
this.ISSUE_REFRESH_MAP[issuesType][update.task.issueId].next(update.issue);
}
this._taskService.update(update.task.id, update.taskChanges);
}
}
}
for (const taskWithoutIssueId of tasksWithoutIssueId) {
throw new Error('No issue task ' + taskWithoutIssueId.id);
}
for (const taskWithoutMethod of tasksWithoutMethod) {
throw new Error('Issue method not available ' + taskWithoutMethod);
}
}
async addTaskWithIssue(
issueType: IssueProviderKey,
issueIdOrData: string | number | IssueDataReduced,

View file

@ -8,7 +8,7 @@ import { GitlabOriginalComment, GitlabOriginalIssue } from './gitlab-api-respons
import { HANDLED_ERROR_PROP_STR } from 'src/app/app.constants';
import { GITLAB_API_BASE_URL, GITLAB_URL_REGEX, GITLAB_PROJECT_REGEX } from '../gitlab.const';
import { T } from 'src/app/t.const';
import { catchError, filter, map, mergeMap, share, switchMap, take } from 'rxjs/operators';
import { catchError, filter, map, mergeMap, take } from 'rxjs/operators';
import { GitlabIssue } from '../gitlab-issue/gitlab-issue.model';
import { mapGitlabIssue, mapGitlabIssueToSearchResult } from '../gitlab-issue/gitlab-issue-map.util';
import { SearchResultItem } from '../../../issue.model';
@ -42,10 +42,41 @@ export class GitlabApiService {
}
getById$(id: number, cfg: GitlabCfg): Observable<GitlabIssue> {
return this.getProjectData$(cfg)
.pipe(switchMap(issues => {
return issues.filter(issue => issue.id === id);
}));
return this._sendRequest$({
url: `${this.apiLink(cfg)}/issues/${id}`
}, cfg).pipe(
mergeMap(
(issue: GitlabOriginalIssue) => {
return this.getIssueWithComments$(mapGitlabIssue(issue), cfg);
})
);
}
getByIds$(ids: string[], cfg: GitlabCfg): Observable<GitlabIssue[]> {
let queryParams = 'iids[]=';
for (let i = 0; i < ids.length ; i++) {
if (i === ids.length - 1) {
queryParams += ids[i];
} else {
queryParams += `${ids[i]}&iids[]=`;
}
}
return this._sendRequest$({
url: `${this.apiLink(cfg)}/issues?${queryParams}&per_page=100`
}, cfg).pipe(
map((issues: GitlabOriginalIssue[]) => {
return issues ? issues.map(mapGitlabIssue) : [];
}),
mergeMap((issues: GitlabIssue[]) => {
if (issues && issues.length) {
return forkJoin([
...issues.map(issue => this.getIssueWithComments$(issue, cfg))
]);
} else {
return of([]);
}
})
);
}
getIssueWithComments$(issue: GitlabIssue, cfg: GitlabCfg): Observable<GitlabIssue> {
@ -61,32 +92,34 @@ export class GitlabApiService {
}
searchIssueInProject$(searchText: string, cfg: GitlabCfg): Observable<SearchResultItem[]> {
const filterFn = (issue: GitlabIssue) => {
try {
return issue.title.toLowerCase().match(searchText.toLowerCase())
|| issue.body.toLowerCase().match(searchText.toLowerCase());
} catch (e) {
console.warn('RegEx Error', e);
return false;
}
};
if (!this._isValidSettings(cfg)) {
return EMPTY;
}
return this.getProjectData$(cfg)
.pipe(
// a single request should suffice
share(),
map((issues: GitlabIssue[]) =>
issues.filter(filterFn)
.map(mapGitlabIssueToSearchResult)
),
);
return this._sendRequest$({
url: `${this.apiLink(cfg)}/issues?search=${searchText}&order_by=updated_at`
}, cfg).pipe(
map((issues: GitlabOriginalIssue[]) => {
return issues ? issues.map(mapGitlabIssue) : [];
}),
mergeMap(
(issues: GitlabIssue[]) => {
if (issues && issues.length) {
return forkJoin([
...issues.map(issue => this.getIssueWithComments$(issue, cfg))
]);
} else {
return of([]);
}
}),
map((issues: GitlabIssue[]) => {
return issues ? issues.map(mapGitlabIssueToSearchResult) : [];
})
);
}
private _getProjectIssues$(pageNumber: number, cfg: GitlabCfg): Observable<GitlabIssue[]> {
return this._sendRequest$({
url: `${this.apiLink(cfg)}/issues?order_by=updated_at&per_page=100&page=${pageNumber}`
url: `${this.apiLink(cfg)}/issues?state=opened&order_by=updated_at&per_page=100&page=${pageNumber}`
}, cfg).pipe(
take(1),
map((issues: GitlabOriginalIssue[]) => {

View file

@ -100,7 +100,7 @@ export class GitlabCommonInterfacesService implements IssueServiceInterface {
taskChanges: {
issueWasUpdated: true,
issueLastUpdated: lastRemoteUpdate,
title: `#${issue.number} ${issue.title}`,
title: this._formatIssueTitle(issue.number, issue.title),
},
issue,
};
@ -108,6 +108,78 @@ export class GitlabCommonInterfacesService implements IssueServiceInterface {
return null;
}
async refreshIssues(
tasks: Task[],
isNotifySuccess: boolean = true,
isNotifyNoUpdateRequired: boolean = false
): Promise<{ task: Task, taskChanges: Partial<Task>, issue: GitlabIssue }[]> {
// First sort the tasks by the issueId
// because the API returns it in a desc order by issue iid(issueId)
// so it makes the update check easier and faster
tasks.sort((a, b) => +(b.issueId as string) - +(a.issueId as string));
const projectId = tasks && tasks[0].projectId ? tasks[0].projectId : 0;
if (!projectId) {
throw new Error('No projectId');
}
const cfg = await this._getCfgOnce$(projectId).toPromise();
const issues: GitlabIssue[] = [];
const paramsCount = 59; // Can't send more than 59 issue id For some reason it returns 502 bad gateway
let ids;
let i = 0;
while (i < tasks.length) {
ids = [];
for (let j = 0; j < paramsCount && i < tasks.length ; j++, i++) {
ids.push(tasks[i].issueId);
}
issues.push(...(await this._gitlabApiService.getByIds$(ids as string[], cfg).toPromise()));
}
const updatedIssues: { task: Task, taskChanges: Partial<Task>, issue: GitlabIssue }[] = [];
for (i = 0; i < tasks.length; i++) {
const issueUpdate: number = new Date(issues[i].updated_at).getTime();
const commentsByOthers = (cfg.filterUsername && cfg.filterUsername.length > 1)
? issues[i].comments.filter(comment => comment.author.username !== cfg.filterUsername)
: issues[i].comments;
const updates: number[] = [
...(commentsByOthers.map(comment => new Date(comment.created_at).getTime())),
issueUpdate
].sort();
const lastRemoteUpdate = updates[updates.length - 1];
const wasUpdated = lastRemoteUpdate > (tasks[i].issueLastUpdated || 0);
if (wasUpdated) {
updatedIssues.push({
task: tasks[i],
taskChanges: {
issueWasUpdated: true,
issueLastUpdated: lastRemoteUpdate,
title: this._formatIssueTitle(issues[i].number, issues[i].title),
},
issue: issues[i],
});
}
if (wasUpdated && isNotifySuccess) {
this._snackService.open({
ico: 'cloud_download',
translateParams: {
issueText: this._formatIssueTitleForSnack(issues[i].number, issues[i].title)
},
msg: T.F.GITLAB.S.ISSUE_UPDATE,
});
} else if (isNotifyNoUpdateRequired) {
this._snackService.open({
msg: T.F.GITLAB.S.ISSUE_NO_UPDATE_REQUIRED,
ico: 'cloud_download',
});
}
}
return updatedIssues;
}
getAddTaskData(issue: GitlabIssue): { title: string; additionalFields: Partial<IssueFieldsForTask> } {
return {
title: this._formatIssueTitle(issue.number, issue.title),

View file

@ -38,17 +38,16 @@ export class GitlabIssueEffects {
)
.map(({task}: { cfg: GitlabCfg, task: TaskWithSubTasks }) => task)
),
tap((gitlabTasks: TaskWithSubTasks[]) => gitlabTasks.forEach((gitlabTask) => {
tap((gitlabTasks: TaskWithSubTasks[]) => {
if (gitlabTasks && gitlabTasks.length > 0) {
this._snackService.open({
msg: T.F.GITLAB.S.POLLING,
svgIco: 'gitlab',
isSpinner: true,
});
gitlabTasks.forEach((task) =>
this._issueService.refreshIssue(task, true, false));
this._issueService.refreshIssues(gitlabTasks, true, false);
}
})),
}),
);
private _pollTimer$: Observable<any> = timer(GITLAB_INITIAL_POLL_DELAY, GITLAB_POLL_INTERVAL);