blob: 2078a99d7a60abe7824844d7c79cff3b7550a081 [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
import {Injectable} from "@angular/core";
import {Router} from "@angular/router";
import {Actions, createEffect, ofType} from "@ngrx/effects";
import {Action} from "@ngrx/store";
import {concat, EMPTY, from, Observable, of, pipe} from "rxjs";
import {catchError, endWith, exhaustMap, filter, ignoreElements, map, mergeMap, retry, startWith, switchMap} from "rxjs/operators";
import {EAPIProcessTaskDefinitionKey, IAPIProcessTask, ProcessApiService, TCompleteTaskVariable} from "../../../core/api/process";
import {catchHttpError, catchHttpErrorTo, EHttpStatusCodes} from "../../../util/http";
import {catchErrorTo, emitOnComplete, endWithObservable, ignoreError, throwAfterActionType} from "../../../util/rxjs";
import {arrayJoin} from "../../../util/store";
import {setErrorAction} from "../../root/actions";
import {EErrorCode} from "../../root/model";
import {
claimAndCompleteTask,
claimTaskAction,
completeTaskAction,
deleteTaskAction,
setProcessLoadingAction,
setStatementTasksAction,
setTaskEntityAction,
unclaimAllTasksAction
} from "../actions";
@Injectable({providedIn: "root"})
export class ProcessTaskEffect {
public claim$ = createEffect(() => this.actions.pipe(
ofType(claimTaskAction),
switchMap((action) => this.claimTask(action.statementId, action.taskId, true).pipe(
catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
))
));
public claimAndComplete$ = createEffect(() => this.actions.pipe(
ofType(claimAndCompleteTask),
switchMap((action) => this.claimAndCompleteTask(action.statementId, action.taskId, action.variables, action.claimNext).pipe(
catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
))
));
public completeTask$ = createEffect(() => this.actions.pipe(
ofType(completeTaskAction),
exhaustMap((action) => this.completeTask(action.statementId, action.taskId, action.variables, action.claimNext).pipe(
catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
))
));
public unclaimAll$ = createEffect(() => this.actions.pipe(
ofType(unclaimAllTasksAction),
switchMap((action) => this.unclaim(action.statementId, action.assignee))
));
public constructor(
private readonly actions: Actions,
private readonly processApiService: ProcessApiService,
private readonly router: Router
) {
}
public claimTask(statementId: number, taskId: string, navigate?: boolean): Observable<Action> {
return this.processApiService.claimStatementTask(statementId, taskId).pipe(
map((task) => setTaskEntityAction({task})),
endWithObservable(() => navigate ? this.navigateTo(statementId, taskId) : EMPTY),
catchHttpErrorTo(setErrorAction({
statementId,
error: EErrorCode.CLAIMED_BY_OTHER_USER
}), EHttpStatusCodes.BAD_REQUEST),
catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
this.fetchTasksAfterError(statementId)
);
}
public claimAndCompleteTask(
statementId: number,
taskId: string,
variable: TCompleteTaskVariable,
claimNext?: boolean | EAPIProcessTaskDefinitionKey
): Observable<Action> {
return this.claimTask(statementId, taskId).pipe(
throwAfterActionType(setErrorAction),
endWithObservable(() => this.completeTask(statementId, taskId, variable, claimNext)),
ignoreError()
);
}
public completeTask(
statementId: number,
taskId: string,
variable: TCompleteTaskVariable,
claimNext?: boolean | EAPIProcessTaskDefinitionKey
): Observable<Action> {
let nextTaskId = taskId;
return concat(
this.processApiService.completeStatementTask(statementId, taskId, variable).pipe(
map(() => {
nextTaskId = null;
return deleteTaskAction({statementId, taskId});
}),
catchHttpError(() => {
nextTaskId = null;
return of(
deleteTaskAction({statementId, taskId}),
setErrorAction({statementId, error: EErrorCode.TASK_TO_COMPLETE_NOT_FOUND})
);
}, EHttpStatusCodes.NOT_FOUND)
),
this.claimNext(statementId, claimNext, false).pipe(
map((task) => {
nextTaskId = task.taskId;
return setTaskEntityAction({task});
}),
catchHttpErrorTo(setErrorAction({
statementId,
error: EErrorCode.CLAIMED_BY_OTHER_USER
}), EHttpStatusCodes.BAD_REQUEST)
),
this.fetchTasks(statementId)
).pipe(
this.fetchTasksAfterError(statementId),
endWithObservable(() => this.navigateTo(statementId, nextTaskId))
);
}
public unclaim(statementId: number, assignee: string): Observable<Action> {
return this.processApiService.getStatementTasks(statementId).pipe(
switchMap((tasks) => of(...tasks)),
filter((task) => task?.assignee === assignee),
mergeMap((task) => this.processApiService.unclaimStatementTask(statementId, task.taskId).pipe(
map((unclaimedTask) => setTaskEntityAction({task: unclaimedTask})),
catchHttpErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED}))
)),
this.fetchTasksAfterError(statementId)
);
}
public fetchTasks(statementId: number): Observable<Action> {
return this.processApiService.getStatementTasks(statementId).pipe(
retry(2),
map((tasks) => setStatementTasksAction({statementId, tasks})),
startWith(deleteTaskAction({statementId})),
emitOnComplete(),
catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED}))
);
}
public claimNext(
statementId: number,
claimNext: boolean | EAPIProcessTaskDefinitionKey,
navigate?: boolean
): Observable<IAPIProcessTask> {
let taskId: string;
return claimNext == null || claimNext === false ? EMPTY : this.getNextTask(statementId, claimNext).pipe(
switchMap((_taskId) => this.processApiService.claimStatementTask(statementId, taskId = _taskId)),
endWithObservable(() => navigate ? this.navigateTo(statementId, taskId) : EMPTY)
);
}
public getNextTask(statementId: number, next?: boolean | EAPIProcessTaskDefinitionKey): Observable<string> {
return this.processApiService.getStatementTasks(statementId).pipe(
map((tasks) => {
return arrayJoin(tasks).find((_) => {
return next === true || _.taskDefinitionKey === next;
})?.taskId;
})
);
}
public navigateTo(statementId: number, taskId?: string): Observable<never> {
return from(taskId == null ?
this.router.navigate(["details"], {queryParams: {id: statementId}}) :
this.router.navigate(["edit"], {queryParams: {id: statementId, taskId}})
).pipe(ignoreElements());
}
public fetchTasksAfterError(statementId: number) {
return pipe(
catchErrorTo(setErrorAction({statementId, error: EErrorCode.UNEXPECTED})),
throwAfterActionType(setErrorAction),
catchError(() => this.fetchTasks(statementId)),
ignoreError()
);
}
}