[AAE-7856] Show variables columns for tasks (#7659)

* Exclude flaky tests

* Revert "Exclude flaky tests"

This reverts commit 6ac24cc14a.

* [AAE-7856] Show variable columns in tasks

* revert loadTask to reload method name

* Temporary solution due to incompatible type used for dates
This commit is contained in:
Bartosz Sekuła
2022-06-06 21:50:32 +02:00
committed by GitHub
parent f3e4ff5aa3
commit c05259e6cf
28 changed files with 534 additions and 253 deletions

View File

@@ -0,0 +1,36 @@
/*!
* @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';
export const getProcessInstanceVariableMock = (variable: Partial<ProcessInstanceVariable> = {}): 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
});

View File

@@ -0,0 +1,45 @@
/*!
* @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 { TaskCloudModel } from '../models/task-cloud.model';
export const getTaskCloudModelMock = (cloudModel: Partial<TaskCloudModel> = {}): TaskCloudModel => ({
id: '1',
appName: 'app',
appVersion: 'version',
assignee: 'hr',
candidateGroups: [],
candidateUsers: [],
createdDate: '2022-06-01T10:33:52.275+0000',
formKey: '',
inFinalState: true,
lastModified: '2022-07-01T10:33:52.275+0000',
name: '',
priority: 1,
processDefinitionId: '',
processDefinitionName: '',
processDefinitionVersion: 1,
processInstanceId: '',
serviceFullName: '',
serviceName: '',
serviceVersion: '',
standalone: true,
status: '',
taskDefinitionKey: '',
processVariables: undefined,
...cloudModel
});

View File

@@ -49,6 +49,7 @@ export class TaskQueryCloudRequestModel {
completedFrom?: string;
completedTo?: string;
candidateGroupId?: string;
variableDefinitions?: string[];
constructor(obj?: any) {
if (obj) {
@@ -83,6 +84,7 @@ export class TaskQueryCloudRequestModel {
this.completedTo = obj.completedTo;
this.completedDate = obj.completedDate;
this.candidateGroupId = obj.candidateGroupId;
this.variableDefinitions = obj.variableDefinitions;
}
}
}

View File

@@ -15,6 +15,10 @@
* limitations under the License.
*/
export type WithVariablesMap<T> = T & {
variablesMap?: { [key: string]: ProcessInstanceVariable };
};
export interface ProcessInstanceVariable {
id: number;
variableDefinitionId: string;

View File

@@ -17,6 +17,7 @@
import { Pagination } from '@alfresco/js-api';
import { IdentityGroupModel, IdentityUserModel } from '@alfresco/adf-core';
import { ProcessInstanceVariable } from './process-instance-variable.model';
export class TaskCloudNodePaging {
list: TaskCloudPagingList;
@@ -54,4 +55,5 @@ export interface TaskCloudModel {
standalone: boolean;
status: string;
taskDefinitionKey: string;
processVariables?: ProcessInstanceVariable[];
}

View File

@@ -31,7 +31,8 @@ import { PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN } from '../../../services/cloud
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';
import { ProcessListDataColumnCustomData } from '../../../models/data-column-custom-data';
import { VariableMapperService } from '../../../services/variable-mapper.sevice';
const PRESET_KEY = 'adf-cloud-process-list.presets';
@@ -211,7 +212,9 @@ export class ProcessListCloudComponent extends DataTableSchema<ProcessListDataCo
constructor(private processListCloudService: ProcessListCloudService,
appConfigService: AppConfigService,
private userPreferences: UserPreferencesService,
@Inject(PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN) private cloudPreferenceService: PreferenceCloudServiceInterface) {
@Inject(PROCESS_LISTS_PREFERENCES_SERVICE_TOKEN) private cloudPreferenceService: PreferenceCloudServiceInterface,
private variableMapperService: VariableMapperService
) {
super(appConfigService, PRESET_KEY, processCloudPresetsDefaultModel);
this.size = userPreferences.paginationSize;
this.userPreferences.select(UserPreferenceValues.PaginationSize).subscribe((pageSize) => {
@@ -282,7 +285,7 @@ export class ProcessListCloudComponent extends DataTableSchema<ProcessListDataCo
tap((requestNode) => this.requestNode = requestNode),
switchMap((requestNode) => this.processListCloudService.getProcessByRequest(requestNode))
).subscribe((processes) => {
this.rows = this.processListCloudService.createRowsViewModel(
this.rows = this.variableMapperService.mapVariablesByColumnTitle(
processes.list.entries,
this.columns
);

View File

@@ -17,8 +17,8 @@
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 { 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';

View File

@@ -1,5 +1,5 @@
import { DataColumn, DataRow, ObjectDataTableAdapter } from '@alfresco/adf-core';
import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data';
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';

View File

@@ -1,19 +0,0 @@
import { ProcessInstanceVariable } from '../../../models/process-instance-variable.model';
export const getProcessInstanceVariableMock = (variable: Partial<ProcessInstanceVariable> = {}): 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
});

View File

@@ -16,7 +16,7 @@
*/
import { ObjectDataColumn } from '@alfresco/adf-core';
import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data';
import { ProcessListDataColumnCustomData } from '../../../models/data-column-custom-data';
export const fakeProcessCloudList = {
list: {

View File

@@ -15,11 +15,7 @@
* limitations under the License.
*/
import { ProcessInstanceVariable } from '../../../models/process-instance-variable.model';
import { WithVariablesMap } 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;
};
}
export type ProcessInstanceCloudListViewModel = WithVariablesMap<ProcessInstanceCloud>;

View File

@@ -15,16 +15,10 @@
* limitations under the License.
*/
import { fakeAsync, TestBed } from '@angular/core/testing';
import { setupTestBed, AlfrescoApiService, getDataColumnMock } from '@alfresco/adf-core';
import { setupTestBed, AlfrescoApiService } 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;
@@ -104,35 +98,4 @@ describe('ProcessListCloudService', () => {
}
);
});
it('should map to view model', () => {
const processInstanceVariable: ProcessInstanceVariable = getProcessInstanceVariableMock({
variableDefinitionId: '5c75b259-dc59-11ec-aa89-fed162b97957'
});
const columnTitle = 'columnTitle';
const column = getDataColumnMock<ProcessListDataColumnCustomData>({
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]);
});
});

View File

@@ -15,15 +15,12 @@
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { AlfrescoApiService, AppConfigService, DataColumn, DataColumnType, LogService } from '@alfresco/adf-core';
import { AlfrescoApiService, AppConfigService, 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 {
@@ -65,51 +62,6 @@ export class ProcessListCloudService extends BaseCloudService {
}
}
createRowsViewModel(
processes: ProcessInstanceCloud[] = [],
columnsSchema: DataColumn<ProcessListDataColumnCustomData>[]
): 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;
}
@@ -164,18 +116,4 @@ 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';
}
}
}

