File

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

Extends

AbstractSortableViewComponent

Index

Properties
Methods
Accessors

Constructor

constructor(_taskService: TaskResourceService, _userService: UserService, _snackBarService: SnackBarService, _translate: TranslateService, _searchService: SearchService, _log: LoggerService, _userComparator: UserComparatorService, resolver: SearchIndexResolverService, _preferredEndpoint: TaskEndpoint, taskViewConfig: TaskViewConfiguration)
Parameters :
Name Type Optional
_taskService TaskResourceService No
_userService UserService No
_snackBarService SnackBarService No
_translate TranslateService No
_searchService SearchService No
_log LoggerService No
_userComparator UserComparatorService No
resolver SearchIndexResolverService No
_preferredEndpoint TaskEndpoint No
taskViewConfig TaskViewConfiguration No

Methods

Protected addPageParams
addPageParams(params: HttpParams, pagination: Pagination)
Parameters :
Name Type Optional
params HttpParams No
pagination Pagination No
Returns : HttpParams
Protected getDefaultSortParam
getDefaultSortParam()
Returns : string
Protected getMetaFieldSortId
getMetaFieldSortId()
Returns : string
Public loadPage
loadPage(requestContext: PageLoadRequestContext)
Parameters :
Name Type Optional
requestContext PageLoadRequestContext No
Returns : Observable<TaskPageLoadRequestResult>
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 reloadCurrentPage
reloadCurrentPage(force?: boolean)
Parameters :
Name Type Optional
force boolean Yes
Returns : void

Properties

Protected _allowMultiOpen
Type : boolean
Protected _changedFields$
Type : Subject<ChangedFieldsMap>
Protected _closeTab$
Type : ReplaySubject<void>
Protected _closeTaskTabOnNoTasks
Type : boolean
Protected _endOfData
Type : boolean
Protected _initiallyOpenOneTask
Type : boolean
Protected _loading$
Type : LoadingWithFilterEmitter
Protected _pagination
Type : Pagination
Protected _panelUpdate$
Type : BehaviorSubject<Array<TaskPanelData>>
Protected _requestedPage$
Type : BehaviorSubject<PageLoadRequestContext>
Protected _subCloseTask
Type : Subscription
Protected _subInitiallyOpen
Type : Subscription
Protected _subSearch
Type : Subscription
Protected _tasks$
Type : Observable<Array<TaskPanelData>>

Accessors

