| /******************************************************************************** |
| * 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() |
| ); |
| } |
| |
| } |