File

projects/netgrif-components-core/src/lib/view/case-view/service/case-view-service.ts

Extends

AbstractSortableViewComponent

Index

Properties
Methods
Accessors

Constructor

constructor(_allowedNetsService: AllowedNetsService, _dialog: MatDialog, _caseResourceService: CaseResourceService, _log: LoggerService, _snackBarService: SnackBarService, _searchService: SearchService, _translate: TranslateService, _user: UserService, _processService: ProcessService, resolver: SearchIndexResolverService, _newCaseComponent: any, newCaseConfig: NewCaseConfiguration, _permissionService: PermissionService)
Parameters :
Name Type Optional
_allowedNetsService AllowedNetsService No
_dialog MatDialog No
_caseResourceService CaseResourceService No
_log LoggerService No
_snackBarService SnackBarService No
_searchService SearchService No
_translate TranslateService No
_user UserService No
_processService ProcessService No
resolver SearchIndexResolverService No
_newCaseComponent any No
newCaseConfig NewCaseConfiguration No
_permissionService PermissionService No

Methods

Protected addPageParams
addPageParams(params: HttpParams, pagination: Pagination)
Parameters :
Name Type Optional
params HttpParams No
pagination Pagination No
Returns : HttpParams
Public createDefaultNewCase
createDefaultNewCase(newCaseCreationConfiguration: NewCaseCreationConfigurationData)
Parameters :
Name Type Optional Default value
newCaseCreationConfiguration NewCaseCreationConfigurationData No { enableCaseTitle: true, isCaseTitleRequired: true }
Returns : Observable<Case>
Public createNewCase
createNewCase(newCaseCreationConfiguration: NewCaseCreationConfigurationData)
Parameters :
Name Type Optional Default value
newCaseCreationConfiguration NewCaseCreationConfigurationData No { enableCaseTitle: true, isCaseTitleRequired: true }
Returns : Observable<Case>
Public getAllowedNetsCount
getAllowedNetsCount()

Returns number of allowed nets of case view

Returns : number

length of allowedNets array if it is defined, 0 if it is undefined

Protected getDefaultSortParam
getDefaultSortParam()
Returns : string
Protected getMetaFieldSortId
getMetaFieldSortId()
Returns : string
Public getNewCaseAllowedNets
getNewCaseAllowedNets(blockNets: string[])
Parameters :
Name Type Optional Default value
blockNets string[] No []
Returns : Observable<Array<PetriNetReferenceWithPermissions>>
Public hasAuthority
hasAuthority(authority: Array | string)
Parameters :
Name Type Optional
authority Array<string> | string No
Returns : boolean
Public loadPage
loadPage(requestContext: PageLoadRequestContext)
Parameters :
Name Type Optional
requestContext PageLoadRequestContext No
Returns : Observable<CasePageLoadRequestResult>
Public nextPage
nextPage(renderedRange: ListRange, totalLoaded: number, requestContext?: PageLoadRequestContext)
Parameters :
Name Type Optional
renderedRange ListRange No
totalLoaded number No
requestContext PageLoadRequestContext Yes
Returns : void
Public nextPagePagination
nextPagePagination(length: number, pageIndex: number, requestContext?: PageLoadRequestContext)
Parameters :
Name Type Optional
length number No
pageIndex number No
requestContext PageLoadRequestContext Yes
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
Public reload
reload()
Returns : void
Public viewEnabled
viewEnabled(aCase: Case)

Determines whether the current user has the VIEW permission on the current case

Parameters :
Name Type Optional Description
aCase Case No

the tested case

Returns : boolean

true if the current user has the VIEW permission on the tested case, false otherwise.

Properties

Protected _cases
Type : Array<Case>
Protected _cases$
Type : Observable<Array<Case>>
Protected _endOfData
Type : boolean
Protected _loading$
Type : LoadingWithFilterEmitter
Protected _newCaseConfiguration
Type : NewCaseConfiguration
Protected _nextPage$
Type : BehaviorSubject<PageLoadRequestContext>
Protected _pagination
Type : Pagination
Protected _paginationView
Type : boolean
Default value : false
Readonly DEFAULT_NEW_CASE_CONFIGURATION
Type : NewCaseConfiguration
Default value : { useCachedProcesses: true }

Accessors