tasks$
gettasks$()
changedFields$
getchangedFields$()
loading$
getloading$()
loading
getloading()
panelUpdate
getpanelUpdate()
closeTab
getcloseTab()
pagination
getpagination()
activeFilter
getactiveFilter()
allowMultiOpen
getallowMultiOpen()
setallowMultiOpen(bool: boolean)
Parameters :
Name Type Optional
bool boolean No
Returns : void
import {Inject, Injectable, OnDestroy, Optional} from '@angular/core';
import {BehaviorSubject, Observable, of, ReplaySubject, Subject, Subscription, timer} from 'rxjs';
import {TaskPanelData} from '../../../panel/task-panel-list/task-panel-data/task-panel-data';
import {TaskResourceService} from '../../../resources/engine-endpoint/task-resource.service';
import {UserService} from '../../../user/services/user.service';
import {SnackBarService} from '../../../snack-bar/services/snack-bar.service';
import {TranslateService} from '@ngx-translate/core';
import {catchError, concatMap, filter, map, mergeMap, scan, switchMap, take, tap} from 'rxjs/operators';
import {HttpParams} from '@angular/common/http';
import {Pagination} from '../../../resources/interface/pagination';
import {Task} from '../../../resources/interface/task';
import {SearchService} from '../../../search/search-service/search.service';
import {LoggerService} from '../../../logger/services/logger.service';
import {ListRange} from '@angular/cdk/collections';
import {UserComparatorService} from '../../../user/services/user-comparator.service';
import {TaskEndpoint} from '../models/task-endpoint';
import {Page} from '../../../resources/interface/page';
import {NAE_PREFERRED_TASK_ENDPOINT} from '../models/injection-token-task-endpoint';
import {PageLoadRequestContext} from '../../abstract/page-load-request-context';
import {Filter} from '../../../filter/models/filter';
import {TaskPageLoadRequestResult} from '../models/task-page-load-request-result';
import {LoadingWithFilterEmitter} from '../../../utility/loading-with-filter-emitter';
import {arrayToObservable} from '../../../utility/array-to-observable';
import {SearchIndexResolverService} from '../../../search/search-keyword-resolver-service/search-index-resolver.service';
import {AbstractSortableViewComponent} from '../../abstract/sortable-view';
import {NAE_TASK_VIEW_CONFIGURATION} from '../models/task-view-configuration-injection-token';
import {TaskViewConfiguration} from '../models/task-view-configuration';
import {ChangedFieldsMap} from '../../../event/services/interfaces/changed-fields-map';
import {PaginationParams} from '../../../utility/pagination/pagination-params';
import {createSortParam, PaginationSort} from '../../../utility/pagination/pagination-sort';


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

    protected _tasks$: Observable<Array<TaskPanelData>>;
    protected _changedFields$: Subject<ChangedFieldsMap>;
    protected _requestedPage$: BehaviorSubject<PageLoadRequestContext>;
    protected _loading$: LoadingWithFilterEmitter;
    protected _endOfData: boolean;
    protected _pagination: Pagination;
    protected _initiallyOpenOneTask: boolean;
    protected _closeTaskTabOnNoTasks: boolean;
    protected _panelUpdate$: BehaviorSubject<Array<TaskPanelData>>;
    protected _closeTab$: ReplaySubject<void>;
    protected _subInitiallyOpen: Subscription;
    protected _subCloseTask: Subscription;
    protected _subSearch: Subscription;

    // Serializing assign after cancel
    protected _allowMultiOpen: boolean;

    private readonly _initializing: boolean = true;

    constructor(protected _taskService: TaskResourceService,
                private _userService: UserService,
                private _snackBarService: SnackBarService,
                private _translate: TranslateService,
                protected _searchService: SearchService,
                private _log: LoggerService,
                private _userComparator: UserComparatorService,
                resolver: SearchIndexResolverService,
                @Optional() @Inject(NAE_PREFERRED_TASK_ENDPOINT) protected readonly _preferredEndpoint: TaskEndpoint = null,
                @Optional() @Inject(NAE_TASK_VIEW_CONFIGURATION) taskViewConfig: TaskViewConfiguration = null) {
        super(resolver);
        this._tasks$ = new Subject<Array<TaskPanelData>>();
        this._loading$ = new LoadingWithFilterEmitter();
        this._changedFields$ = new Subject<ChangedFieldsMap>();
        this._allowMultiOpen = true;
        this._endOfData = false;
        this._pagination = {
            size: 50,
            totalElements: undefined,
            totalPages: undefined,
            number: -1
        };
        this._requestedPage$ = new BehaviorSubject<PageLoadRequestContext>(
            new PageLoadRequestContext(this.activeFilter, Object.assign({}, this._pagination, {number: 0}))
        );
        this._panelUpdate$ = new BehaviorSubject<Array<TaskPanelData>>([]);
        this._closeTab$ = new ReplaySubject<void>(1);
        this._preferredEndpoint = taskViewConfig?.preferredEndpoint ?? (this._preferredEndpoint ?? TaskEndpoint.MONGO);

        this._initializing = false;

        this._subSearch = this._searchService.activeFilter$.subscribe(() => {
            this.reload();
        });

        const tasksMap$ = this._requestedPage$.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 = [{tasks: {}, requestContext: null}, pageLoadResult];
                    return arrayToObservable(results);
                } else {
                    return of(pageLoadResult);
                }
            }),
            concatMap(o => o),
            scan((acc, pageLoadResult) => {
                let result: { [k: string]: TaskPanelData };
                if (pageLoadResult.requestContext === null) {
                    return pageLoadResult.tasks;
                }

                if (pageLoadResult.requestContext.reloadCurrentTaskPage) {
                    Object.keys(acc).forEach(taskId => {
                        if (!pageLoadResult.tasks[taskId]) {
                            delete acc[taskId];
                        } else {
                            pageLoadResult.tasks[taskId].task.dataGroups = acc[taskId].task.dataGroups;
                            pageLoadResult.tasks[taskId].initiallyExpanded = acc[taskId].initiallyExpanded;
                            this.updateTask(acc[taskId].task, pageLoadResult.tasks[taskId].task);
                            this.blockTaskFields(acc[taskId].task, !(acc[taskId].task.user
                                && this._userComparator.compareUsers(acc[taskId].task.user)));
                            delete pageLoadResult.tasks[taskId];
                        }
                    });
                    result = Object.assign(acc, pageLoadResult.tasks);
                } else {
                    result = {...acc, ...pageLoadResult.tasks};
                }

                Object.assign(this._pagination, pageLoadResult.requestContext.pagination);
                if (pageLoadResult.requestContext !== null) {
                    this._loading$.off(pageLoadResult.requestContext.filter);
                }
                return result;
            }, {})
        );
        this._tasks$ = tasksMap$.pipe(
            map(v => Object.values(v) as Array<TaskPanelData>),
            map(taskArray => {
                if (taskArray.length === 1 && this._initiallyOpenOneTask) {
                    taskArray[0].task.finishDate === undefined ?
                        taskArray[0].initiallyExpanded = true :
                        taskArray[0].initiallyExpanded = false;
                }
                return taskArray;
            }),
            tap(v => this._panelUpdate$.next(v)));

        this._subInitiallyOpen = (taskViewConfig?.initiallyOpenOneTask ?? of(true)).subscribe(bool => {
            this._initiallyOpenOneTask = bool;
        });

        this._subCloseTask = (taskViewConfig?.closeTaskTabOnNoTasks ?? of(true)).subscribe(bool => {
            this._closeTaskTabOnNoTasks = bool;
        });
    }

    ngOnDestroy(): void {
        super.ngOnDestroy();
        this._changedFields$.complete();
        this._requestedPage$.complete();
        this._panelUpdate$.complete();
        this._closeTab$.complete();
        this._subInitiallyOpen.unsubscribe();
        this._subCloseTask.unsubscribe();
        this._subSearch.unsubscribe();
    }

    public get tasks$(): Observable<Array<TaskPanelData>> {
        return this._tasks$;
    }

    public get changedFields$(): Subject<ChangedFieldsMap> {
        return this._changedFields$;
    }

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

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

    public get panelUpdate(): Observable<Array<TaskPanelData>> {
        return this._panelUpdate$.asObservable();
    }

    public get closeTab(): Observable<void> {
        return this._closeTab$.asObservable();
    }

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

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

    public set allowMultiOpen(bool: boolean) {
        this._allowMultiOpen = bool;
    }

    public get allowMultiOpen(): boolean {
        return this._allowMultiOpen;
    }

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

        let request: Observable<Page<Task>>;
        if (requestContext.filter.bodyContainsQuery() || this._preferredEndpoint === TaskEndpoint.ELASTIC) {
            request = timer(200).pipe(
                switchMap(() => this._taskService.searchTask(requestContext.filter, params).pipe(take(1)))
            );
        } else {
            request = this._taskService.getTasks(requestContext.filter, params).pipe(take(1));
        }
        return request.pipe(
            catchError(err => {
                this._log.error('Loading tasks 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 tasks page is no longer relevant since the active filter has changed before it could arrive.'
                        + ' Discarding...');
                }
                return r;
            }),
            tap(t => {
                Object.assign(requestContext.pagination, t.pagination);
            }),
            tap(t => {
                if (this._pagination.totalElements && this._pagination.totalElements > 0
                    && t.pagination.totalElements === 0 && !Array.isArray(t.content) && this._closeTaskTabOnNoTasks) {
                    this._closeTab$.next();
                }
            }),
            tap(t => {
                this._endOfData = !Array.isArray(t.content)
                    || t.content.length === 0
                    || t.pagination.number === t.pagination.totalPages;
            }),
            map(tasks => Array.isArray(tasks.content) ? tasks : {...tasks, content: []}),
            map(tasks => {
                return tasks.content.reduce((acc, curr) => {
                    this.blockTaskFields(curr, !(curr.user && this._userComparator.compareUsers(curr.user)));
                    return {
                        ...acc, [curr.stringId]: {
                            task: curr,
                            changedFields: this._changedFields$,
                            initiallyExpanded: false
                        }
                    };
                }, {});
            }),
            map(tasks => ({tasks, requestContext}))
        );
    }

    private updateTask(old: Task, neww: Task) {
        Object.keys(old).forEach(key => {
            if (!neww.hasOwnProperty(key)) {
                delete old[key];
            }
        });
        Object.keys(neww).forEach(key => {
            if (neww[key] !== undefined && neww[key] !== null) {
                old[key] = neww[key];
            }
        });
        this.blockTaskFields(old, !(old.user && this._userComparator.compareUsers(old.user)));
    }

    private blockTaskFields(task: Task, block: boolean): void {
        if (!task.dataGroups) {
            return;
        }
        task.dataGroups.forEach(g => g.fields.forEach(f => f.block = block));
    }

    public nextPage(renderedRange: ListRange, totalLoaded: number, requestContext?: PageLoadRequestContext): void {
        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._requestedPage$.next(requestContext);
        }
    }

    public nextPagePagination(length: number, pageIndex: number, requestContext?: PageLoadRequestContext): void {
        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._requestedPage$.next(requestContext);
    }

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

    public reload(): void {
        if (!this._tasks$ || !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 reloadCurrentPage(force?: boolean): void {
        if (!this._tasks$ || !this._pagination) {
            return;
        }

        this._endOfData = false;
        const requestContext = new PageLoadRequestContext(this.activeFilter, this._pagination, false, true, force);
        requestContext.pagination.number = 0; // TODO [BUG] - Reloading only first page
        const range = {
            start: -1,
            end: 0
        };
        this.nextPage(range, 0, requestContext);
    }

    protected getMetaFieldSortId(): string {
        // TODO Tasks were not sortable on old frontend sorting might require elastic mapping changes on backend
        return this._lastHeaderSearchState.fieldIdentifier;
    }

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

    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;
    }
}

result-matching ""

    No results matching ""