From 58002fc00a856f14b4633600b15d473e5cd49272 Mon Sep 17 00:00:00 2001 From: palajsamuel Date: Fri, 9 Oct 2026 11:23:33 +0200 Subject: [PATCH 1/2] [NAE-2487] Add Column Configuration and Sorting to Menu Item - implemented sorting by selected headers in menu items - code adapted from task NAE-2454 --- .../header/case-header/case-header.service.ts | 12 +- .../sort-mode/abstract-sort-mode.component.ts | 2 +- .../lib/header/models/meta-fields-factory.ts | 40 ++++++ .../src/lib/header/models/public-api.ts | 1 + .../header/task-header/task-header.service.ts | 15 +- .../workflow-header.service.ts | 13 +- .../model/group-navigation-constants.ts | 20 +++ .../src/lib/resources/public-api.ts | 1 + .../src/lib/view/abstract/sortable-view.ts | 18 ++- .../models/dynamic-default-sort-token.ts | 10 ++ .../case-view/service/case-view-service.ts | 39 +++++- .../src/lib/view/public-api.ts | 1 + .../task-view/service/task-view.service.ts | 31 ++++- .../model/factory-methods.ts | 131 +++++++++++++++++- .../default-tabbed-case-view.component.ts | 8 ++ .../default-tabbed-task-view.component.ts | 7 + 16 files changed, 298 insertions(+), 51 deletions(-) create mode 100644 projects/netgrif-components-core/src/lib/header/models/meta-fields-factory.ts create mode 100644 projects/netgrif-components-core/src/lib/view/case-view/models/dynamic-default-sort-token.ts diff --git a/projects/netgrif-components-core/src/lib/header/case-header/case-header.service.ts b/projects/netgrif-components-core/src/lib/header/case-header/case-header.service.ts index e06e5ed12f..4b3e910f92 100644 --- a/projects/netgrif-components-core/src/lib/header/case-header/case-header.service.ts +++ b/projects/netgrif-components-core/src/lib/header/case-header/case-header.service.ts @@ -1,8 +1,7 @@ import {Inject, Injectable, OnDestroy, Optional} from '@angular/core'; import {AbstractHeaderService} from '../abstract-header-service'; import {HeaderType} from '../models/header-type'; -import {HeaderColumn, HeaderColumnType} from '../models/header-column'; -import {CaseMetaField} from './case-menta-enum'; +import {HeaderColumn} from '../models/header-column'; import {UserPreferenceService} from '../../user/services/user-preference.service'; import {LoggerService} from '../../logger/services/logger.service'; import {NAE_DEFAULT_HEADERS} from '../models/default-headers-token'; @@ -12,6 +11,7 @@ import {ViewIdService} from '../../user/services/view-id.service'; import {AllowedNetsService} from '../../allowed-nets/services/allowed-nets.service'; import {HeaderSortingMode} from '../models/header-sorting-mode'; import {NAE_HEADER_SORTING_MODE} from '../models/header-sorting-mode-injection-token'; +import {getCaseMetaHeaders} from '../models/meta-fields-factory'; @Injectable() @@ -40,13 +40,7 @@ export class CaseHeaderService extends AbstractHeaderService implements OnDestro } protected createMetaHeaders(): Array { - return [ - new HeaderColumn(HeaderColumnType.META, CaseMetaField.VISUAL_ID, 'headers.caseMeta.visualID', 'text'), - new HeaderColumn(HeaderColumnType.META, CaseMetaField.MONGO_ID, 'headers.caseMeta.mongoID', 'text', false), - new HeaderColumn(HeaderColumnType.META, CaseMetaField.TITLE, 'headers.caseMeta.title', 'text'), - new HeaderColumn(HeaderColumnType.META, CaseMetaField.AUTHOR, 'headers.caseMeta.author', 'user'), - new HeaderColumn(HeaderColumnType.META, CaseMetaField.CREATION_DATE, 'headers.caseMeta.creationDate', 'date'), - ]; + return getCaseMetaHeaders(); } ngOnDestroy(): void { diff --git a/projects/netgrif-components-core/src/lib/header/header-modes/sort-mode/abstract-sort-mode.component.ts b/projects/netgrif-components-core/src/lib/header/header-modes/sort-mode/abstract-sort-mode.component.ts index cba149092f..05c868985a 100644 --- a/projects/netgrif-components-core/src/lib/header/header-modes/sort-mode/abstract-sort-mode.component.ts +++ b/projects/netgrif-components-core/src/lib/header/header-modes/sort-mode/abstract-sort-mode.component.ts @@ -10,7 +10,7 @@ import {HeaderColumn} from "../../models/header-column"; }) export abstract class AbstractSortModeComponent extends AbstractHeaderModeComponent { - constructor() { + protected constructor() { super(); } diff --git a/projects/netgrif-components-core/src/lib/header/models/meta-fields-factory.ts b/projects/netgrif-components-core/src/lib/header/models/meta-fields-factory.ts new file mode 100644 index 0000000000..6458580373 --- /dev/null +++ b/projects/netgrif-components-core/src/lib/header/models/meta-fields-factory.ts @@ -0,0 +1,40 @@ +import {HeaderColumn, HeaderColumnType} from "./header-column"; +import {CaseMetaField} from "../case-header/case-menta-enum"; +import {TaskMetaField} from "../task-header/task-meta-enum"; +import {WorkflowMetaField} from "../workflow-header/workflow-meta-enum"; + +/** Returns meta-headers for case view */ +export function getCaseMetaHeaders(): HeaderColumn[] { + return [ + new HeaderColumn(HeaderColumnType.META, CaseMetaField.VISUAL_ID, 'headers.caseMeta.visualID', 'text'), + new HeaderColumn(HeaderColumnType.META, CaseMetaField.MONGO_ID, 'headers.caseMeta.mongoID', 'text', false), + new HeaderColumn(HeaderColumnType.META, CaseMetaField.TITLE, 'headers.caseMeta.title', 'text'), + new HeaderColumn(HeaderColumnType.META, CaseMetaField.AUTHOR, 'headers.caseMeta.author', 'user'), + new HeaderColumn(HeaderColumnType.META, CaseMetaField.CREATION_DATE, 'headers.caseMeta.creationDate', 'date') + ]; +} + +/** Returns meta-headers for task view */ +export function getTaskMetaHeaders(): HeaderColumn[] { + return [ + new HeaderColumn(HeaderColumnType.META, TaskMetaField.CASE, 'headers.taskMeta.case', 'text'), + new HeaderColumn(HeaderColumnType.META, TaskMetaField.CASE_ID, 'headers.taskMeta.caseID', 'text', false), + new HeaderColumn(HeaderColumnType.META, TaskMetaField.TASK_ID, 'headers.taskMeta.taskID', 'text', false), + new HeaderColumn(HeaderColumnType.META, TaskMetaField.TITLE, 'headers.caseMeta.title', 'text'), + new HeaderColumn(HeaderColumnType.META, TaskMetaField.PRIORITY, 'headers.taskMeta.priority', 'enumeration'), + new HeaderColumn(HeaderColumnType.META, TaskMetaField.USER, 'headers.taskMeta.user', 'text'), + new HeaderColumn(HeaderColumnType.META, TaskMetaField.ASSIGN_DATE, 'headers.taskMeta.assignDate', 'date'), + ]; +} + +/** Returns meta-headers for workflow view */ +export function getWorkflowMetaHeaders(): HeaderColumn[] { + return [ + new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.INITIALS, 'headers.workflowMeta.initials', 'text'), + new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.TITLE, 'headers.workflowMeta.title', 'text'), + new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.NET_ID, 'headers.workflowMeta.netId', 'text', false), + new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.VERSION, 'headers.workflowMeta.version', 'text'), + new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.AUTHOR, 'headers.workflowMeta.author', 'text'), + new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.CREATION_DATE, 'headers.workflowMeta.creationDate', 'date'), + ]; +} diff --git a/projects/netgrif-components-core/src/lib/header/models/public-api.ts b/projects/netgrif-components-core/src/lib/header/models/public-api.ts index a76016c87b..810c3bcbfe 100644 --- a/projects/netgrif-components-core/src/lib/header/models/public-api.ts +++ b/projects/netgrif-components-core/src/lib/header/models/public-api.ts @@ -4,6 +4,7 @@ export * from './header-sorting-mode'; export * from './header-sorting-mode-injection-token'; export * from './header-type'; export * from './default-headers-token'; +export * from './meta-fields-factory'; export * from './user-changes/header-change'; export * from './user-changes/header-change-description'; diff --git a/projects/netgrif-components-core/src/lib/header/task-header/task-header.service.ts b/projects/netgrif-components-core/src/lib/header/task-header/task-header.service.ts index 47e9ecb1cb..f142995c8e 100644 --- a/projects/netgrif-components-core/src/lib/header/task-header/task-header.service.ts +++ b/projects/netgrif-components-core/src/lib/header/task-header/task-header.service.ts @@ -1,8 +1,7 @@ import {Inject, Injectable, OnDestroy, Optional} from '@angular/core'; import {AbstractHeaderService} from '../abstract-header-service'; import {HeaderType} from '../models/header-type'; -import {HeaderColumn, HeaderColumnType} from '../models/header-column'; -import {TaskMetaField} from './task-meta-enum'; +import {HeaderColumn} from '../models/header-column'; import {UserPreferenceService} from '../../user/services/user-preference.service'; import {LoggerService} from '../../logger/services/logger.service'; import {Subscription} from 'rxjs'; @@ -12,7 +11,7 @@ import {AllowedNetsService} from '../../allowed-nets/services/allowed-nets.servi import {OverflowService} from '../services/overflow.service'; import {HeaderSortingMode} from '../models/header-sorting-mode'; import {NAE_HEADER_SORTING_MODE} from '../models/header-sorting-mode-injection-token'; - +import {getTaskMetaHeaders} from '../models/meta-fields-factory'; @Injectable() export class TaskHeaderService extends AbstractHeaderService implements OnDestroy { @@ -40,15 +39,7 @@ export class TaskHeaderService extends AbstractHeaderService implements OnDestro } protected createMetaHeaders(): Array { - return [ - new HeaderColumn(HeaderColumnType.META, TaskMetaField.CASE, 'headers.taskMeta.case', 'text'), - new HeaderColumn(HeaderColumnType.META, TaskMetaField.CASE_ID, 'headers.taskMeta.caseID', 'text', false), - new HeaderColumn(HeaderColumnType.META, TaskMetaField.TASK_ID, 'headers.taskMeta.taskID', 'text', false), - new HeaderColumn(HeaderColumnType.META, TaskMetaField.TITLE, 'headers.caseMeta.title', 'text'), - new HeaderColumn(HeaderColumnType.META, TaskMetaField.PRIORITY, 'headers.taskMeta.priority', 'enumeration'), - new HeaderColumn(HeaderColumnType.META, TaskMetaField.USER, 'headers.taskMeta.user', 'text'), - new HeaderColumn(HeaderColumnType.META, TaskMetaField.ASSIGN_DATE, 'headers.taskMeta.assignDate', 'date'), - ]; + return getTaskMetaHeaders(); } protected saveState() { diff --git a/projects/netgrif-components-core/src/lib/header/workflow-header/workflow-header.service.ts b/projects/netgrif-components-core/src/lib/header/workflow-header/workflow-header.service.ts index 71e5bc8da9..6ddb07753b 100644 --- a/projects/netgrif-components-core/src/lib/header/workflow-header/workflow-header.service.ts +++ b/projects/netgrif-components-core/src/lib/header/workflow-header/workflow-header.service.ts @@ -1,14 +1,14 @@ import {Inject, Injectable, OnDestroy, Optional} from '@angular/core'; import {AbstractHeaderService} from '../abstract-header-service'; import {HeaderType} from '../models/header-type'; -import {HeaderColumn, HeaderColumnType} from '../models/header-column'; +import {HeaderColumn} from '../models/header-column'; import {UserPreferenceService} from '../../user/services/user-preference.service'; import {LoggerService} from '../../logger/services/logger.service'; -import {WorkflowMetaField} from './workflow-meta-enum'; import {ViewIdService} from '../../user/services/view-id.service'; import {OverflowService} from '../services/overflow.service'; import {HeaderSortingMode} from '../models/header-sorting-mode'; import {NAE_HEADER_SORTING_MODE} from '../models/header-sorting-mode-injection-token'; +import {getWorkflowMetaHeaders} from '../models/meta-fields-factory'; @Injectable() export class WorkflowHeaderService extends AbstractHeaderService implements OnDestroy { @@ -25,14 +25,7 @@ export class WorkflowHeaderService extends AbstractHeaderService implements OnDe } protected createMetaHeaders(): Array { - return [ - new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.INITIALS, 'headers.workflowMeta.initials', 'text'), - new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.TITLE, 'headers.workflowMeta.title', 'text'), - new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.NET_ID, 'headers.workflowMeta.netId', 'text', false), - new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.VERSION, 'headers.workflowMeta.version', 'text'), - new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.AUTHOR, 'headers.workflowMeta.author', 'text'), - new HeaderColumn(HeaderColumnType.META, WorkflowMetaField.CREATION_DATE, 'headers.workflowMeta.creationDate', 'date'), - ]; + return getWorkflowMetaHeaders(); } ngOnDestroy(): void { diff --git a/projects/netgrif-components-core/src/lib/navigation/model/group-navigation-constants.ts b/projects/netgrif-components-core/src/lib/navigation/model/group-navigation-constants.ts index 5050ab5639..32fe156260 100644 --- a/projects/netgrif-components-core/src/lib/navigation/model/group-navigation-constants.ts +++ b/projects/netgrif-components-core/src/lib/navigation/model/group-navigation-constants.ts @@ -140,6 +140,26 @@ export enum GroupNavigationConstants { * */ ITEM_FIELD_ID_TASK_DEFAULT_HEADERS = 'default_headers', + /** + * Text field, which contains unique ids of header columns for sorting separated by comma for case view + */ + ITEM_FIELD_CASE_HEADERS_SORT_MODE_ACTIVE = 'headers_sort_mode_active', + + /** + * Enumeration map field, which contains default direction for sorting for case view + */ + ITEM_FIELD_CASE_HEADERS_SORT_MODE_DIRECTION = 'headers_sort_mode_direction', + + /** + * Text field, which contains unique ids of header columns for sorting separated by comma for task view + */ + ITEM_FIELD_TASK_HEADERS_SORT_MODE_ACTIVE = 'headers_sort_mode_active', + + /** + * Enumeration map field, which contains default direction for sorting for task view + */ + ITEM_FIELD_TASK_HEADERS_SORT_MODE_DIRECTION = 'headers_sort_mode_direction', + /** * MultichoiceMap field, that contains allowed roles as value * */ diff --git a/projects/netgrif-components-core/src/lib/resources/public-api.ts b/projects/netgrif-components-core/src/lib/resources/public-api.ts index 8b901b4233..ff5e58061c 100644 --- a/projects/netgrif-components-core/src/lib/resources/public-api.ts +++ b/projects/netgrif-components-core/src/lib/resources/public-api.ts @@ -41,6 +41,7 @@ export * from './interface/group'; export * from './interface/petri-net-request-body'; export * from './interface/page'; export * from './interface/preferences'; +export * from './interface/sorting-header'; export * from './interface/user-resource-small'; export * from './interface/user-resource'; export * from './interface/create-case-request-body'; diff --git a/projects/netgrif-components-core/src/lib/view/abstract/sortable-view.ts b/projects/netgrif-components-core/src/lib/view/abstract/sortable-view.ts index 51b9fe72b1..933ea69925 100644 --- a/projects/netgrif-components-core/src/lib/view/abstract/sortable-view.ts +++ b/projects/netgrif-components-core/src/lib/view/abstract/sortable-view.ts @@ -108,20 +108,24 @@ export abstract class AbstractSortableViewComponent implements OnDestroy { } protected getPreferredSortableFieldId(column: HeaderColumn): string { - if (column.type === HeaderColumnType.META) { - return this.getMetaFieldSortId(column.fieldIdentifier); + return this.getPreferredSortableFieldIdByFieldIdentifier(column.fieldIdentifier, column.type, column.fieldType); + } + + protected getPreferredSortableFieldIdByFieldIdentifier(fieldIdentifier: string, columnType: HeaderColumnType, fieldType: string): string { + if (columnType === HeaderColumnType.META) { + return this.getMetaFieldSortId(fieldIdentifier); } else { - switch (column.fieldType) { + switch (fieldType) { case 'number': - return this._resolver.getIndex(column.fieldIdentifier, SearchIndex.NUMBER); + return this._resolver.getIndex(fieldIdentifier, SearchIndex.NUMBER); case 'date': case 'dateTime': - return this._resolver.getIndex(column.fieldIdentifier, SearchIndex.TIMESTAMP); + return this._resolver.getIndex(fieldIdentifier, SearchIndex.TIMESTAMP); case 'actor': case 'actorList': - return this._resolver.getIndex(column.fieldIdentifier, SearchIndex.FULL_NAME, true); + return this._resolver.getIndex(fieldIdentifier, SearchIndex.FULL_NAME, true); default: - return this._resolver.getIndex(column.fieldIdentifier, SearchIndex.FULLTEXT, true); + return this._resolver.getIndex(fieldIdentifier, SearchIndex.FULLTEXT, true); } } } diff --git a/projects/netgrif-components-core/src/lib/view/case-view/models/dynamic-default-sort-token.ts b/projects/netgrif-components-core/src/lib/view/case-view/models/dynamic-default-sort-token.ts new file mode 100644 index 0000000000..cfe52ae254 --- /dev/null +++ b/projects/netgrif-components-core/src/lib/view/case-view/models/dynamic-default-sort-token.ts @@ -0,0 +1,10 @@ +import {InjectionToken} from '@angular/core'; +import {SortingHeader} from '../../../resources/interface/sorting-header'; + +/** + * Default sorting of the view, that is used when the user has no sorting stored in his preferences. + * + * Each {@link SortingHeader} references a header column by its unique id (e.g. `meta-title` or `-`). + * The order of the array defines the sorting priority. + */ +export const NAE_DYNAMIC_DEFAULT_SORT = new InjectionToken>('NaeDynamicDefaultSort'); diff --git a/projects/netgrif-components-core/src/lib/view/case-view/service/case-view-service.ts b/projects/netgrif-components-core/src/lib/view/case-view/service/case-view-service.ts index eb06a6e518..f49f000e5b 100644 --- a/projects/netgrif-components-core/src/lib/view/case-view/service/case-view-service.ts +++ b/projects/netgrif-components-core/src/lib/view/case-view/service/case-view-service.ts @@ -37,6 +37,8 @@ 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'; +import {NAE_DYNAMIC_DEFAULT_SORT} from '../models/dynamic-default-sort-token'; +import {SortChangeDescription} from '../../../header/models/user-changes/sort-change-description'; @Injectable() export class CaseViewService extends AbstractSortableViewComponent implements OnDestroy { @@ -64,6 +66,7 @@ export class CaseViewService extends AbstractSortableViewComponent implements On protected _processService: ProcessService, resolver: SearchIndexResolverService, @Optional() @Inject(NAE_NEW_CASE_DIALOG_COMPONENT) protected _newCaseComponent: any, + @Optional() @Inject(NAE_DYNAMIC_DEFAULT_SORT) protected _dynamicDefaultSort$: Observable, @Optional() @Inject(NAE_NEW_CASE_CONFIGURATION) newCaseConfig: NewCaseConfiguration, protected _permissionService: PermissionService) { super(resolver); @@ -82,9 +85,7 @@ export class CaseViewService extends AbstractSortableViewComponent implements On totalPages: undefined, number: -1 }; - this._nextPage$ = new BehaviorSubject( - new PageLoadRequestContext(this.activeFilter, Object.assign({}, this._pagination, {number: 0})) - ); + this.requestPageWithDynamicSort(new PageLoadRequestContext(this.activeFilter, Object.assign({}, this._pagination, {number: 0}))); const casesMap = this._nextPage$.pipe( mergeMap(p => this.loadPage(p)), @@ -213,7 +214,7 @@ export class CaseViewService extends AbstractSortableViewComponent implements On if (this.isLoadingRelevantFilter(requestContext) || this._endOfData) { return; } - this._nextPage$.next(requestContext); + this.requestPageWithDynamicSort(requestContext); } private isLoadingRelevantFilter(requestContext?: PageLoadRequestContext): boolean { @@ -228,7 +229,7 @@ export class CaseViewService extends AbstractSortableViewComponent implements On const dialogRef = this._dialog.open(this._newCaseComponent, { width: '40%', minWidth: '300px', - panelClass: "dialog-responsive", + panelClass: 'dialog-responsive', data: { allowedNets$: this.getNewCaseAllowedNets(newCaseCreationConfiguration.blockNets), newCaseCreationConfiguration @@ -358,4 +359,32 @@ export class CaseViewService extends AbstractSortableViewComponent implements On public viewEnabled(aCase: Case): boolean { return this._permissionService.hasCasePermission(aCase, PermissionType.VIEW); } + + // todo check header sort resolving, this seems not user friendly + probably won't work with userPreferences + protected requestPageWithDynamicSort(requestContext: PageLoadRequestContext) { + if (!!this._dynamicDefaultSort$ && this._lastHeaderSearchState.fieldIdentifier === '') { + this._dynamicDefaultSort$.subscribe(changes => { + this._lastHeaderSearchState.sortDirection = ''; + this._preferredSortableHeaders = []; + changes.forEach( + change => this._preferredSortableHeaders.push( + { + propertyId: this.getPreferredSortableFieldIdByFieldIdentifier(change.fieldIdentifier, change.columnType,change.fieldType), + sortDirection: change.sortDirection + } + )); + this.requestNextPage(requestContext); + }); + } else { + this.requestNextPage(requestContext); + } + } + + protected requestNextPage(page: PageLoadRequestContext) { + if (!this._nextPage$) { + this._nextPage$ = new BehaviorSubject(page); + } else { + this._nextPage$.next(page); + } + } } diff --git a/projects/netgrif-components-core/src/lib/view/public-api.ts b/projects/netgrif-components-core/src/lib/view/public-api.ts index bd5872ff56..4f319ad610 100644 --- a/projects/netgrif-components-core/src/lib/view/public-api.ts +++ b/projects/netgrif-components-core/src/lib/view/public-api.ts @@ -31,6 +31,7 @@ export * from './abstract/page-load-request-context'; export * from './case-view/models/autoswitch-token' export * from './case-view/models/open-existing-tab-token' export * from './case-view/models/new-case-configuration-injection-token'; +export * from './case-view/models/dynamic-default-sort-token'; export * from './task-view/models/injection-token-task-endpoint'; export * from './task-view/models/task-view-configuration-injection-token'; export * from './workflow-view/models/injection-token-workflow-service'; diff --git a/projects/netgrif-components-core/src/lib/view/task-view/service/task-view.service.ts b/projects/netgrif-components-core/src/lib/view/task-view/service/task-view.service.ts index dcfa0f09e8..d8b7d25b4c 100644 --- a/projects/netgrif-components-core/src/lib/view/task-view/service/task-view.service.ts +++ b/projects/netgrif-components-core/src/lib/view/task-view/service/task-view.service.ts @@ -28,6 +28,8 @@ 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'; +import {NAE_DYNAMIC_DEFAULT_SORT} from '../../case-view/models/dynamic-default-sort-token'; +import {SortChangeDescription} from '../../../header/models/user-changes/sort-change-description'; @Injectable() @@ -62,6 +64,7 @@ export class TaskViewService extends AbstractSortableViewComponent implements On private _userComparator: UserComparatorService, resolver: SearchIndexResolverService, @Optional() @Inject(NAE_PREFERRED_TASK_ENDPOINT) protected readonly _preferredEndpoint: TaskEndpoint = null, + @Optional() @Inject(NAE_DYNAMIC_DEFAULT_SORT) protected _dynamicDefaultSort$: Observable, @Optional() @Inject(NAE_TASK_VIEW_CONFIGURATION) taskViewConfig: TaskViewConfiguration = null) { super(resolver); this._tasks$ = new Subject>(); @@ -75,9 +78,6 @@ export class TaskViewService extends AbstractSortableViewComponent implements On totalPages: undefined, number: -1 }; - this._requestedPage$ = new BehaviorSubject( - new PageLoadRequestContext(this.activeFilter, Object.assign({}, this._pagination, {number: 0})) - ); this._panelUpdate$ = new BehaviorSubject>([]); this._closeTab$ = new ReplaySubject(1); this._preferredEndpoint = taskViewConfig?.preferredEndpoint ?? (this._preferredEndpoint ?? TaskEndpoint.MONGO); @@ -88,6 +88,8 @@ export class TaskViewService extends AbstractSortableViewComponent implements On this.reload(); }); + this.requestPageWithDynamicSort(new PageLoadRequestContext(this.activeFilter, Object.assign({}, this._pagination, {number: 0}))); + const tasksMap$ = this._requestedPage$.pipe( mergeMap(p => this.loadPage(p)), map(pageLoadResult => { @@ -307,7 +309,7 @@ export class TaskViewService extends AbstractSortableViewComponent implements On } if (renderedRange.end === totalLoaded) { - this._requestedPage$.next(requestContext); + this.requestPageWithDynamicSort(requestContext); } } @@ -321,7 +323,7 @@ export class TaskViewService extends AbstractSortableViewComponent implements On if (this.isLoadingRelevantFilter(requestContext) || this._endOfData) { return; } - this._requestedPage$.next(requestContext); + this.requestPageWithDynamicSort(requestContext); } private isLoadingRelevantFilter(requestContext?: PageLoadRequestContext): boolean { @@ -373,4 +375,23 @@ export class TaskViewService extends AbstractSortableViewComponent implements On params = params.set(PaginationParams.PAGE_NUMBER, `${pagination.number}`); return params; } + + protected requestPageWithDynamicSort(requestContext: PageLoadRequestContext) { + if (!!this._dynamicDefaultSort$ && this._lastHeaderSearchState.fieldIdentifier === '') { + this._dynamicDefaultSort$.subscribe(sortChangeDesc => { + this._lastHeaderSearchState = sortChangeDesc; + this.requestNextPage(requestContext); + }); + } else { + this.requestNextPage(requestContext); + } + } + + protected requestNextPage(page: PageLoadRequestContext) { + if (!this._requestedPage$) { + this._requestedPage$ = new BehaviorSubject(page); + } else { + this._requestedPage$.next(page); + } + } } diff --git a/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/model/factory-methods.ts b/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/model/factory-methods.ts index bdd4b58ca2..00a57c0fae 100644 --- a/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/model/factory-methods.ts +++ b/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/model/factory-methods.ts @@ -1,16 +1,31 @@ import { AllowedNetsService, AllowedNetsServiceFactory, - BaseFilter, BaseAllowedNetsService, + BaseFilter, Category, + CategoryResolverService, + DataGroup, + extractFieldValueFromData, + FilterExtractionService, + getCaseMetaHeaders, + getTaskMetaHeaders, + GroupNavigationConstants, + HeaderColumn, + HeaderColumnType, + ImmediateData, navigationItemTaskAllowedNetsServiceFactory, + navigationItemTaskCategoryFactory, navigationItemTaskFilterFactory, - navigationItemTaskCategoryFactory, CategoryResolverService, FilterExtractionService + ProcessService, + SortChangeDescription, } from '@netgrif/components-core'; import {InjectedTabbedCaseViewDataWithNavigationItemTaskData} from './injected-tabbed-case-view-data-with-navigation-item-task-data'; import {Type} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; +import {SortDirection} from '@angular/material/sort'; +import {Observable, of} from 'rxjs'; +import {map} from 'rxjs/operators'; /** * Converts a navigation item case task data injected by the {@link NAE_TAB_DATA} injection token into a {@link BaseFilter} instance @@ -57,3 +72,115 @@ export function filterCaseTabbedDataSearchCategoriesFactory(categoryResolverServ defaultCaseSearchCategories, defaultTaskSearchCategories); } + +/** + * Builds {@link SortChangeDescription} based on dynamic menu item data for case view. This can be used for default + * sorting when the view is being initialized + * + * @return Observable of sort change description or undefined if the data is wrong or no data provided + */ +export function buildDynamicSortChangeDescriptionForCase$(menuItemData: Array, processService: ProcessService): Observable | undefined { + return buildDynamicSortChangeDescription$(menuItemData, processService, 'case'); +} + +/** + * Builds {@link SortChangeDescription} based on dynamic menu item data for task view. This can be used for default + * sorting when the view is being initialized + * + * @return Observable of sort change description or undefined if the data is wrong or no data provided + */ +export function buildDynamicSortChangeDescriptionForTask$(menuItemData: Array, processService: ProcessService): Observable | undefined { + return buildDynamicSortChangeDescription$(menuItemData, processService, 'task'); +} + +function buildDynamicSortChangeDescription$(menuItemData: Array, processService: ProcessService, + viewType: 'case' | 'task'): Observable | undefined { + if (!menuItemData || menuItemData.length === 0) { + return undefined; + } + + const processDataCache: Map = new Map; + + let activeColumnsRaw: string | undefined; + let direction: SortDirection = ''; + try { + activeColumnsRaw = extractFieldValueFromData(menuItemData, + viewType === 'task' ? GroupNavigationConstants.ITEM_FIELD_TASK_HEADERS_SORT_MODE_ACTIVE : GroupNavigationConstants.ITEM_FIELD_CASE_HEADERS_SORT_MODE_ACTIVE); + direction = extractFieldValueFromData(menuItemData, + viewType === 'task' ? GroupNavigationConstants.ITEM_FIELD_TASK_HEADERS_SORT_MODE_DIRECTION : GroupNavigationConstants.ITEM_FIELD_CASE_HEADERS_SORT_MODE_DIRECTION) ?? ''; + } catch (e) { + // sort configuration fields are not part of the menu item data + return undefined; + } + + if (!activeColumnsRaw || activeColumnsRaw.trim() === '') { + return undefined; + } + + const activeColumns: string[] = activeColumnsRaw.split(',').map(col => col.trim()).filter(col => col !== ''); + if (activeColumns.length === 0) { + return undefined; + } + + const result: SortChangeDescription[] = []; + for (const activeColumn of activeColumns) { + const firstDashIdx = activeColumn.indexOf('-'); + if (firstDashIdx === -1) { + return undefined; + } + + const colTypeRaw: string = activeColumn.substring(0, firstDashIdx); + let colType: HeaderColumnType; + let processIdentifier: string; + if (!!colTypeRaw && colTypeRaw === HeaderColumnType.META) { + colType = HeaderColumnType.META; + } else if (!!colTypeRaw && colTypeRaw !== '') { + processIdentifier = colTypeRaw; + colType = HeaderColumnType.IMMEDIATE; + } + + const colIdentifier: string = activeColumn.substring(firstDashIdx + 1, activeColumn.length); + + if (!!processIdentifier) { + let immediateData: ImmediateData[] = processDataCache.get(processIdentifier); + if (!!immediateData) { + result.push(createSortChangeDescription(immediateData.find(data => data.stringId === colIdentifier)?.type, colType, colIdentifier, direction)); + continue; + } + processService.getNet(processIdentifier).pipe( + map(net => { + processDataCache[processIdentifier] = net.immediateData; + result.push(createSortChangeDescription(net.immediateData.find(data => data.stringId === colIdentifier)?.type, colType, colIdentifier, direction)); + }) + ); + } else { + result.push( + createSortChangeDescription(determineMetaFieldType(viewType, colIdentifier), colType, colIdentifier, direction)); + } + } + return of(result); +} + +function createSortChangeDescription(fieldType: string, colType: HeaderColumnType, colIdentifier: string, direction: SortDirection) { + return { + columnType: colType, + fieldIdentifier: colIdentifier, + sortDirection: direction, + columnIdentifier: -1, + fieldType: !!fieldType ? fieldType : 'text' + }; +} + +function determineMetaFieldType(viewType: 'case' | 'task', colIdentifier: string): string { + let metaFields: HeaderColumn[]; + if (viewType === 'case') { + metaFields = getCaseMetaHeaders(); + } else if (viewType === 'task') { + metaFields = getTaskMetaHeaders(); + } + if (!metaFields) { + return 'text'; + } + const fieldType: string = metaFields.find(headerCol => headerCol.fieldIdentifier === colIdentifier)?.fieldType; + return !!fieldType ? fieldType : 'text'; +} diff --git a/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-case-view/default-tabbed-case-view.component.ts b/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-case-view/default-tabbed-case-view.component.ts index 05cbc29824..b14a538208 100644 --- a/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-case-view/default-tabbed-case-view.component.ts +++ b/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-case-view/default-tabbed-case-view.component.ts @@ -28,12 +28,15 @@ import { navigationItemCaseViewDefaultHeadersFactory, NAE_NAVIGATION_ITEM_TASK_DATA, OverflowService, + NAE_DYNAMIC_DEFAULT_SORT, + ProcessService, } from '@netgrif/components-core'; import {HeaderComponent} from '../../../../../header/header.component'; import { InjectedTabbedCaseViewDataWithNavigationItemTaskData } from '../../model/injected-tabbed-case-view-data-with-navigation-item-task-data'; import { + buildDynamicSortChangeDescriptionForCase$, filterCaseTabbedDataAllowedNetsServiceFactory, filterCaseTabbedDataFilterFactory, filterCaseTabbedDataSearchCategoriesFactory @@ -69,6 +72,11 @@ import {ActivatedRoute} from '@angular/router'; provide: NAE_DEFAULT_HEADERS, useFactory: navigationItemCaseViewDefaultHeadersFactory, deps: [NAE_NAVIGATION_ITEM_TASK_DATA] + }, + { + provide: NAE_DYNAMIC_DEFAULT_SORT, + useFactory: buildDynamicSortChangeDescriptionForCase$, + deps: [NAE_NAVIGATION_ITEM_TASK_DATA, ProcessService] } ] }) diff --git a/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-task-view/default-tabbed-task-view.component.ts b/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-task-view/default-tabbed-task-view.component.ts index 7f848cca8f..26873f1f86 100644 --- a/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-task-view/default-tabbed-task-view.component.ts +++ b/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-task-view/default-tabbed-task-view.component.ts @@ -19,11 +19,13 @@ import { NAE_DEFAULT_HEADERS, NAE_NAVIGATION_ITEM_TASK_DATA, OverflowService, + NAE_DYNAMIC_DEFAULT_SORT, ProcessService, } from '@netgrif/components-core'; import {HeaderComponent} from '../../../../../header/header.component'; import { InjectedTabbedTaskViewDataWithNavigationItemTaskData } from "../../model/injected-tabbed-task-view-data-with-navigation-item-task-data"; +import {buildDynamicSortChangeDescriptionForTask$} from "../../model/factory-methods"; export function baseFilterFactory(injectedTabData: InjectedTabbedTaskViewDataWithNavigationItemTaskData) { return { @@ -61,6 +63,11 @@ export function baseFilterFactory(injectedTabData: InjectedTabbedTaskViewDataWit provide: NAE_DEFAULT_HEADERS, useFactory: navigationItemTaskViewDefaultHeadersFactory, deps: [[new Optional(), NAE_NAVIGATION_ITEM_TASK_DATA]] + }, + { + provide: NAE_DYNAMIC_DEFAULT_SORT, + useFactory: buildDynamicSortChangeDescriptionForTask$, + deps: [NAE_NAVIGATION_ITEM_TASK_DATA, ProcessService] } ] }) From 0ce101d0ff2945c131f830d966edd3b127416497 Mon Sep 17 00:00:00 2001 From: palajsamuel Date: Fri, 9 Oct 2026 16:46:14 +0200 Subject: [PATCH 2/2] [NAE-2487] Add Column Configuration and Sorting to Menu Item - case/task view service differences fix - factory-methods buildDynamicSortChangeDescription$ function dead code and caching rework --- .../models/dynamic-default-sort-token.ts | 10 +---- .../case-view/service/case-view-service.ts | 38 +++++++++---------- .../task-view/service/task-view.service.ts | 29 +++++++++----- .../model/factory-methods.ts | 27 +++++-------- 4 files changed, 49 insertions(+), 55 deletions(-) diff --git a/projects/netgrif-components-core/src/lib/view/case-view/models/dynamic-default-sort-token.ts b/projects/netgrif-components-core/src/lib/view/case-view/models/dynamic-default-sort-token.ts index cfe52ae254..51ba15f9ef 100644 --- a/projects/netgrif-components-core/src/lib/view/case-view/models/dynamic-default-sort-token.ts +++ b/projects/netgrif-components-core/src/lib/view/case-view/models/dynamic-default-sort-token.ts @@ -1,10 +1,4 @@ import {InjectionToken} from '@angular/core'; -import {SortingHeader} from '../../../resources/interface/sorting-header'; +import {SortChangeDescription} from '../../../header/models/user-changes/sort-change-description'; -/** - * Default sorting of the view, that is used when the user has no sorting stored in his preferences. - * - * Each {@link SortingHeader} references a header column by its unique id (e.g. `meta-title` or `-`). - * The order of the array defines the sorting priority. - */ -export const NAE_DYNAMIC_DEFAULT_SORT = new InjectionToken>('NaeDynamicDefaultSort'); +export const NAE_DYNAMIC_DEFAULT_SORT = new InjectionToken>('NaeDynamicDefaultSort'); diff --git a/projects/netgrif-components-core/src/lib/view/case-view/service/case-view-service.ts b/projects/netgrif-components-core/src/lib/view/case-view/service/case-view-service.ts index f49f000e5b..9f6b18b62d 100644 --- a/projects/netgrif-components-core/src/lib/view/case-view/service/case-view-service.ts +++ b/projects/netgrif-components-core/src/lib/view/case-view/service/case-view-service.ts @@ -1,13 +1,13 @@ 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 {ReplaySubject, 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 {catchError, concatMap, filter, map, mergeMap, scan, switchMap, take, 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'; @@ -49,7 +49,7 @@ export class CaseViewService extends AbstractSortableViewComponent implements On protected _loading$: LoadingWithFilterEmitter; protected _cases$: Observable>; - protected _nextPage$: BehaviorSubject; + protected _nextPage$: ReplaySubject; protected _endOfData: boolean; protected _pagination: Pagination; protected _newCaseConfiguration: NewCaseConfiguration; @@ -85,6 +85,7 @@ export class CaseViewService extends AbstractSortableViewComponent implements On totalPages: undefined, number: -1 }; + this._nextPage$ = new ReplaySubject(1); this.requestPageWithDynamicSort(new PageLoadRequestContext(this.activeFilter, Object.assign({}, this._pagination, {number: 0}))); const casesMap = this._nextPage$.pipe( @@ -362,17 +363,20 @@ export class CaseViewService extends AbstractSortableViewComponent implements On // todo check header sort resolving, this seems not user friendly + probably won't work with userPreferences protected requestPageWithDynamicSort(requestContext: PageLoadRequestContext) { - if (!!this._dynamicDefaultSort$ && this._lastHeaderSearchState.fieldIdentifier === '') { - this._dynamicDefaultSort$.subscribe(changes => { - this._lastHeaderSearchState.sortDirection = ''; - this._preferredSortableHeaders = []; - changes.forEach( - change => this._preferredSortableHeaders.push( - { - propertyId: this.getPreferredSortableFieldIdByFieldIdentifier(change.fieldIdentifier, change.columnType,change.fieldType), - sortDirection: change.sortDirection - } - )); + if (!!this._dynamicDefaultSort$ + && this._lastHeaderSearchState.fieldIdentifier === '' + && this._preferredSortableHeaders.length === 0) { + this._dynamicDefaultSort$.pipe(take(1)).subscribe(changes => { + if (this._preferredSortableHeaders.length === 0) { + this._lastHeaderSearchState.sortDirection = ''; + changes.forEach( + change => this._preferredSortableHeaders.push( + { + propertyId: this.getPreferredSortableFieldIdByFieldIdentifier(change.fieldIdentifier, change.columnType, change.fieldType), + sortDirection: change.sortDirection + } + )); + } this.requestNextPage(requestContext); }); } else { @@ -381,10 +385,6 @@ export class CaseViewService extends AbstractSortableViewComponent implements On } protected requestNextPage(page: PageLoadRequestContext) { - if (!this._nextPage$) { - this._nextPage$ = new BehaviorSubject(page); - } else { - this._nextPage$.next(page); - } + this._nextPage$.next(page); } } diff --git a/projects/netgrif-components-core/src/lib/view/task-view/service/task-view.service.ts b/projects/netgrif-components-core/src/lib/view/task-view/service/task-view.service.ts index d8b7d25b4c..2600004fab 100644 --- a/projects/netgrif-components-core/src/lib/view/task-view/service/task-view.service.ts +++ b/projects/netgrif-components-core/src/lib/view/task-view/service/task-view.service.ts @@ -37,7 +37,7 @@ export class TaskViewService extends AbstractSortableViewComponent implements On protected _tasks$: Observable>; protected _changedFields$: Subject; - protected _requestedPage$: BehaviorSubject; + protected _requestedPage$: ReplaySubject; protected _loading$: LoadingWithFilterEmitter; protected _endOfData: boolean; protected _pagination: Pagination; @@ -64,7 +64,7 @@ export class TaskViewService extends AbstractSortableViewComponent implements On private _userComparator: UserComparatorService, resolver: SearchIndexResolverService, @Optional() @Inject(NAE_PREFERRED_TASK_ENDPOINT) protected readonly _preferredEndpoint: TaskEndpoint = null, - @Optional() @Inject(NAE_DYNAMIC_DEFAULT_SORT) protected _dynamicDefaultSort$: Observable, + @Optional() @Inject(NAE_DYNAMIC_DEFAULT_SORT) protected _dynamicDefaultSort$: Observable, @Optional() @Inject(NAE_TASK_VIEW_CONFIGURATION) taskViewConfig: TaskViewConfiguration = null) { super(resolver); this._tasks$ = new Subject>(); @@ -78,6 +78,7 @@ export class TaskViewService extends AbstractSortableViewComponent implements On totalPages: undefined, number: -1 }; + this._requestedPage$ = new ReplaySubject(1); this._panelUpdate$ = new BehaviorSubject>([]); this._closeTab$ = new ReplaySubject(1); this._preferredEndpoint = taskViewConfig?.preferredEndpoint ?? (this._preferredEndpoint ?? TaskEndpoint.MONGO); @@ -376,10 +377,22 @@ export class TaskViewService extends AbstractSortableViewComponent implements On return params; } + // todo check header sort resolving, this seems not user friendly + probably won't work with userPreferences protected requestPageWithDynamicSort(requestContext: PageLoadRequestContext) { - if (!!this._dynamicDefaultSort$ && this._lastHeaderSearchState.fieldIdentifier === '') { - this._dynamicDefaultSort$.subscribe(sortChangeDesc => { - this._lastHeaderSearchState = sortChangeDesc; + if (!!this._dynamicDefaultSort$ + && this._lastHeaderSearchState.fieldIdentifier === '' + && this._preferredSortableHeaders.length === 0) { + this._dynamicDefaultSort$.pipe(take(1)).subscribe(changes => { + if (this._preferredSortableHeaders.length === 0) { + this._lastHeaderSearchState.sortDirection = ''; + changes.forEach( + change => this._preferredSortableHeaders.push( + { + propertyId: this.getPreferredSortableFieldIdByFieldIdentifier(change.fieldIdentifier, change.columnType, change.fieldType), + sortDirection: change.sortDirection + } + )); + } this.requestNextPage(requestContext); }); } else { @@ -388,10 +401,6 @@ export class TaskViewService extends AbstractSortableViewComponent implements On } protected requestNextPage(page: PageLoadRequestContext) { - if (!this._requestedPage$) { - this._requestedPage$ = new BehaviorSubject(page); - } else { - this._requestedPage$.next(page); - } + this._requestedPage$.next(page); } } diff --git a/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/model/factory-methods.ts b/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/model/factory-methods.ts index 00a57c0fae..5d54faf54f 100644 --- a/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/model/factory-methods.ts +++ b/projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/model/factory-methods.ts @@ -24,7 +24,7 @@ import {InjectedTabbedCaseViewDataWithNavigationItemTaskData} from './injected-t import {Type} from '@angular/core'; import {ActivatedRoute} from '@angular/router'; import {SortDirection} from '@angular/material/sort'; -import {Observable, of} from 'rxjs'; +import {forkJoin, Observable, of} from 'rxjs'; import {map} from 'rxjs/operators'; /** @@ -99,8 +99,6 @@ function buildDynamicSortChangeDescription$(menuItemData: Array, proc return undefined; } - const processDataCache: Map = new Map; - let activeColumnsRaw: string | undefined; let direction: SortDirection = ''; try { @@ -122,7 +120,7 @@ function buildDynamicSortChangeDescription$(menuItemData: Array, proc return undefined; } - const result: SortChangeDescription[] = []; + const result$: Array> = []; for (const activeColumn of activeColumns) { const firstDashIdx = activeColumn.indexOf('-'); if (firstDashIdx === -1) { @@ -142,23 +140,16 @@ function buildDynamicSortChangeDescription$(menuItemData: Array, proc const colIdentifier: string = activeColumn.substring(firstDashIdx + 1, activeColumn.length); if (!!processIdentifier) { - let immediateData: ImmediateData[] = processDataCache.get(processIdentifier); - if (!!immediateData) { - result.push(createSortChangeDescription(immediateData.find(data => data.stringId === colIdentifier)?.type, colType, colIdentifier, direction)); - continue; - } - processService.getNet(processIdentifier).pipe( - map(net => { - processDataCache[processIdentifier] = net.immediateData; - result.push(createSortChangeDescription(net.immediateData.find(data => data.stringId === colIdentifier)?.type, colType, colIdentifier, direction)); - }) - ); + result$.push(processService.getNet(processIdentifier).pipe( + map(net => createSortChangeDescription( + net?.immediateData?.find(data => data.stringId === colIdentifier)?.type, colType, colIdentifier, direction)) + )); } else { - result.push( - createSortChangeDescription(determineMetaFieldType(viewType, colIdentifier), colType, colIdentifier, direction)); + result$.push(of( + createSortChangeDescription(determineMetaFieldType(viewType, colIdentifier), colType, colIdentifier, direction))); } } - return of(result); + return forkJoin(result$); } function createSortChangeDescription(fieldType: string, colType: HeaderColumnType, colIdentifier: string, direction: SortDirection) {