loading
getloading()
loading$
getloading$()
cases$
getcases$()
cases
getcases()
pagination
getpagination()
activeFilter
getactiveFilter()
paginationView
setpaginationView(value: boolean)
Parameters :
Name Type Optional
value boolean No
Returns : void
import {Inject, Injectable, OnDestroy, Optional} from '@angular/core';
import {CaseResourceService} from '../../../resources/engine-endpoint/case-resource.service';
import {BehaviorSubject, Observable, of, Subject} from 'rxjs';
import {HttpParams} from '@angular/common/http';
import {Case} from '../../../resources/interface/case';
import {LoggerService} from '../../../logger/services/logger.service';
import {SnackBarService} from '../../../snack-bar/services/snack-bar.service';
import {SearchService} from '../../../search/search-service/search.service';
import {TranslateService} from '@ngx-translate/core';
import {catchError, concatMap, filter, map, mergeMap, scan, switchMap, tap} from 'rxjs/operators';
import {Pagination} from '../../../resources/interface/pagination';
import {CaseMetaField} from '../../../header/case-header/case-menta-enum';
import {PageLoadRequestContext} from '../../abstract/page-load-request-context';
import {Filter} from '../../../filter/models/filter';
import {ListRange} from '@angular/cdk/collections';
import {LoadingWithFilterEmitter} from '../../../utility/loading-with-filter-emitter';
import {CasePageLoadRequestResult} from '../models/case-page-load-request-result';
import {UserService} from '../../../user/services/user.service';
import {arrayToObservable} from '../../../utility/array-to-observable';
import {PermissionType} from '../../../process/permissions';
import {NAE_NEW_CASE_CONFIGURATION} from '../models/new-case-configuration-injection-token';
import {NewCaseConfiguration} from '../models/new-case-configuration';
import {ProcessService} from '../../../process/process.service';
import {PetriNetReferenceWithPermissions} from '../../../process/petri-net-reference-with-permissions';
import {
    SearchIndexResolverService
} from '../../../search/search-keyword-resolver-service/search-index-resolver.service';
import {AllowedNetsService} from '../../../allowed-nets/services/allowed-nets.service';
import {AbstractSortableViewComponent} from '../../abstract/sortable-view';
import {
    NewCaseCreationConfigurationData
} from '../../../side-menu/content-components/new-case/model/new-case-injection-data';
import {PermissionService} from '../../../authorization/permission/permission.service';
import {EventOutcomeMessageResource} from '../../../resources/interface/message-resource';
import {CreateCaseEventOutcome} from '../../../event/model/event-outcomes/case-outcomes/create-case-event-outcome';
import {PaginationParams} from '../../../utility/pagination/pagination-params';
import {createSortParam, PaginationSort} from '../../../utility/pagination/pagination-sort';
import {MatDialog} from '@angular/material/dialog';
import {NAE_NEW_CASE_DIALOG_COMPONENT} from '../../../dialog/injection-tokens';

@Injectable()
export class CaseViewService extends AbstractSortableViewComponent implements OnDestroy {

    readonly DEFAULT_NEW_CASE_CONFIGURATION: NewCaseConfiguration = {
        useCachedProcesses: true
    };

    protected _loading$: LoadingWithFilterEmitter;
    protected _cases$: Observable<Array<Case>>;
    protected _cases: Array<Case>;
    protected _nextPage$: BehaviorSubject<PageLoadRequestContext>;
    protected _endOfData: boolean;
    protected _pagination: Pagination;
    protected _newCaseConfiguration: NewCaseConfiguration;
    protected _paginationView: boolean = false;

    constructor(protected _allowedNetsService: AllowedNetsService,
                protected _dialog: MatDialog,
                protected _caseResourceService: CaseResourceService,
                protected _log: LoggerService,
                protected _snackBarService: SnackBarService,
                protected _searchService: SearchService,
                protected _translate: TranslateService,
                protected _user: UserService,
                protected _processService: ProcessService,
                resolver: SearchIndexResolverService,
                @Optional() @Inject(NAE_NEW_CASE_DIALOG_COMPONENT) protected _newCaseComponent: any,
                @Optional() @Inject(NAE_NEW_CASE_CONFIGURATION) newCaseConfig: NewCaseConfiguration,
                protected _permissionService: PermissionService) {
        super(resolver);
        this._cases = [];
        this._newCaseConfiguration = {...this.DEFAULT_NEW_CASE_CONFIGURATION};
        if (newCaseConfig !== null) {
            Object.assign(this._newCaseConfiguration, newCaseConfig);
        }
        this._loading$ = new LoadingWithFilterEmitter();
        this._searchService.activeFilter$.subscribe(() => {
            this.reload();
        });
        this._endOfData = false;
        this._pagination = {
            size: 25,
            totalElements: undefined,
            totalPages: undefined,
            number: -1
        };
        this._nextPage$ = new BehaviorSubject<PageLoadRequestContext>(
            new PageLoadRequestContext(this.activeFilter, Object.assign({}, this._pagination, {number: 0}))
        );

        const casesMap = this._nextPage$.pipe(
            mergeMap(p => this.loadPage(p)),
            map(pageLoadResult => {
                if (pageLoadResult.requestContext && pageLoadResult.requestContext.clearLoaded) {
                    // we set an empty value to the virtual scroll and then replace it by the real value forcing it to redraw its content
                    const results = [{cases: {}, requestContext: null}, pageLoadResult];
                    return arrayToObservable(results);
                } else {
                    return of(pageLoadResult);
                }
            }),
            concatMap(o => o),
            scan((acc, pageLoadResult) => {
                if (pageLoadResult.requestContext === null) {
                    return pageLoadResult.cases;
                } else {
                    this._loading$.off(pageLoadResult.requestContext.filter);
                }
                Object.assign(this._pagination, pageLoadResult.requestContext.pagination);
                if (this._paginationView) {
                    return pageLoadResult.cases;
                }
                return {...acc, ...pageLoadResult.cases};
            }, {})
        );
        this._cases$ = casesMap.pipe(
            map(v => Object.values(v) as Array<Case>),
            tap(cases => this._cases = cases as Array<Case>),
        );
    }