View File

@@ -23,3 +23,4 @@ export * from './preference-cloud.interface';
export * from './form-fields.interfaces';
export * from './base-cloud.service';
export * from './task-list-cloud.service.interface';
export * from './variable-mapper.sevice';

View File

@@ -0,0 +1,61 @@
/*!
* @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 { getProcessInstanceVariableMock } from '../mock/process-instance-variable.mock';
import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data';
import { ProcessInstanceVariable } from '../models/process-instance-variable.model';
import { VariableMapperService } from './variable-mapper.sevice';
import { getDataColumnMock } from '@alfresco/adf-core';
describe('VariableMapperService', () => {
let service: VariableMapperService;
beforeEach(() => {
service = new VariableMapperService();
});
it('should map variables by column title', () => {
const variable: ProcessInstanceVariable = getProcessInstanceVariableMock({
variableDefinitionId: 'variableDefinitionIdOne'
});
const objectWithVariables = {
variables: [variable]
};
const column = getDataColumnMock<ProcessListDataColumnCustomData>({
title: 'column name',
key: '',
customData: {
assignedVariableDefinitionIds: [variable.variableDefinitionId],
columnType: 'text'
}
});
const viewModel = service.mapVariablesByColumnTitle([objectWithVariables], [column]);
const expectedObjectWithVariableMap = {
...objectWithVariables,
variablesMap: {
[column.title]: variable
}
};
expect(viewModel).toEqual([expectedObjectWithVariableMap]);
});
});

View File

@@ -0,0 +1,91 @@
/*!
* @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 { Injectable } from '@angular/core';
import { DataColumn, DataColumnType } from '@alfresco/adf-core';
import { ProcessListDataColumnCustomData } from '../models/data-column-custom-data';
import { ProcessInstanceVariable, WithVariablesMap } from '../models/process-instance-variable.model';
@Injectable({ providedIn: 'root' })
export class VariableMapperService {
mapVariablesByColumnTitle <T extends { variables?: ProcessInstanceVariable[] }>(
instancesList: T[] = [],
columnsSchema: DataColumn<ProcessListDataColumnCustomData>[] = []
): Array<WithVariablesMap<T>> {
const columnsByVariableId = this.mapColumnKeysByVariableId(columnsSchema);
const rowsViewModel = instancesList.map<WithVariablesMap<T>>((instance) => {
if (!instance.variables?.length) {
return instance;
}
const variablesMap = (instance.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 {
...instance,
variablesMap
};
});
return rowsViewModel;
}
private mapColumnKeysByVariableId(
columnsSchema: DataColumn<ProcessListDataColumnCustomData>[]
): { [variableId: string]: string } {
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;
}, {});
return columnsByVariableId;
}
private mapProcessVariableTypes(variableType: string): DataColumnType {
switch (variableType) {
case 'boolean':
case 'integer':
case 'string':
return 'text';
case 'date':
case 'datetime':
return 'date';
default:
return 'text';
}
}
}

View File

@@ -3,6 +3,7 @@
<adf-datatable
[rows]="rows"
[columns]="columns"
[data]="dataAdapter"
[loading]="isLoading"
[sorting]="formattedSorting"
[multiselect]="multiselect"

View File

@@ -20,7 +20,7 @@ import {
AppConfigService, UserPreferencesService,
DataTableSchema, UserPreferenceValues,
PaginatedComponent, PaginationModel,
DataRowEvent, CustomEmptyContentTemplateDirective, DataCellEvent, DataRowActionEvent, DataRow, DataColumn
DataRowEvent, CustomEmptyContentTemplateDirective, DataCellEvent, DataRowActionEvent, DataRow, DataColumn, ObjectDataTableAdapter
} from '@alfresco/adf-core';
import { taskPresetsCloudDefaultModel } from '../models/task-preset-cloud.model';
import { TaskQueryCloudRequestModel } from '../../../models/filter-cloud-model';
@@ -33,7 +33,7 @@ import { TasksListCloudPreferences } from '../models/tasks-cloud-preferences';
@Directive()
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export abstract class BaseTaskListCloudComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent, OnDestroy, OnInit {
export abstract class BaseTaskListCloudComponent<T = unknown> extends DataTableSchema<T> implements OnChanges, AfterContentInit, PaginatedComponent, OnDestroy, OnInit {
@ContentChild(CustomEmptyContentTemplateDirective)
emptyCustomContent: CustomEmptyContentTemplateDirective;
@@ -118,6 +118,8 @@ export abstract class BaseTaskListCloudComponent extends DataTableSchema impleme
isLoading = true;
selectedInstances: any[];
formattedSorting: any[];
dataAdapter: ObjectDataTableAdapter | undefined;
private defaultSorting = { key: 'startDate', direction: 'desc' };
boundReplacePriorityValues: (row: DataRow, col: DataColumn) => any;
@@ -186,15 +188,6 @@ export abstract class BaseTaskListCloudComponent extends DataTableSchema impleme
);
}
reload() {
this.requestNode = this.createRequestNode();
if (this.requestNode.appName || this.requestNode.appName === '') {
this.load(this.requestNode);
} else {
this.rows = [];
}
}
isListEmpty(): boolean {
return !this.rows || this.rows.length === 0;
}
@@ -295,6 +288,8 @@ export abstract class BaseTaskListCloudComponent extends DataTableSchema impleme
this.columnsVisibility
);
}
this.reload();
}
setSorting(sortDetail) {
@@ -325,6 +320,5 @@ export abstract class BaseTaskListCloudComponent extends DataTableSchema impleme
}, row.obj);
}
abstract load(requestNode);
abstract createRequestNode();
abstract reload();
}

View File

@@ -43,28 +43,35 @@ export class ServiceTaskListCloudComponent extends BaseTaskListCloudComponent {
appConfigService: AppConfigService,
taskCloudService: TaskCloudService,
userPreferences: UserPreferencesService,
@Inject(TASK_LIST_PREFERENCES_SERVICE_TOKEN) cloudPreferenceService: PreferenceCloudServiceInterface) {
@Inject(TASK_LIST_PREFERENCES_SERVICE_TOKEN) cloudPreferenceService: PreferenceCloudServiceInterface
) {
super(appConfigService, taskCloudService, userPreferences, PRESET_KEY, cloudPreferenceService);
}
load(requestNode: ServiceTaskQueryCloudRequestModel) {
this.isLoading = true;
reload() {
this.requestNode = this.createRequestNode();
combineLatest([
this.serviceTaskListCloudService.getServiceTaskByRequest(requestNode),
this.isColumnSchemaCreated$
]).pipe(
take(1)
).subscribe(
([tasks]) => {
this.rows = tasks.list.entries;
this.success.emit(tasks);
this.isLoading = false;
this.pagination.next(tasks.list.pagination);
}, (error) => {
this.error.emit(error);
this.isLoading = false;
});
if (this.requestNode.appName || this.requestNode.appName === '') {
this.isLoading = true;
combineLatest([
this.serviceTaskListCloudService.getServiceTaskByRequest(this.requestNode),
this.isColumnSchemaCreated$
]).pipe(
take(1)
).subscribe(
([tasks]) => {
this.rows = tasks.list.entries;
this.success.emit(tasks);
this.isLoading = false;
this.pagination.next(tasks.list.pagination);
}, (error) => {
this.error.emit(error);
this.isLoading = false;
});
} else {
this.rows = [];
}
}
createRequestNode(): ServiceTaskQueryCloudRequestModel {

View File

@@ -21,7 +21,7 @@ import { By } from '@angular/platform-browser';
import { AppConfigService, setupTestBed, DataRowEvent, ObjectDataRow, EcmUserModel, DataColumn, ColumnsSelectorComponent } from '@alfresco/adf-core';
import { TaskListCloudService } from '../services/task-list-cloud.service';
import { TaskListCloudComponent } from './task-list-cloud.component';
import { fakeGlobalTask, fakeCustomSchema } from '../mock/fake-task-response.mock';
import { fakeGlobalTasks, fakeCustomSchema, fakeGlobalTask } from '../mock/fake-task-response.mock';
import { of } from 'rxjs';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { TranslateModule } from '@ngx-translate/core';
@@ -165,7 +165,7 @@ describe('TaskListCloudComponent', () => {
});
it('should load spinner and show the content', () => {
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);
fixture.detectChanges();
@@ -195,7 +195,8 @@ describe('TaskListCloudComponent', () => {
it('should hide columns on applying new columns visibility through columns selector', () => {
component.showMainDatatableActions = true;
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
component.ngAfterContentInit();
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);
component.ngOnChanges({ appName });
@@ -238,33 +239,22 @@ describe('TaskListCloudComponent', () => {
});
it('should return the results if an application name is given', (done) => {
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
component.ngAfterContentInit();
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);
component.success.subscribe((res) => {
expect(res).toBeDefined();
expect(component.rows).toBeDefined();
expect(component.isListEmpty()).not.toBeTruthy();
expect(component.rows.length).toEqual(1);
expect(component.rows[0]['appName']).toBe('test-ciprian2');
expect(component.rows[0]['appVersion']).toBe('');
expect(component.rows[0]['id']).toBe('11fe013d-c263-11e8-b75b-0a5864600540');
expect(component.rows[0]['assignee']).toBeNull();
expect(component.rows[0]['name']).toEqual('standalone-subtask');
expect(component.rows[0]['description']).toBeNull();
expect(component.rows[0]['createdDate']).toBe(1538059139420);
expect(component.rows[0]['dueDate']).toBeNull();
expect(component.rows[0]['claimedDate']).toBeNull();
expect(component.rows[0]['priority']).toBe(0);
expect(component.rows[0]['category']).toBeNull();
expect(component.rows[0]['processDefinitionId']).toBeNull();
expect(component.rows[0]['processInstanceId']).toBeNull();
expect(component.rows[0]['status']).toBe('CREATED');
expect(component.rows[0]['owner']).toBe('devopsuser');
expect(component.rows[0]['parentTaskId']).toBe('71fda20b-c25b-11e8-b75b-0a5864600540');
expect(component.rows[0]['lastModified']).toBe(1538059139420);
expect(component.rows[0]['lastModifiedTo']).toBeNull();
expect(component.rows[0]['lastModifiedFrom']).toBeNull();
expect(component.rows[0]['standalone']).toBeTruthy();
const expectedTask = {
...fakeGlobalTask,
variables: fakeGlobalTask.processVariables
};
expect(component.rows[0]).toEqual(expectedTask);
done();
});
component.appName = appName.currentValue;
@@ -274,7 +264,7 @@ describe('TaskListCloudComponent', () => {
it('should reload tasks when reload() is called', (done) => {
component.appName = 'fake';
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
component.success.subscribe((res) => {
expect(res).toBeDefined();
expect(component.rows).toBeDefined();
@@ -299,7 +289,7 @@ describe('TaskListCloudComponent', () => {
describe('component changes', () => {
beforeEach(() => {
component.rows = fakeGlobalTask.list.entries;
component.rows = fakeGlobalTasks.list.entries;
fixture.detectChanges();
});
@@ -311,7 +301,7 @@ describe('TaskListCloudComponent', () => {
});
it('should reload the task list when input parameters changed', () => {
const getTaskByRequestSpy = spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
const getTaskByRequestSpy = spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
component.appName = 'mock-app-name';
component.priority = 1;
component.status = 'mock-status';
@@ -333,7 +323,7 @@ describe('TaskListCloudComponent', () => {
});
it('should set formattedSorting if sorting input changes', () => {
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
spyOn(component, 'formatSorting').and.callThrough();
component.appName = 'mock-app-name';
@@ -353,7 +343,7 @@ describe('TaskListCloudComponent', () => {
});
it('should reload task list when sorting on a column changes', () => {
const getTaskByRequestSpy = spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
const getTaskByRequestSpy = spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
component.onSortingChanged(new CustomEvent('sorting-changed', {
detail: {
key: 'fakeName',
@@ -374,7 +364,7 @@ describe('TaskListCloudComponent', () => {
});
it('should reset pagination when resetPaginationValues is called', async (done) => {
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);
component.ngOnChanges({ appName });
@@ -402,7 +392,7 @@ describe('TaskListCloudComponent', () => {
});
it('should set pagination and reload when updatePagination is called', (done) => {
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
spyOn(component, 'reload').and.stub();
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);
component.ngOnChanges({ appName });
@@ -445,7 +435,7 @@ describe('TaskListCloudComponent', () => {
});
beforeEach(() => {
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
fixtureCustom = TestBed.createComponent(CustomTaskListComponent);
copyFixture = TestBed.createComponent(CustomCopyContentTaskListComponent);
fixtureCustom.detectChanges();
@@ -575,7 +565,7 @@ describe('TaskListCloudComponent', () => {
fixture = TestBed.createComponent(TaskListCloudComponent);
component = fixture.componentInstance;
element = fixture.debugElement.nativeElement;
taskSpy = spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTask));
taskSpy = spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(fakeGlobalTasks));
component.isColumnSchemaCreated$ = of(true);
});
@@ -587,7 +577,7 @@ describe('TaskListCloudComponent', () => {
// TODO: highly unstable test
// eslint-disable-next-line
xit('should show tooltip if config copyContent flag is true', fakeAsync(() => {
taskSpy.and.returnValue(of(fakeGlobalTask));
taskSpy.and.returnValue(of(fakeGlobalTasks));
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);
component.success.subscribe(() => {
@@ -609,7 +599,7 @@ describe('TaskListCloudComponent', () => {
// TODO: highly unstable test
// eslint-disable-next-line
xit('should replace priority values', (done) => {
taskSpy.and.returnValue(of(fakeGlobalTask));
taskSpy.and.returnValue(of(fakeGlobalTasks));
component.presetColumn = 'fakeCustomSchema';
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);
component.ngOnChanges({ appName });
@@ -647,7 +637,7 @@ describe('TaskListCloudComponent', () => {
});
it('replacePriorityValues should return replaced value when rows are defined', () => {
taskSpy.and.returnValue(of(fakeGlobalTask));
taskSpy.and.returnValue(of(fakeGlobalTasks));
fixture.detectChanges();
const appName = new SimpleChange(null, 'FAKE-APP-NAME', true);

View File

@@ -23,8 +23,14 @@ import { TaskCloudService } from '../../services/task-cloud.service';
import { TASK_LIST_CLOUD_TOKEN, TASK_LIST_PREFERENCES_SERVICE_TOKEN } from '../../../services/cloud-token.service';
import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface';
import { TaskListCloudServiceInterface } from '../../../services/task-list-cloud.service.interface';
import { combineLatest } from 'rxjs';
import { take } from 'rxjs/operators';
import { of } from 'rxjs';
import { switchMap, take, tap } from 'rxjs/operators';
import { VariableMapperService } from '../../../services/variable-mapper.sevice';
import { ProcessListDataColumnCustomData } from '../../../models/data-column-custom-data';
import { TaskCloudModel } from '../../../models/task-cloud.model';
import { PaginatedEntries } from '@alfresco/js-api';
import { TaskInstanceCloudListViewModel } from '../models/task-cloud-view.model';
import { TasksListDatatableAdapter } from '../datatable/task-list-datatable-adapter';
const PRESET_KEY = 'adf-cloud-task-list.presets';
@@ -34,7 +40,7 @@ const PRESET_KEY = 'adf-cloud-task-list.presets';
styleUrls: ['./base-task-list-cloud.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TaskListCloudComponent extends BaseTaskListCloudComponent {
export class TaskListCloudComponent extends BaseTaskListCloudComponent<ProcessListDataColumnCustomData> {
/**
* The assignee of the process. Possible values are: "assignee" (the current user is the assignee),
* "candidate" (the current user is a task candidate", "group_x" (the task is assigned to a group
@@ -135,32 +141,47 @@ export class TaskListCloudComponent extends BaseTaskListCloudComponent {
@Input()
candidateGroupId: string = '';
rows: TaskInstanceCloudListViewModel[] = [];
dataAdapter: TasksListDatatableAdapter | undefined;
constructor(@Inject(TASK_LIST_CLOUD_TOKEN) public taskListCloudService: TaskListCloudServiceInterface,
appConfigService: AppConfigService,
taskCloudService: TaskCloudService,
userPreferences: UserPreferencesService,
@Inject(TASK_LIST_PREFERENCES_SERVICE_TOKEN) cloudPreferenceService: PreferenceCloudServiceInterface) {
@Inject(TASK_LIST_PREFERENCES_SERVICE_TOKEN) cloudPreferenceService: PreferenceCloudServiceInterface,
private viewModelCreator: VariableMapperService
) {
super(appConfigService, taskCloudService, userPreferences, PRESET_KEY, cloudPreferenceService);
}
load(requestNode: TaskQueryCloudRequestModel) {
reload() {
this.isLoading = true;
combineLatest([
this.taskListCloudService.getTaskByRequest(requestNode),
this.isColumnSchemaCreated$
]).pipe(
take(1)
).subscribe(
([tasks]) => {
this.rows = tasks.list.entries;
this.success.emit(tasks);
this.isLoading = false;
this.pagination.next(tasks.list.pagination);
}, (error) => {
this.error.emit(error);
this.isLoading = false;
});
this.isColumnSchemaCreated$.pipe(
take(1),
switchMap(() => of(this.createRequestNode())),
tap((requestNode) => this.requestNode = requestNode),
switchMap((requestNode) => this.taskListCloudService.getTaskByRequest(requestNode))
).subscribe((tasks: { list: PaginatedEntries<TaskCloudModel> }) => {
const tasksWithVariables = tasks.list.entries.map((task) => ({
...task,
variables: task.processVariables
}));
this.rows = this.viewModelCreator.mapVariablesByColumnTitle(
tasksWithVariables,
this.columns
);
this.dataAdapter = new TasksListDatatableAdapter(this.rows, this.columns);
this.success.emit(tasks);
this.isLoading = false;
this.pagination.next(tasks.list.pagination);
}, (error) => {
this.error.emit(error);
this.isLoading = false;
});
}
createRequestNode(): TaskQueryCloudRequestModel {
@@ -192,8 +213,22 @@ export class TaskListCloudComponent extends BaseTaskListCloudComponent {
completedFrom: this.completedFrom,
completedTo: this.completedTo,
completedDate: this.completedDate,
candidateGroupId: this.candidateGroupId
candidateGroupId: this.candidateGroupId,
variableDefinitions: this.getRequestNodeVariableIds()
};
return new TaskQueryCloudRequestModel(requestNode);
}
private getRequestNodeVariableIds(): string[] | undefined {
const displayedVariableColumns: string[] = (this.columns ?? [])
.filter(column =>
column.customData?.columnType === 'process-variable-column' &&
column.isHidden !== true
)
.map(column => column.customData.assignedVariableDefinitionIds)
.reduce((allIds, ids) => [...ids, ...allIds], []);
return displayedVariableColumns.length ? displayedVariableColumns : undefined;
}
}

View File

@@ -0,0 +1,63 @@
/*!
* @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 { ProcessListDataColumnCustomData } from '../../../models/data-column-custom-data';
import { TasksListDatatableAdapter } from './task-list-datatable-adapter';
import { TaskInstanceCloudListViewModel } from '../models/task-cloud-view.model';
import { getTaskCloudModelMock } from '../../../mock/task-cloud-model.mock';
import { getProcessInstanceVariableMock } from '../../../mock/process-instance-variable.mock';
describe('TasksListDatatableAdapter', () => {
it('should get proepr type for column', () => {
const processVariable = getProcessInstanceVariableMock({
variableDefinitionId: 'variableDefinitionId',
type: 'number'
});
const cloudModel = getTaskCloudModelMock({
processVariables: [processVariable]
});
const viewModel: TaskInstanceCloudListViewModel = {
...cloudModel,
variablesMap: {
columnDisplayName1: processVariable
}
};
const row: DataRow = {
getValue: () => {},
hasValue: () => true,
isSelected: false,
obj: viewModel
};
const column: DataColumn<ProcessListDataColumnCustomData> = getDataColumnMock({
title: 'columnDisplayName1',
customData: {
assignedVariableDefinitionIds: ['variableDefinitionId'],
columnType: ColumnDataType.processVariableColumn
}
});
const adapter = new TasksListDatatableAdapter([], []);
expect(adapter.getColumnType(row, column)).toBe('number');
});
});

View File

@@ -0,0 +1,40 @@
/*!
* @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, ObjectDataTableAdapter } from '@alfresco/adf-core';
import { ProcessListDataColumnCustomData } from '../../../models/data-column-custom-data';
import { ColumnDataType } from '../../../models/column-data-type.model';
import { TaskInstanceCloudListViewModel } from '../models/task-cloud-view.model';
export class TasksListDatatableAdapter extends ObjectDataTableAdapter {
constructor(
data: TaskInstanceCloudListViewModel[],
schema: DataColumn<ProcessListDataColumnCustomData>[]
) {
super(data, schema);
}
getColumnType(row: DataRow, col: DataColumn<ProcessListDataColumnCustomData>): 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);
}
}

View File

@@ -16,33 +16,27 @@
*/
import { ObjectDataColumn } from '@alfresco/adf-core';
import { ProcessListDataColumnCustomData } from 'process-services-cloud/src/lib/models/data-column-custom-data';
import { getTaskCloudModelMock } from '../../../mock/task-cloud-model.mock';
export const fakeGlobalTask = {
export const fakeGlobalTask = getTaskCloudModelMock({
appName: 'test-ciprian2',
appVersion: '',
id: '11fe013d-c263-11e8-b75b-0a5864600540',
assignee: null,
name: 'standalone-subtask',
createdDate: '1538059139420',
priority: 0,
processDefinitionId: null,
processInstanceId: null,
status: 'CREATED',
lastModified: '1538059139420',
standalone: true
});
export const fakeGlobalTasks = {
list: {
entries: [
{
appName: 'test-ciprian2',
appVersion: '',
id: '11fe013d-c263-11e8-b75b-0a5864600540',
assignee: null,
name: 'standalone-subtask',
description: null,
createdDate: 1538059139420,
dueDate: null,
claimedDate: null,
priority: 0,
category: null,
processDefinitionId: null,
processInstanceId: null,
status: 'CREATED',
owner: 'devopsuser',
parentTaskId: '71fda20b-c25b-11e8-b75b-0a5864600540',
lastModified: 1538059139420,
lastModifiedTo: null,
lastModifiedFrom: null,
standalone: true
}
],
entries: [fakeGlobalTask],
pagination: {
skipCount: 0,
maxItems: 100,
@@ -87,13 +81,13 @@ export const fakeServiceTask = {
export const fakeCustomSchema =
[
new ObjectDataColumn({
new ObjectDataColumn<ProcessListDataColumnCustomData>({
key: 'fakeName',
type: 'text',
title: 'ADF_CLOUD_TASK_LIST.PROPERTIES.FAKE',
sortable: true
}),
new ObjectDataColumn({
new ObjectDataColumn<ProcessListDataColumnCustomData>({
key: 'fakeTaskName',
type: 'text',
title: 'ADF_CLOUD_TASK_LIST.PROPERTIES.TASK_FAKE',

View File

@@ -0,0 +1,27 @@
/*!
* @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 { TaskCloudModel } from '../../../models/task-cloud.model';
import { WithVariablesMap } from '../../../models/process-instance-variable.model';
// Temporary solution due to incompatibility types for dates (TaskCloudModel is not exposed right now)
interface TaskViewCloudModel extends TaskCloudModel {
createdDate: any;
lastModified: any;
}
export type TaskInstanceCloudListViewModel = WithVariablesMap<TaskViewCloudModel>;

View File

@@ -66,11 +66,17 @@ export class TaskListCloudService extends BaseCloudService implements TaskListCl
protected buildQueryParams(requestNode: TaskQueryCloudRequestModel): any {
const queryParam: any = {};
for (const property in requestNode) {
if (requestNode.hasOwnProperty(property) &&
!this.isExcludedField(property) &&
this.isPropertyValueValid(requestNode, property)) {
queryParam[property] = requestNode[property];
for (const propertyKey in requestNode) {
if (
requestNode.hasOwnProperty(propertyKey) &&
!this.isExcludedField(propertyKey) &&
this.isPropertyValueValid(requestNode, propertyKey)
) {
if (propertyKey === 'variableDefinitions' && requestNode[propertyKey]?.length > 0) {
queryParam['variableDefinitions'] = requestNode[propertyKey].join(',');
} else {
queryParam[propertyKey] = requestNode[propertyKey];
}
}
}
return queryParam;

View File

@@ -30,6 +30,7 @@ export * from './lib/models/process-definition-cloud.model';
export * from './lib/models/date-cloud-filter.model';
export * from './lib/models/application-version.model';
export * from './lib/models/engine-event-cloud.model';
export * from './lib/models/task-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';