From f3e4ff5aa3a297615645984942d7bb0b81f6dd37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Seku=C5=82a?= Date: Mon, 6 Jun 2022 15:36:05 +0200 Subject: [PATCH] [AAE-7856] Show process variables in table (#7630) * [AAE-7856] Show variables in table * Exclude flaky tests * Revert "Exclude flaky tests" This reverts commit 6ac24cc14a90d7b494fbc4dcc72e073e83a46457. --- docs/core/components/data-column.component.md | 31 ++++++++ .../data/share-datatable-adapter.ts | 4 + lib/core/data-column/data-column.component.ts | 8 ++ .../columns-selector.component.html | 30 ++++---- .../columns-selector.component.scss | 8 +- .../datatable/datatable.component.html | 6 +- lib/core/datatable/data/data-column.model.ts | 3 +- lib/core/datatable/data/data-table.schema.ts | 6 +- lib/core/datatable/data/datatable-adapter.ts | 1 + .../datatable/data/object-datacolumn.model.ts | 4 +- .../data/object-datatable-adapter.ts | 4 + lib/core/mock/data-column.mock.ts | 22 ++++++ lib/core/mock/public-api.ts | 1 + .../src/lib/models/column-data-type.model.ts | 4 + .../models/process-definition-cloud.model.ts | 4 + .../models/process-instance-variable.model.ts | 33 ++++++++ .../src/lib/models/variable-definition.ts | 25 ++++++ .../process-list-cloud.component.html | 1 + .../process-list-cloud.component.spec.ts | 70 +++++++++++++++-- .../process-list-cloud.component.ts | 56 ++++++++++---- .../process-list-datatable-adapter.spec.ts | 53 +++++++++++++ .../process-list-datatable-adapter.ts | 23 ++++++ .../mock/process-instance-variable.mock.ts | 19 +++++ .../mock/process-list-service.mock.ts | 8 +- .../models/data-column-custom-data.ts | 4 + .../perocess-instance-cloud-view.model.ts | 25 ++++++ .../process-cloud-query-request.model.ts | 3 + .../process-list-cloud.service.spec.ts | 39 +++++++++- .../services/process-list-cloud.service.ts | 77 ++++++++++++++++++- .../models/process-instance-cloud.model.ts | 2 + .../services/start-process-cloud.service.ts | 4 +- lib/process-services-cloud/src/public-api.ts | 2 + 32 files changed, 528 insertions(+), 52 deletions(-) create mode 100644 lib/core/mock/data-column.mock.ts create mode 100644 lib/process-services-cloud/src/lib/models/column-data-type.model.ts create mode 100644 lib/process-services-cloud/src/lib/models/process-instance-variable.model.ts create mode 100644 lib/process-services-cloud/src/lib/models/variable-definition.ts create mode 100644 lib/process-services-cloud/src/lib/process/process-list/datatable/process-list-datatable-adapter.spec.ts create mode 100644 lib/process-services-cloud/src/lib/process/process-list/datatable/process-list-datatable-adapter.ts create mode 100644 lib/process-services-cloud/src/lib/process/process-list/mock/process-instance-variable.mock.ts create mode 100644 lib/process-services-cloud/src/lib/process/process-list/models/data-column-custom-data.ts create mode 100644 lib/process-services-cloud/src/lib/process/process-list/models/perocess-instance-cloud-view.model.ts diff --git a/docs/core/components/data-column.component.md b/docs/core/components/data-column.component.md index 6ab5c1c3424..ad6e280df17 100644 --- a/docs/core/components/data-column.component.md +++ b/docs/core/components/data-column.component.md @@ -21,6 +21,7 @@ Defines column properties for DataTable, Tasklist, Document List and other compo - [Column Template](#column-template) - [Styling Techniques](#styling-techniques) - [Using the copyContent option](#using-the-copycontent-option) + - [Exapmple of column customData](#example-of-column-customData) - [See also](#see-also) ## Basic Usage @@ -52,6 +53,7 @@ Defines column properties for DataTable, Tasklist, Document List and other compo | formatTooltip | `Function` | | Custom tooltip formatter function. | | key | `string` | | Data source key. Can be either a column/property key like `title` or a property path like `createdBy.name`. | | sortable | `boolean` | true | Toggles ability to sort by this column, for example by clicking the column header. | +| customData | `Generic` | any | Any feature specific data | | draggable | `boolean` | false | Toggles drag and drop for header column. | | isHidden | `boolean` | false | Hides columns | | sortingKey | `string` | | When using server side sorting the column used by the api call where the sorting will be performed | @@ -351,6 +353,35 @@ HTML `` element example: ``` +### Example of column customData + +If you would like to pass any custom data related to your specific feature, you can use customData + +HTML `` element example: + +```html + +``` + +You can use generic type for `DataColumn` in order to get intellisense working e.g. + +```ts +const dataColumn: DataColumn<{ shouldPerformActionIfDisplayed: boolean }> = { + ... + customData: { shouldPerformActionIfDisplayed: true } +} + +// We should get proper types +consol.log(dataColumn.customData.shouldPerformActionIfDisplayed); + +// Now we can use this data in our feature e.g. +const shouldPerformAction = this.columns + .filter(column => column.isHidden) + .some(column => column.customData?.shouldPerformActionIfDisplayed === true); + +if (shouldPerformAction) { /* action */} +``` + ## See also - [Document list component](../../content-services/components/document-list.component.md) diff --git a/lib/content-services/src/lib/document-list/data/share-datatable-adapter.ts b/lib/content-services/src/lib/document-list/data/share-datatable-adapter.ts index 0b14b22cd72..d3a79e2571b 100644 --- a/lib/content-services/src/lib/document-list/data/share-datatable-adapter.ts +++ b/lib/content-services/src/lib/document-list/data/share-datatable-adapter.ts @@ -70,6 +70,10 @@ export class ShareDataTableAdapter implements DataTableAdapter { this.allowDropFiles = allowDropFiles; } + getColumnType(_row: DataRow, col: DataColumn): string { + return col.type; + } + getRows(): Array { return this.rows; } diff --git a/lib/core/data-column/data-column.component.ts b/lib/core/data-column/data-column.component.ts index 4c9d4c3c262..4a351dec7e9 100644 --- a/lib/core/data-column/data-column.component.ts +++ b/lib/core/data-column/data-column.component.ts @@ -34,6 +34,10 @@ export class DataColumnComponent implements OnInit { @Input() key: string; + /** You can specify any custom data which can be used by any specific feature */ + @Input() + customData: any; + /** Value type for the column. Possible settings are 'text', 'image', * 'date', 'fileSize', 'location', and 'json'. */ @@ -52,6 +56,10 @@ export class DataColumnComponent implements OnInit { @Input() draggable: boolean = false; + /* Hide column */ + @Input() + isHidden: boolean = false; + /** Display title of the column, typically used for column headers. You can use the * i18n resource key to get it translated automatically. */ diff --git a/lib/core/datatable/components/columns-selector/columns-selector.component.html b/lib/core/datatable/components/columns-selector/columns-selector.component.html index 17bfb9f804a..0dbe1bf9be1 100644 --- a/lib/core/datatable/components/columns-selector/columns-selector.component.html +++ b/lib/core/datatable/components/columns-selector/columns-selector.component.html @@ -24,20 +24,22 @@ [placeholder]='"ADF-DATATABLE.COLUMNS_SELECTOR.SEARCH" | translate'> - -
- -
{{translatedTitle}}
-
-
-
+
+ +
+ +
{{translatedTitle}}
+
+
+
+
diff --git a/lib/core/datatable/components/columns-selector/columns-selector.component.scss b/lib/core/datatable/components/columns-selector/columns-selector.component.scss index 1757a5c1922..b56dc7f5aed 100644 --- a/lib/core/datatable/components/columns-selector/columns-selector.component.scss +++ b/lib/core/datatable/components/columns-selector/columns-selector.component.scss @@ -25,7 +25,13 @@ $adf-columns-selector-space: 12px; font-size: var(--theme-body-1-font-size); } - &-list-item-container { + &-list-container { + max-height: 350px; + overflow-x: hidden; + overflow-y: auto; + } + + &-list-item { margin-top: 10px; &:hover { diff --git a/lib/core/datatable/components/datatable/datatable.component.html b/lib/core/datatable/components/datatable/datatable.component.html index 2edadb9b8a2..3a19a8e489a 100644 --- a/lib/core/datatable/components/datatable/datatable.component.html +++ b/lib/core/datatable/components/datatable/datatable.component.html @@ -194,7 +194,7 @@ [adf-context-menu-enabled]="contextMenu" adf-drop-zone dropTarget="cell" [dropColumn]="col" [dropRow]="row">
- +
{{ asIconValue(row, col) }} @@ -204,12 +204,12 @@ { id?: string; key: string; type: DataColumnType; @@ -47,4 +47,5 @@ export interface DataColumn { header?: TemplateRef; draggable?: boolean; isHidden?: boolean; + customData?: T; } diff --git a/lib/core/datatable/data/data-table.schema.ts b/lib/core/datatable/data/data-table.schema.ts index 7def0d458d8..bf4b7965e85 100644 --- a/lib/core/datatable/data/data-table.schema.ts +++ b/lib/core/datatable/data/data-table.schema.ts @@ -24,7 +24,7 @@ import { ObjectDataColumn } from './object-datacolumn.model'; @Directive() // eslint-disable-next-line @angular-eslint/directive-class-suffix -export abstract class DataTableSchema { +export abstract class DataTableSchema { @ContentChild(DataColumnListComponent) columnList: DataColumnListComponent; @@ -33,7 +33,7 @@ export abstract class DataTableSchema { @Input() presetColumn: string; - columns: any; + columns: DataColumn[]; protected columnsOrder: string[] | undefined; protected columnsOrderedByKey: string = 'id'; @@ -91,7 +91,7 @@ export abstract class DataTableSchema { return customSchemaColumns; } - public getSchemaFromHtml(columnList: DataColumnListComponent): any { + public getSchemaFromHtml(columnList: DataColumnListComponent): DataColumn[] { let schema = []; if (columnList && columnList.columns && columnList.columns.length > 0) { schema = columnList.columns.map((c) => c as DataColumn); diff --git a/lib/core/datatable/data/datatable-adapter.ts b/lib/core/datatable/data/datatable-adapter.ts index 49400b8add7..87220e695a0 100644 --- a/lib/core/datatable/data/datatable-adapter.ts +++ b/lib/core/datatable/data/datatable-adapter.ts @@ -29,6 +29,7 @@ export interface DataTableAdapter { getColumns(): Array; setColumns(columns: Array): void; getValue(row: DataRow, col: DataColumn, resolverFn?: (_row: DataRow, _col: DataColumn) => any): any; + getColumnType(row: DataRow, col: DataColumn): string; getSorting(): DataSorting; setSorting(sorting: DataSorting): void; sort(key?: string, direction?: string): void; diff --git a/lib/core/datatable/data/object-datacolumn.model.ts b/lib/core/datatable/data/object-datacolumn.model.ts index b4d6d9151dd..afcbc34d320 100644 --- a/lib/core/datatable/data/object-datacolumn.model.ts +++ b/lib/core/datatable/data/object-datacolumn.model.ts @@ -19,7 +19,7 @@ import { TemplateRef } from '@angular/core'; import { DataColumn, DataColumnType } from './data-column.model'; // Simple implementation of the DataColumn interface. -export class ObjectDataColumn implements DataColumn { +export class ObjectDataColumn implements DataColumn { id?: string; key: string; type: DataColumnType; @@ -35,6 +35,7 @@ export class ObjectDataColumn implements DataColumn { header?: TemplateRef; draggable: boolean; isHidden: boolean; + customData?: T; constructor(input: any) { this.id = input.id ?? ''; @@ -52,5 +53,6 @@ export class ObjectDataColumn implements DataColumn { this.header = input.header; this.draggable = input.draggable ?? false; this.isHidden = input.isHidden ?? false; + this.customData = input.customData; } } diff --git a/lib/core/datatable/data/object-datatable-adapter.ts b/lib/core/datatable/data/object-datatable-adapter.ts index e9b1af41fc6..665946d8aee 100644 --- a/lib/core/datatable/data/object-datatable-adapter.ts +++ b/lib/core/datatable/data/object-datatable-adapter.ts @@ -77,6 +77,10 @@ export class ObjectDataTableAdapter implements DataTableAdapter { this.rowsChanged = new Subject>(); } + getColumnType(_row: DataRow, col: DataColumn): string { + return col.type; + } + getRows(): Array { return this._rows; } diff --git a/lib/core/mock/data-column.mock.ts b/lib/core/mock/data-column.mock.ts new file mode 100644 index 00000000000..e730bd58f14 --- /dev/null +++ b/lib/core/mock/data-column.mock.ts @@ -0,0 +1,22 @@ +import { DataColumn } from '../datatable/data/data-column.model'; + +export const getDataColumnMock = (column: Partial> = {}): DataColumn => ({ + id: 'columnId', + key: 'key', + type: 'text', + format: 'format', + sortable: false, + title: 'title', + srTitle: 'srTitle', + cssClass: 'cssClass', + template: undefined, + copyContent: false, + editable: false, + focus: false, + sortingKey: 'sortingKey', + header: undefined, + draggable: false, + isHidden: false, + customData: undefined, + ...column +}); diff --git a/lib/core/mock/public-api.ts b/lib/core/mock/public-api.ts index 1f1fb1520d5..af8ffc2b789 100644 --- a/lib/core/mock/public-api.ts +++ b/lib/core/mock/public-api.ts @@ -41,3 +41,4 @@ export * from './identity-group.mock'; export * from './identity-user.mock'; export * from './identity-group.service.mock'; export * from './identity-user.service.mock'; +export * from './data-column.mock'; diff --git a/lib/process-services-cloud/src/lib/models/column-data-type.model.ts b/lib/process-services-cloud/src/lib/models/column-data-type.model.ts new file mode 100644 index 00000000000..5b7cbcc916e --- /dev/null +++ b/lib/process-services-cloud/src/lib/models/column-data-type.model.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line no-shadow +export enum ColumnDataType { + processVariableColumn = 'process-variable-column' +} diff --git a/lib/process-services-cloud/src/lib/models/process-definition-cloud.model.ts b/lib/process-services-cloud/src/lib/models/process-definition-cloud.model.ts index 000e4141d21..db8f7180f69 100755 --- a/lib/process-services-cloud/src/lib/models/process-definition-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/models/process-definition-cloud.model.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { ProcessVariableDefinition } from './variable-definition'; + export class ProcessDefinitionCloud { id: string; appName: string; @@ -25,6 +27,7 @@ export class ProcessDefinitionCloud { name: string; category: string; description: string; + variableDefinitions?: ProcessVariableDefinition[]; constructor(obj?: any) { this.id = obj && obj.id || null; @@ -36,5 +39,6 @@ export class ProcessDefinitionCloud { this.appVersion = obj && obj.appVersion || 0; this.category = obj && obj?.category || ''; this.description = obj && obj?.description || ''; + this.variableDefinitions = obj?.variableDefinitions ?? []; } } diff --git a/lib/process-services-cloud/src/lib/models/process-instance-variable.model.ts b/lib/process-services-cloud/src/lib/models/process-instance-variable.model.ts new file mode 100644 index 00000000000..0838f59c68f --- /dev/null +++ b/lib/process-services-cloud/src/lib/models/process-instance-variable.model.ts @@ -0,0 +1,33 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface ProcessInstanceVariable { + id: number; + variableDefinitionId: string; + value: string; + appName: string; + createTime: string; + lastUpdatedTime: string; + markedAsDeleted: boolean; + name: string; + processInstanceId: string; + serviceFullName: string; + serviceName: string; + serviceVersion: string; + taskVariable: boolean; + type: string; +} diff --git a/lib/process-services-cloud/src/lib/models/variable-definition.ts b/lib/process-services-cloud/src/lib/models/variable-definition.ts new file mode 100644 index 00000000000..8e54c1e6582 --- /dev/null +++ b/lib/process-services-cloud/src/lib/models/variable-definition.ts @@ -0,0 +1,25 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface ProcessVariableDefinition { + id: string; + name: string; + type: string; + required: boolean; + display: boolean; + displayName?: string; +} diff --git a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.html b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.html index 654cd2ecb01..5450062757f 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.html +++ b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.html @@ -1,6 +1,7 @@ { let fixture: ComponentFixture; let appConfig: AppConfigService; let processListCloudService: ProcessListCloudService; + let preferencesService: LocalPreferenceCloudService; + const fakeCustomSchemaName = 'fakeCustomSchema'; + const schemaWithVariable = 'schemaWithVariableId'; setupTestBed({ imports: [ @@ -82,12 +90,13 @@ describe('ProcessListCloudComponent', () => { beforeEach(() => { appConfig = TestBed.inject(AppConfigService); processListCloudService = TestBed.inject(ProcessListCloudService); + preferencesService = TestBed.inject(PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN); fixture = TestBed.createComponent(ProcessListCloudComponent); component = fixture.componentInstance; appConfig.config = Object.assign(appConfig.config, { 'adf-cloud-process-list': { presets: { - fakeCustomSchema: [ + [fakeCustomSchemaName]: [ { key: 'fakeName', type: 'text', @@ -100,6 +109,16 @@ describe('ProcessListCloudComponent', () => { title: 'ADF_CLOUD_TASK_LIST.PROPERTIES.TASK_FAKE', sortable: true } + ], + [schemaWithVariable]: [ + getDataColumnMock(), + getDataColumnMock({ + id: 'variableColumnId', + customData: { + assignedVariableDefinitionIds: ['variableDefinitionId'], + columnType: ColumnDataType.processVariableColumn + } + }) ] } } @@ -108,11 +127,12 @@ describe('ProcessListCloudComponent', () => { component.isColumnSchemaCreated$ = of(true).pipe(shareReplay(1)); }); - afterEach(() => fixture.destroy()); + afterEach(() => { + fixture.destroy(); + }); it('should use the default schemaColumn', () => { appConfig.config = Object.assign(appConfig.config, { 'adf-cloud-process-list': processListSchemaMock }); - component.ngAfterContentInit(); fixture.detectChanges(); expect(component.columns).toBeDefined(); @@ -164,6 +184,7 @@ describe('ProcessListCloudComponent', () => { it('should the payload contain the appVersion if it is defined', () => { spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); component.appVersion = 1; + component.ngAfterContentInit(); component.reload(); expect(component.requestNode.appVersion).toEqual('1'); @@ -172,6 +193,7 @@ describe('ProcessListCloudComponent', () => { it('should the payload contain all the app versions joined by a comma separator', () => { spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); component.appVersion = [1, 2, 3]; + component.ngAfterContentInit(); component.reload(); expect(component.requestNode.appVersion).toEqual('1,2,3'); @@ -180,20 +202,21 @@ describe('ProcessListCloudComponent', () => { it('should the payload NOT contain any app version when appVersion does not have a value', () => { spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); component.appVersion = undefined; + component.ngAfterContentInit(); component.reload(); expect(component.requestNode.appVersion).toEqual(''); }); it('should use the custom schemaColumn from app.config.json', () => { - component.presetColumn = 'fakeCustomSchema'; + component.presetColumn = fakeCustomSchemaName; component.ngAfterContentInit(); fixture.detectChanges(); expect(component.columns).toEqual(fakeCustomSchema); }); it('should fetch custom schemaColumn when the input presetColumn is defined', () => { - component.presetColumn = 'fakeCustomSchema'; + component.presetColumn = fakeCustomSchemaName; fixture.detectChanges(); expect(component.columns).toBeDefined(); expect(component.columns.length).toEqual(2); @@ -223,6 +246,7 @@ describe('ProcessListCloudComponent', () => { done(); }); component.appName = appName.currentValue; + component.ngAfterContentInit(); component.ngOnChanges({ appName }); fixture.detectChanges(); }); @@ -244,6 +268,7 @@ describe('ProcessListCloudComponent', () => { spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + component.ngAfterContentInit(); component.ngOnChanges({ appName }); fixture.detectChanges(); @@ -285,6 +310,41 @@ describe('ProcessListCloudComponent', () => { expect(displayedColumns.length).toBe(2, 'only column with isHidden set to false and action column should be shown'); }); + it('should NOT request process variable if columns for process variables are not displayed', () => { + spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); + spyOn(preferencesService, 'getPreferences').and.returnValue(of({ + list: { + entries: [] + } + })); + + component.ngAfterContentInit(); + component.reload(); + + expect(component.requestNode.variableDefinitions).not.toBeDefined(); + }); + + it('should request process variable if column for process variable is displayed', () => { + component.presetColumn = schemaWithVariable; + + spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); + spyOn(preferencesService, 'getPreferences').and.returnValue(of({ + list: { + entries: [{ + entry: { + key: ProcessListCloudPreferences.columnsVisibility, + value: '{"variableColumnId":true, "2":true}' + } + }] + } + })); + + component.ngAfterContentInit(); + component.reload(); + + expect(component.requestNode.variableDefinitions).toEqual(['variableDefinitionId']); + }); + it('should reload tasks when reload() is called', (done) => { component.appName = 'fake'; spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); diff --git a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.ts b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.ts index 425dcbc5df2..b45c3262eb2 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/components/process-list-cloud.component.ts @@ -21,14 +21,17 @@ import { DataTableSchema, PaginatedComponent, UserPreferencesService, PaginationModel, UserPreferenceValues, DataRowEvent, CustomLoadingContentTemplateDirective, DataCellEvent, DataRowActionEvent, DataTableComponent, DataColumn } from '@alfresco/adf-core'; import { ProcessListCloudService } from '../services/process-list-cloud.service'; -import { BehaviorSubject, combineLatest } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { processCloudPresetsDefaultModel } from '../models/process-cloud-preset.model'; import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model'; import { ProcessListCloudSortingModel } from '../models/process-list-sorting.model'; -import { map, take } from 'rxjs/operators'; +import { map, switchMap, take, tap } from 'rxjs/operators'; import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface'; import { PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN } from '../../../services/cloud-token.service'; import { ProcessListCloudPreferences } from '../models/process-cloud-preferences'; +import { ColumnDataType } from '../../../models/column-data-type.model'; +import { ProcessListDatatableAdapter } from '../datatable/process-list-datatable-adapter'; +import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data'; const PRESET_KEY = 'adf-cloud-process-list.presets'; @@ -38,7 +41,7 @@ const PRESET_KEY = 'adf-cloud-process-list.presets'; styleUrls: ['./process-list-cloud.component.scss'], encapsulation: ViewEncapsulation.None }) -export class ProcessListCloudComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent { +export class ProcessListCloudComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent { @ViewChild(DataTableComponent) dataTable: DataTableComponent; @@ -201,6 +204,7 @@ export class ProcessListCloudComponent extends DataTableSchema implements OnChan rows: any[] = []; formattedSorting: any[]; requestNode: ProcessQueryCloudRequestModel; + dataAdapter: ProcessListDatatableAdapter; private defaultSorting = { key: 'startDate', direction: 'desc' }; @@ -231,7 +235,7 @@ export class ProcessListCloudComponent extends DataTableSchema implements OnChan return { columnsOrder: columnsOrder ? JSON.parse(columnsOrder.entry.value) : undefined, - columnsVisibility: columnsVisibility ? JSON.parse(columnsVisibility.entry.value) : undefined + columnsVisibility: columnsVisibility ? JSON.parse(columnsVisibility.entry.value) : this.columnsVisibility }; })) ) @@ -262,24 +266,29 @@ export class ProcessListCloudComponent extends DataTableSchema implements OnChan } reload() { - this.requestNode = this.createRequestNode(); - if (this.requestNode.appName || this.requestNode.appName === '') { - this.load(this.requestNode); + if (this.appName || this.appName === '') { + this.load(); } else { this.rows = []; } } - private load(requestNode: ProcessQueryCloudRequestModel) { + private load() { this.isLoading = true; - combineLatest([ - this.processListCloudService.getProcessByRequest(requestNode), - this.isColumnSchemaCreated$ - ]).pipe( - take(1) - ).subscribe(([processes]) => { - this.rows = processes.list.entries; + this.isColumnSchemaCreated$.pipe( + take(1), + switchMap(() => of(this.createRequestNode())), + tap((requestNode) => this.requestNode = requestNode), + switchMap((requestNode) => this.processListCloudService.getProcessByRequest(requestNode)) + ).subscribe((processes) => { + this.rows = this.processListCloudService.createRowsViewModel( + processes.list.entries, + this.columns + ); + + this.dataAdapter = new ProcessListDatatableAdapter(this.rows, this.columns); + this.success.emit(processes); this.isLoading = false; this.pagination.next(processes.list.pagination); @@ -359,6 +368,7 @@ export class ProcessListCloudComponent extends DataTableSchema implements OnChan }, {}); this.createColumns(); + this.reload(); if (this.appName) { this.cloudPreferenceService.updatePreference( @@ -427,8 +437,10 @@ export class ProcessListCloudComponent extends DataTableSchema implements OnChan suspendedFrom: this.suspendedFrom, suspendedTo: this.suspendedTo, completedDate: this.completedDate, - sorting: this.sorting + sorting: this.sorting, + variableDefinitions: this.getRequestNodeVariableIds() }; + return new ProcessQueryCloudRequestModel(requestNode); } @@ -454,4 +466,16 @@ export class ProcessListCloudComponent extends DataTableSchema implements OnChan isValidSorting(sorting: ProcessListCloudSortingModel[]) { return sorting.length && sorting[0].orderBy && sorting[0].direction; } + + private getRequestNodeVariableIds(): string[] | undefined { + const displayedVariableColumns = this.columns + .filter(column => + column.customData?.columnType === ColumnDataType.processVariableColumn && + column.isHidden !== true + ) + .map(column => column.customData.assignedVariableDefinitionIds) + .reduce((allIds, ids) => [...ids, ...allIds], []); + + return displayedVariableColumns.length ? displayedVariableColumns : undefined; + } } diff --git a/lib/process-services-cloud/src/lib/process/process-list/datatable/process-list-datatable-adapter.spec.ts b/lib/process-services-cloud/src/lib/process/process-list/datatable/process-list-datatable-adapter.spec.ts new file mode 100644 index 00000000000..5ffbdfa92c2 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/process-list/datatable/process-list-datatable-adapter.spec.ts @@ -0,0 +1,53 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DataColumn, DataRow, getDataColumnMock } from '@alfresco/adf-core'; +import { ColumnDataType } from '../../../models/column-data-type.model'; +import { getProcessInstanceVariableMock } from '../mock/process-instance-variable.mock'; +import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data'; +import { ProcessInstanceCloudListViewModel } from '../models/perocess-instance-cloud-view.model'; +import { ProcessListDatatableAdapter } from './process-list-datatable-adapter'; + +describe('ProcessListDatatableAdapter', () => { + it('should get proepr type for column', () => { + const viewModel: ProcessInstanceCloudListViewModel = { + id: '1', + variablesMap: { + columnDisplayName1: getProcessInstanceVariableMock({ type: 'number' }) + } + }; + + const row: DataRow = { + getValue: () => {}, + hasValue: () => true, + isSelected: false, + obj: viewModel + }; + + const column: DataColumn = getDataColumnMock({ + title: 'columnDisplayName1', + customData: { + assignedVariableDefinitionIds: ['1'], + columnType: ColumnDataType.processVariableColumn + } + }); + + const adapter = new ProcessListDatatableAdapter([], []); + + expect(adapter.getColumnType(row, column)).toBe('number'); + }); +}); diff --git a/lib/process-services-cloud/src/lib/process/process-list/datatable/process-list-datatable-adapter.ts b/lib/process-services-cloud/src/lib/process/process-list/datatable/process-list-datatable-adapter.ts new file mode 100644 index 00000000000..e103a96eb08 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/process-list/datatable/process-list-datatable-adapter.ts @@ -0,0 +1,23 @@ +import { DataColumn, DataRow, ObjectDataTableAdapter } from '@alfresco/adf-core'; +import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data'; +import { ColumnDataType } from '../../../models/column-data-type.model'; +import { ProcessInstanceCloudListViewModel } from '../models/perocess-instance-cloud-view.model'; + +export class ProcessListDatatableAdapter extends ObjectDataTableAdapter { + constructor( + data: ProcessInstanceCloudListViewModel[], + schema: DataColumn[] + ) { + super(data, schema); + } + + getColumnType(row: DataRow, col: DataColumn): string { + if (col.customData?.columnType === ColumnDataType.processVariableColumn) { + const variableDisplayName = col.title; + const columnType = row.obj.variablesMap?.[variableDisplayName]?.type; + return columnType ?? 'text'; + } + + return super.getColumnType(row, col); + } +} diff --git a/lib/process-services-cloud/src/lib/process/process-list/mock/process-instance-variable.mock.ts b/lib/process-services-cloud/src/lib/process/process-list/mock/process-instance-variable.mock.ts new file mode 100644 index 00000000000..e4da82bc386 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/process-list/mock/process-instance-variable.mock.ts @@ -0,0 +1,19 @@ +import { ProcessInstanceVariable } from '../../../models/process-instance-variable.model'; + +export const getProcessInstanceVariableMock = (variable: Partial = {}): ProcessInstanceVariable => ({ + id: 1, + variableDefinitionId: 'variableDefinitionId', + value: 'value', + appName: 'appName', + createTime: 'createTime', + lastUpdatedTime: 'lastUpdatedTime', + markedAsDeleted: false, + name: 'name', + processInstanceId: 'processInstanceId', + serviceFullName: 'serviceFullName', + serviceName: 'serviceName', + serviceVersion: 'serviceVersion', + taskVariable: false, + type: 'text', + ...variable +}); diff --git a/lib/process-services-cloud/src/lib/process/process-list/mock/process-list-service.mock.ts b/lib/process-services-cloud/src/lib/process/process-list/mock/process-list-service.mock.ts index c175aa4cdfc..09225b95a0b 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/mock/process-list-service.mock.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/mock/process-list-service.mock.ts @@ -16,6 +16,7 @@ */ import { ObjectDataColumn } from '@alfresco/adf-core'; +import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data'; export const fakeProcessCloudList = { list: { @@ -34,7 +35,8 @@ export const fakeProcessCloudList = { status: 'RUNNING', lastModified: 1540381146276, lastModifiedTo: null, - lastModifiedFrom: null + lastModifiedFrom: null, + variables: [{ id: 'variableId', value: 'variableValue'}] } }, { @@ -84,13 +86,13 @@ export const fakeProcessCloudList = { export const fakeCustomSchema = [ - new ObjectDataColumn({ + new ObjectDataColumn({ key: 'fakeName', type: 'text', title: 'ADF_CLOUD_TASK_LIST.PROPERTIES.FAKE', sortable: true }), - new ObjectDataColumn({ + new ObjectDataColumn({ key: 'fakeTaskName', type: 'text', title: 'ADF_CLOUD_TASK_LIST.PROPERTIES.TASK_FAKE', diff --git a/lib/process-services-cloud/src/lib/process/process-list/models/data-column-custom-data.ts b/lib/process-services-cloud/src/lib/process/process-list/models/data-column-custom-data.ts new file mode 100644 index 00000000000..4c761e5d5ac --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/process-list/models/data-column-custom-data.ts @@ -0,0 +1,4 @@ +export interface ProcessListDataColumnCustomData { + assignedVariableDefinitionIds: string[]; + columnType: string; +} diff --git a/lib/process-services-cloud/src/lib/process/process-list/models/perocess-instance-cloud-view.model.ts b/lib/process-services-cloud/src/lib/process/process-list/models/perocess-instance-cloud-view.model.ts new file mode 100644 index 00000000000..37be8f39e7b --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/process-list/models/perocess-instance-cloud-view.model.ts @@ -0,0 +1,25 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ProcessInstanceVariable } from '../../../models/process-instance-variable.model'; +import { ProcessInstanceCloud } from '../../start-process/models/process-instance-cloud.model'; + +export interface ProcessInstanceCloudListViewModel extends ProcessInstanceCloud { + variablesMap?: { + [variableDisplayName: string]: ProcessInstanceVariable; + }; +} diff --git a/lib/process-services-cloud/src/lib/process/process-list/models/process-cloud-query-request.model.ts b/lib/process-services-cloud/src/lib/process/process-list/models/process-cloud-query-request.model.ts index 55c0572c045..7e937448ec7 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/models/process-cloud-query-request.model.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/models/process-cloud-query-request.model.ts @@ -42,6 +42,8 @@ export class ProcessQueryCloudRequestModel { maxItems: number; skipCount: number; sorting?: ProcessListCloudSortingModel[]; + variableDefinitions?: string[]; + constructor(obj?: any) { if (obj) { this.appName = obj.appName; @@ -68,6 +70,7 @@ export class ProcessQueryCloudRequestModel { this.maxItems = obj.maxItems; this.skipCount = obj.skipCount; this.sorting = obj.sorting; + this.variableDefinitions = obj.variableDefinitions; } } } diff --git a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts index b34613ba72e..d617823c1a5 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.spec.ts @@ -15,10 +15,16 @@ * limitations under the License. */ import { fakeAsync, TestBed } from '@angular/core/testing'; -import { setupTestBed, AlfrescoApiService } from '@alfresco/adf-core'; +import { setupTestBed, AlfrescoApiService, getDataColumnMock } from '@alfresco/adf-core'; import { ProcessListCloudService } from './process-list-cloud.service'; import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model'; import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; +import { ProcessInstanceVariable } from '../../../models/process-instance-variable.model'; +import { ProcessInstanceCloudListViewModel } from '../models/perocess-instance-cloud-view.model'; +import { ProcessInstanceCloud } from '../../public-api'; +import { getProcessInstanceVariableMock } from '../mock/process-instance-variable.mock'; +import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data'; +import { ColumnDataType } from '../../../models/column-data-type.model'; describe('ProcessListCloudService', () => { let service: ProcessListCloudService; @@ -98,4 +104,35 @@ describe('ProcessListCloudService', () => { } ); }); + + it('should map to view model', () => { + const processInstanceVariable: ProcessInstanceVariable = getProcessInstanceVariableMock({ + variableDefinitionId: '5c75b259-dc59-11ec-aa89-fed162b97957' + }); + + const columnTitle = 'columnTitle'; + const column = getDataColumnMock({ + title: columnTitle, + customData: { + assignedVariableDefinitionIds: ['5c75b259-dc59-11ec-aa89-fed162b97957'], + columnType: ColumnDataType.processVariableColumn + } + }); + + const processInstance: ProcessInstanceCloud = { + id: 'id', + variables: [processInstanceVariable] + }; + + const expectedViewModel: ProcessInstanceCloudListViewModel = { + ...processInstance, + variablesMap: { + [columnTitle]: processInstanceVariable + } + }; + + const viewModel = service.createRowsViewModel([processInstance], [column]); + + expect(viewModel).toEqual([expectedViewModel]); + }); }); diff --git a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts index c22873f250f..4a16383bb33 100644 --- a/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/process-list/services/process-list-cloud.service.ts @@ -15,12 +15,15 @@ * limitations under the License. */ import { Injectable } from '@angular/core'; -import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core'; +import { AlfrescoApiService, AppConfigService, DataColumn, DataColumnType, LogService } from '@alfresco/adf-core'; import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model'; import { Observable, throwError } from 'rxjs'; import { ProcessListCloudSortingModel } from '../models/process-list-sorting.model'; import { BaseCloudService } from '../../../services/base-cloud.service'; import { map } from 'rxjs/operators'; +import { ProcessInstanceCloudListViewModel } from '../models/perocess-instance-cloud-view.model'; +import { ProcessInstanceCloud } from '../../start-process/models/process-instance-cloud.model'; +import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data'; @Injectable({ providedIn: 'root' }) export class ProcessListCloudService extends BaseCloudService { @@ -62,6 +65,51 @@ export class ProcessListCloudService extends BaseCloudService { } } + createRowsViewModel( + processes: ProcessInstanceCloud[] = [], + columnsSchema: DataColumn[] + ): ProcessInstanceCloudListViewModel[] { + const columnsByVariableId = columnsSchema + .filter(column => !!column.customData) + .reduce<{ [variableId: string]: string }>((columnsByVariable, column) => { + const columnTitle = column.title; + const variableIds = column.customData.assignedVariableDefinitionIds; + + variableIds.forEach((variableId) => { + columnsByVariable[variableId] = columnTitle; + }); + return columnsByVariable; + + }, {}); + + const rowsViewModel = processes.map((process) => { + if (!process.variables?.length) { + return process; + } + + const variablesMap = (process.variables ?? []).reduce((variableAccumulator, variable) => { + const processVariableDefinitionId = variable.variableDefinitionId; + + const column = columnsByVariableId[processVariableDefinitionId]; + if (column) { + variableAccumulator[column] = { + ...variable, + type: this.mapProcessVariableTypes(variable.type) + }; + } + + return variableAccumulator; + }, {}); + + return { + ...process, + variablesMap + }; + }); + + return rowsViewModel; + } + protected isPropertyValueValid(requestNode: any, property: string): boolean { return requestNode[property] !== '' && requestNode[property] !== null && requestNode[property] !== undefined; } @@ -73,7 +121,7 @@ export class ProcessListCloudService extends BaseCloudService { if (requestNode.hasOwnProperty(property) && !this.isExcludedField(property) && this.isPropertyValueValid(requestNode, property)) { - queryParam[property] = requestNode[property]; + queryParam[property] = this.getQueryParamValueFromRequestNode(requestNode, property as keyof ProcessQueryCloudRequestModel); } } @@ -84,6 +132,17 @@ export class ProcessListCloudService extends BaseCloudService { return queryParam; } + private getQueryParamValueFromRequestNode( + requestNode: ProcessQueryCloudRequestModel, + property: keyof ProcessQueryCloudRequestModel + ) { + if (property === 'variableDefinitions' && requestNode[property]?.length > 0) { + return `${requestNode[property].map(variableId => variableId).join(',')}`; + } + + return requestNode[property]; + } + protected buildFilterForAllStatus(): string[] { return ['RUNNING', 'SUSPENDED', 'CANCELLED', 'COMPLETED']; } @@ -105,4 +164,18 @@ export class ProcessListCloudService extends BaseCloudService { } return encodeURI(finalSorting); } + + private mapProcessVariableTypes(variableType: string): DataColumnType { + switch (variableType) { + case 'boolean': + case 'integer': + case 'string': + return 'text'; + case 'date': + case 'datetime': + return 'date'; + default: + return 'text'; + } + } } diff --git a/lib/process-services-cloud/src/lib/process/start-process/models/process-instance-cloud.model.ts b/lib/process-services-cloud/src/lib/process/start-process/models/process-instance-cloud.model.ts index e2582eec7ba..4e451cf0a1d 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/models/process-instance-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/models/process-instance-cloud.model.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { ProcessInstanceVariable } from '../../../models/process-instance-variable.model'; export interface ProcessInstanceCloud { appName?: string; id?: string; @@ -28,4 +29,5 @@ export interface ProcessInstanceCloud { processDefinitionId?: string; processDefinitionKey?: string; processDefinitionName?: string; + variables?: ProcessInstanceVariable[]; } diff --git a/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts index 618f299307f..4fc89e5faf5 100755 --- a/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts @@ -42,11 +42,11 @@ export class StartProcessCloudService extends BaseCloudService { * @param appName Name of the target app * @returns Array of process definitions */ - getProcessDefinitions(appName: string): Observable { + getProcessDefinitions(appName: string, queryParams?: { include: 'variables' }): Observable { if (appName || appName === '') { const url = `${this.getBasePath(appName)}/rb/v1/process-definitions`; - return this.get(url).pipe( + return this.get(url, queryParams).pipe( map((res: any) => res.list.entries.map((processDefs) => new ProcessDefinitionCloud(processDefs.entry))) ); } else { diff --git a/lib/process-services-cloud/src/public-api.ts b/lib/process-services-cloud/src/public-api.ts index 3baf1ea7d45..08b2ebf1a9b 100644 --- a/lib/process-services-cloud/src/public-api.ts +++ b/lib/process-services-cloud/src/public-api.ts @@ -32,3 +32,5 @@ export * from './lib/models/application-version.model'; export * from './lib/models/engine-event-cloud.model'; export * from './lib/models/filter-cloud-model'; export * from './lib/models/task-list-sorting.model'; +export * from './lib/models/column-data-type.model'; +export * from './lib/models/process-instance-variable.model';