    ngOnDestroy(): void {
        super.ngOnDestroy();
        this._loading$.complete();
        this._nextPage$.complete();
    }

    public get loading(): boolean {
        return this._loading$.isActive;
    }

    public get loading$(): Observable<boolean> {
        return this._loading$.asObservable();
    }

    public get cases$(): Observable<Array<Case>> {
        return this._cases$;
    }

    public get cases(): Array<Case> {
        return this._cases;
    }

    public get pagination(): Pagination {
        return this._pagination;
    }

    protected get activeFilter(): Filter {
        return this._searchService.activeFilter;
    }

    public set paginationView(value: boolean) {
        this._paginationView = value;
    }

    public loadPage(requestContext: PageLoadRequestContext): Observable<CasePageLoadRequestResult> {
        if (requestContext === null || requestContext.pageNumber < 0) {
            return of({cases: {}, requestContext});
        }
        let params: HttpParams = new HttpParams();
        params = this.addSortParams(params);
        params = this.addPageParams(params, requestContext.pagination);
        this._loading$.on(requestContext.filter);

        return this._caseResourceService.searchCases(requestContext.filter, params).pipe(
            catchError(err => {
                this._log.error('Loading cases has failed!', err);
                this._loading$.off(requestContext.filter);
                return of({content: [], pagination: {...this._pagination}});
            }),
            filter(() => {
                const r = requestContext.filter === this._searchService.activeFilter;
                if (!r) {
                    this._loading$.off(requestContext.filter);
                    this._log.debug('Received cases page is no longer relevant since the active filter has changed before it could arrive.'
                        + ' Discarding...');
                }
                return r;
            }),
            tap(c => {
                Object.assign(requestContext.pagination, c.pagination);
            }),
            tap(c => {
                this._endOfData = !Array.isArray(c.content)
                    || c.content.length === 0
                    || c.pagination.number === c.pagination.totalPages;
            }),
            map(cases => Array.isArray(cases.content) ? cases : {...cases, content: []}),
            map(cases => {
                return cases.content.reduce((acc, cur) => {
                    return {...acc, [cur.stringId]: cur};
                }, {});
            }),
            map(cases => ({cases, requestContext}))
        );
    }

    public nextPage(renderedRange: ListRange, totalLoaded: number, requestContext?: PageLoadRequestContext) {
        if (requestContext === undefined) {
            requestContext = new PageLoadRequestContext(this.activeFilter, this._pagination);
            requestContext.pagination.number += 1;
        }

        if (this.isLoadingRelevantFilter(requestContext) || this._endOfData) {
            return;
        }

        if (renderedRange.end === totalLoaded) {
            this._nextPage$.next(requestContext);
        }
    }

    public nextPagePagination(length: number, pageIndex: number, requestContext?: PageLoadRequestContext) {
        if (requestContext === undefined) {
            requestContext = new PageLoadRequestContext(this.activeFilter, this._pagination);
            requestContext.pagination.size = length;
            requestContext.pagination.number = pageIndex;
        }

        if (this.isLoadingRelevantFilter(requestContext) || this._endOfData) {
            return;
        }
        this._nextPage$.next(requestContext);
    }

    private isLoadingRelevantFilter(requestContext?: PageLoadRequestContext): boolean {
        return requestContext === undefined || this._loading$.isActiveWithFilter(requestContext.filter);
    }

    public createNewCase(newCaseCreationConfiguration: NewCaseCreationConfigurationData = {
        enableCaseTitle: true,
        isCaseTitleRequired: true
    }): Observable<Case> {
        const myCase = new Subject<Case>();
        const dialogRef = this._dialog.open(this._newCaseComponent, {
            width: '40%',
            minWidth: '300px',
            panelClass: "dialog-responsive",
            data: {
                allowedNets$: this.getNewCaseAllowedNets(newCaseCreationConfiguration.blockNets),
                newCaseCreationConfiguration
            },
        });
        dialogRef.afterClosed().subscribe($event => {
            if ($event?.data) {
                this._log.debug($event.message, $event.data);
                this.reload();
                myCase.next($event.data);
            }
            myCase.complete();
        });
        return myCase.asObservable();
    }

