blob: f99da562382e7a2971784271ebafc3ea27349de8 [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 {sendStatementViaMailAction} from "../../statements/actions";
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, null, 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.assignee, action.claimNext
).pipe(
catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
startWith(setProcessLoadingAction({statementId: action.statementId, loading: true})),
endWith(setProcessLoadingAction({statementId: action.statementId, loading: false}))
))
));
public claimAndSend$ = createEffect(() => this.actions.pipe(
ofType(sendStatementViaMailAction),
switchMap((action) => this.claimAndSend(action.statementId, action.taskId, action.assignee).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, assignee?: 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: assignee ? EErrorCode.CLAIMED_BY_OTHER_USER : EErrorCode.ALREADY_CLAIMED,
errorValue: {user: assignee}
}), EHttpStatusCodes.BAD_REQUEST),
catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED})),
this.fetchTasksAfterError(statementId)
);
}
public claimAndCompleteTask(
statementId: number,
taskId: string,
variable: TCompleteTaskVariable,
assignee: string,
claimNext?: boolean | EAPIProcessTaskDefinitionKey
): Observable<Action> {
return this.claimTask(statementId, taskId, assignee).pipe(
throwAfterActionType(setErrorAction),
endWithObservable(() => this.completeTask(statementId, taskId, variable, claimNext)),
ignoreError()
);
}
public claimAndSend(
statementId: number,
taskId: string,
assignee: string
): Observable<Action> {
return this.claimTask(statementId, taskId, assignee).pipe(
throwAfterActionType(setErrorAction),
endWithObservable(() => concat(
this.sendMailAndComplete(statementId, taskId),
this.fetchTasks(statementId))
),
ignoreError()
);
}
public sendMailAndComplete(statementId: number, taskId: string): Observable<Action> {
return this.processApiService.dispatchStatement(statementId, taskId).pipe(
map(() => deleteTaskAction({statementId, taskId})),
catchError(() => this.processApiService.unclaimStatementTask(statementId, taskId).pipe(
map(() => setErrorAction({statementId, error: EErrorCode.COULD_NOT_SEND_MAIL})),
catchErrorTo(setErrorAction({error: EErrorCode.UNEXPECTED}))
))
);
}
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?.task?.taskId;
const user = task?.task?.assignee;
return task?.errorOccured ? setErrorAction({
statementId,
error: user == null ? EErrorCode.ALREADY_CLAIMED : EErrorCode.CLAIMED_BY_OTHER_USER,
errorValue: {user}
}) : setTaskEntityAction({task: task?.task});
}),
catchHttpErrorTo(setErrorAction({
error: EErrorCode.UNEXPECTED,
}))
),
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<{ task: IAPIProcessTask, errorOccured?: boolean }> {
let taskId: string;
let nextTask: IAPIProcessTask;
let error = false;
return claimNext == null || claimNext === false ? EMPTY : this.getNextTask(statementId, claimNext).pipe(
switchMap((task) => {
nextTask = task;
return task?.taskId ? this.processApiService.claimStatementTask(statementId, taskId = task?.taskId) : EMPTY;
}),
catchHttpError(() => {
error = true;
return EMPTY;
}, EHttpStatusCodes.BAD_REQUEST),
map(() => ({task: nextTask, errorOccured: error})),
endWithObservable(() => navigate ? this.navigateTo(statementId, taskId) : EMPTY)
);
}
public getNextTask(statementId: number, next?: boolean | EAPIProcessTaskDefinitionKey): Observable<IAPIProcessTask> {
return this.processApiService.getStatementTasks(statementId).pipe(
map((tasks) => {
return arrayJoin(tasks).find((_) => {
return next === true || _.taskDefinitionKey === next;
});
})
);
}
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()
);
}
}