    public createDefaultNewCase(newCaseCreationConfiguration: NewCaseCreationConfigurationData = {
        enableCaseTitle: true,
        isCaseTitleRequired: true
    }): Observable<Case> {
        const myCase = new Subject<Case>();
        this.getNewCaseAllowedNets(newCaseCreationConfiguration.blockNets).subscribe((nets: Array<PetriNetReferenceWithPermissions>) => {
            if (!nets || nets.length === 0) {
                const errorMessage = this._translate.instant('side-menu.new-case.noNets');
                this._snackBarService.openErrorSnackBar(errorMessage);
                this._log.error('No nets available for case creation. Ensure the allowed nets configuration is correct.');
                return;
            }
            this._caseResourceService.createCase({
                title: null,
                color: 'panel-primary-icon',
                netId: nets[0].stringId
            }).subscribe((response: EventOutcomeMessageResource) => {
                this._snackBarService.openSuccessSnackBar(this._translate.instant('side-menu.new-case.createCase')
                    + ' ' + this._translate.instant('side-menu.new-case.defaultCaseName'));
                this.reload();
                myCase.next((response.outcome as CreateCaseEventOutcome).aCase);
                myCase.complete();
            }, error => {
                const errorMessage = error.message ? error.message : this._translate.instant('side-menu.new-case.createCaseError');
                this._snackBarService.openErrorSnackBar(errorMessage);
                this._log.error('Error occurred during case creation: ' + errorMessage);
            });
        }, error => {
            const errorMessage = error.message || this._translate.instant('side-menu.new-case.errorCreate');
            this._log.error('Failed to fetch allowed nets. Error: ' + errorMessage);
            this._snackBarService.openErrorSnackBar(errorMessage);
        });
        return myCase;
    }

    public getNewCaseAllowedNets(blockNets: string[] = []): Observable<Array<PetriNetReferenceWithPermissions>> {
        if (this._newCaseConfiguration.useCachedProcesses) {
            return this._allowedNetsService.allowedNets$.pipe(
                map(net => net.filter(n => blockNets.indexOf(n.identifier) === -1)),
                map(net => net.filter(n => this._permissionService.hasNetPermission(PermissionType.CREATE, n)))
            );
        } else {
            return this._allowedNetsService.allowedNets$.pipe(
                switchMap(allowedNets => {
                    return this._processService.getNetReferences(allowedNets.map(net => net.identifier)).pipe(
                        map(net => net.filter(n => blockNets.indexOf(n.identifier) === -1)),
                        map(net => net.filter(n => this._permissionService.hasNetPermission(PermissionType.CREATE, n)))
                    );
                })
            );
        }
    }

    /**
     * Returns number of allowed nets of case view
     * @returns length of allowedNets array if it is defined, 0 if it is undefined
     */
    public getAllowedNetsCount(): number {
        return this._allowedNetsService.allowedNets === undefined ? 0 : this._allowedNetsService.allowedNets.length;
    }

    protected addPageParams(params: HttpParams, pagination: Pagination): HttpParams {
        params = params.set(PaginationParams.PAGE_SIZE, `${pagination.size}`);
        params = params.set(PaginationParams.PAGE_NUMBER, `${pagination.number}`);
        return params;
    }

    protected getDefaultSortParam(): string {
        return createSortParam('stringId', PaginationSort.DESCENDING);
    }

    protected getMetaFieldSortId(): string {
        switch (this._lastHeaderSearchState.fieldIdentifier) {
            case CaseMetaField.TITLE:
                return 'title.keyword';
            case CaseMetaField.VISUAL_ID:
                return 'visualId.keyword';
            case CaseMetaField.CREATION_DATE:
                return 'creationDateSortable';
            default:
                return this._lastHeaderSearchState.fieldIdentifier;
        }
    }

    public reload(): void {
        if (!this._cases$ || !this._pagination) {
            return;
        }

        this._endOfData = false;
        const requestContext = new PageLoadRequestContext(this.activeFilter, this._pagination, true);
        requestContext.pagination.number = 0;
        const range = {
            start: -1,
            end: 0
        };

        this.nextPage(range, 0, requestContext);
    }

    public hasAuthority(authority: Array<string> | string): boolean {
        return this._user.hasAuthority(authority);
    }

    /**
     * Determines whether the current user has the [VIEW]{@link PermissionType#VIEW} permission on the current case
     * @param aCase the tested case
     * @returns `true` if the current user has the `VIEW` permission on the tested case, `false` otherwise.
     */
    public viewEnabled(aCase: Case): boolean {
        return this._permissionService.hasCasePermission(aCase, PermissionType.VIEW);
    }
}

result-matching ""

    No results matching ""