[ADF-1749] Task/Process List - Should be able to customize the rendered columns from JSON (#2613)

* [ADF-1749] Task List - Should be able to customize the rendered columns from JSON

* Refactored task/process list components to support customization key's
* Added Key properties to the app.config.json.
* Created preset model for task/process list .

* [ADF-1749] Task/Process List - Should be able to customize the rendered columns from JSON

* Fixed failing testcases.

* [ADF-1749] Task/Process List - Should be able to customize the rendered columns from JSON.

* Added test cases.
* Updated task/process list readme.md file

* * Updated task/process list readme.md file.
This commit is contained in:
siva kumar 2017-11-08 05:07:07 +05:30 committed by Eugenio Romano
parent 348f4971ce
commit 3204bdebcb
11 changed files with 454 additions and 35 deletions

View File

@ -54,9 +54,9 @@
"size": 25
},
"files": {
"excluded": [".DS_Store", "desktop.ini", ".git"]
"excluded": [".DS_Store", "desktop.ini", ".git"]
},
"logLevel" : "trace",
"logLevel": "trace",
"activiti": {
"rest": {
"fields": [
@ -65,16 +65,25 @@
"taskId": "7501",
"fieldId": "label10",
"values": [
{ "id": "f1", "name": "Field 1" },
{ "id": "f2", "name": "Field 2" },
{ "id": "f3", "name": "Field 3" }
{
"id": "f1",
"name": "Field 1"
},
{
"id": "f2",
"name": "Field 2"
},
{
"id": "f3",
"name": "Field 3"
}
]
}
]
}
},
"document-list": {
"supportedPageSizes": [5, 10, 15, 20],
"supportedPageSizes": [ 5, 10, 15, 20 ],
"presets": {
"-trashcan-": [
{
@ -319,5 +328,79 @@
}
]
}
},
"adf-task-list": {
"supportedPageSizes": [ 5, 10, 15, 20 ],
"presets": {
"default": [
{
"key": "name",
"type": "text",
"title": "ADF_TASK_LIST.PROPERTIES.NAME",
"sortable": true
},
{
"key": "noassignee",
"type": "text",
"title": "ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT",
"cssClass": "hidden",
"sortable": true
},
{
"key": "nodueDate",
"type": "text",
"title": "ADF_TASK_LIST.PROPERTIES.DUE_DATE_DEFAULT",
"cssClass": "hidden",
"sortable": true
},
{
"key": "nocategory",
"type": "text",
"title": "ADF_TASK_LIST.PROPERTIES.CATEGORY_DEFAULT",
"cssClass": "hidden",
"sortable": true
},
{
"key": "noparentname",
"type": "text",
"title": "ADF_TASK_LIST.PROPERTIES.PARENT_NAME_DEFAULT",
"cssClass": "hidden",
"sortable": true
},
{
"key": "nodescription",
"type": "date",
"title": "ADF_TASK_LIST.PROPERTIES.DESCRIPTION_DEFAULT",
"cssClass": "hidden",
"sortable": true
},
{
"key": "noformName",
"type": "date",
"title": "ADF_TASK_LIST.PROPERTIES.FORM_NAME_DEFAULT",
"cssClass": "hidden",
"sortable": true
}
]
}
},
"adf-process-list": {
"presets": {
"default": [
{
"key": "name",
"type": "text",
"title": "ADF_PROCESS_LIST.PROPERTIES.NAME",
"sortable": true
},
{
"key": "created",
"type": "text",
"title": "ADF_PROCESS_LIST.PROPERTIES.CREATED",
"cssClass": "hidden",
"sortable": true
}
]
}
}
}
}

View File

@ -27,6 +27,7 @@
*ngIf="taskFilter && !isStartTaskMode()">
<adf-tasklist
[appId]="taskFilter?.appId"
[presetColumn]="presetColoum"
[page]="taskPage"
[size]="taskPagination.maxItems"
[processDefinitionKey]="taskFilter?.filter?.processDefinitionKey"
@ -43,10 +44,10 @@
#activititasklist>
<!-- Custom column definition demo -->
<data-columns>
<!-- <data-columns>
<data-column key="name" title="{{'ADF_TASK_LIST.PROPERTIES.NAME' | translate}}" class="full-width name-column"></data-column>
<data-column key="created" title="{{'ADF_TASK_LIST.PROPERTIES.CREATED' | translate}}" class="hidden"></data-column>
</data-columns>
</data-columns> -->
</adf-tasklist>
@ -134,6 +135,7 @@
*ngIf="processFilter?.hasFilter()" [appId]="processFilter.appId"
[processDefinitionKey]="processFilter.filter.processDefinitionKey"
[name]="processFilter.filter.name"
[presetColumn]="presetColoum"
[state]="processFilter.filter.state"
[sort]="processFilter.filter.sort"
[data]="dataProcesses"
@ -142,11 +144,11 @@
(success)="onSuccessProcessList($event)">
<!-- Custom column definition demo -->
<data-columns>
<!-- <data-columns>
<data-column key="name" title="ADF_PROCESS_LIST.PROPERTIES.NAME" class="full-width name-column"></data-column>
<data-column key="created" title="ADF_PROCESS_LIST.PROPERTIES.CREATED" class="hidden"></data-column>
</data-columns>
</data-columns> -->
</adf-process-instance-list>
</div>
<div class="activiti-grid-item activiti-processes-details" *ngIf="!isStartProcessMode()" fxFlex.gt-md="1 1 auto">

View File

@ -123,6 +123,7 @@ export class ActivitiComponent implements AfterViewInit, OnDestroy, OnInit {
dataTasks: ObjectDataTableAdapter;
dataProcesses: ObjectDataTableAdapter;
presetColoum: string = 'default';
fieldValidators = [
...FORM_FIELD_VALIDATORS,

View File

@ -26,12 +26,45 @@ This component renders a list containing all the process instances matched by th
</adf-process-instance-list>
```
You can also use custom schema declaration as shown below:
define custom schema in the app.config.json as shown below json format.
```json
"adf-process-list": {
"presets": {
"customSchema": [
{
"key": "name",
"type": "text",
"title": "name",
"sortable": true
}],
"default": [
{
"key": "name",
"type": "text",
"title": "name",
"sortable": true
}],
}
}
```
```html
<adf-process-instance-list
[appId]="'1'"
[state]="'open'"
[presetColumn]="'customSchema'">
</adf-process-instance-list>
```
### Properties
| Name | Description |
| --- | --- |
| appId | The id of the app. |
| processDefinitionKey | The processDefinitionKey of the process. |
| presetColumn | string || The presetColumn of the custom schema to fetch. |
| state | Define state of the processes. Possible values are `running`, `completed` and `all` |
| sort | Define sort of the processes. Possible values are `created-desc`, `created-asc`, `ended-desc`, `ended-asc` |
| schemaColumn | List of columns to display in the process instances datatable (see the [Details](#details) section below) |

View File

@ -23,6 +23,36 @@ You can also use HTML-based schema declaration like shown below:
</adf-tasklist>
```
You can also use custom schema declaration as shown below:
define custom schema in the app.config.json as shown below json format.
```json
"adf-task-list": {
"presets": {
"customSchema": [
{
"key": "name",
"type": "text",
"title": "name",
"sortable": true
}],
"default": [
{
"key": "name",
"type": "text",
"title": "name",
"sortable": true
}],
}
}
```
```html
<adf-tasklist
[appId]="'1'"
[presetColumn]="'customSchema'">
</adf-tasklist>
```
### Properties
| Name | Type | Default | Description |
@ -30,6 +60,7 @@ You can also use HTML-based schema declaration like shown below:
| appId | string || The id of the app. |
| processDefinitionKey | string || The processDefinitionKey of the process. |
| processInstanceId | string || The processInstanceId of the process. |
| presetColumn | string || The presetColumn of the custom schema to fetch. |
| page | number | 0 | The page of the tasks to fetch. |
| size | number | 5 | The number of tasks to fetch. |
| assignment | string || The assignment of the process. <ul>Possible values are: <li>assignee : where the current user is the assignee</li> <li>candidate: where the current user is a task candidate </li><li>group_x: where the task is assigned to a group where the current user is a member of.</li> <li>no value: where the current user is involved</li> </ul> |

View File

@ -21,7 +21,7 @@ import { MatProgressSpinnerModule } from '@angular/material';
import { Observable } from 'rxjs/Rx';
import { ProcessInstanceListComponent } from './process-list.component';
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
import { AlfrescoTranslationService, AppConfigService, CoreModule } from 'ng2-alfresco-core';
import { DataRowEvent, DataSorting, DataTableModule, ObjectDataRow, ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
import { fakeProcessInstances, fakeProcessInstancesWithNoName } from '../assets/process-instances-list.mock';
@ -34,6 +34,40 @@ describe('ProcessInstanceListComponent', () => {
let component: ProcessInstanceListComponent;
let service: ProcessService;
let getProcessInstancesSpy: jasmine.Spy;
let appConfig: AppConfigService;
let fakeCutomSchema = [
{
'key': 'fakeName',
'type': 'text',
'title': 'ADF_PROCESS_LIST.PROPERTIES.FAKE',
'sortable': true
},
{
'key': 'fakeProcessName',
'type': 'text',
'title': 'ADF_PROCESS_LIST.PROPERTIES.PROCESS_FAKE',
'sortable': true
}
];
let fakeColumnSchema = {
'default': [
{
'key': 'name',
'type': 'text',
'title': 'ADF_PROCESS_LIST.PROPERTIES.NAME',
'sortable': true
},
{
'key': 'created',
'type': 'text',
'title': 'ADF_PROCESS_LIST.PROPERTIES.CREATED',
'cssClass': 'hidden',
'sortable': true
}
]
, fakeCutomSchema };
beforeEach(async(() => {
TestBed.configureTestingModule({
@ -50,9 +84,31 @@ describe('ProcessInstanceListComponent', () => {
}).compileComponents().then(() => {
fixture = TestBed.createComponent(ProcessInstanceListComponent);
component = fixture.componentInstance;
appConfig = TestBed.get(AppConfigService);
service = fixture.debugElement.injector.get(ProcessService);
getProcessInstancesSpy = spyOn(service, 'getProcessInstances').and.returnValue(Observable.of(fakeProcessInstances));
appConfig.config = Object.assign(appConfig.config, {
'adf-process-list': {
'presets': {
'fakeCutomSchema': [
{
'key': 'fakeName',
'type': 'text',
'title': 'ADF_PROCESS_LIST.PROPERTIES.FAKE',
'sortable': true
},
{
'key': 'fakeProcessName',
'type': 'text',
'title': 'ADF_PROCESS_LIST.PROPERTIES.PROCESS_FAKE',
'sortable': true
}
]
}
}
}
);
});
}));
@ -76,6 +132,20 @@ describe('ProcessInstanceListComponent', () => {
expect(component.data.getColumns().length).toEqual(1);
});
it('should fetch the custom schemaColumn from app.config.json', () => {
component.ngAfterContentInit();
fixture.detectChanges();
expect(component.layoutPresets).toEqual(fakeColumnSchema);
});
it('should fetch custom schemaColumn when the input presetColumn is defined', () => {
component.presetColumn = 'fakeCutomColumns';
component.ngAfterContentInit();
fixture.detectChanges();
expect(component.data.getColumns()).toBeDefined();
expect(component.data.getColumns().length).toEqual(2);
});
it('should return an empty process list when no input parameters are passed', () => {
component.ngAfterContentInit();
expect(component.data).toBeDefined();

View File

@ -17,10 +17,11 @@
import { DatePipe } from '@angular/common';
import { AfterContentInit, Component, ContentChild, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { DataColumnListComponent } from 'ng2-alfresco-core';
import { DataColumn, DataRowEvent, DataSorting, DataTableAdapter, ObjectDataRow, ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
import { AppConfigService, DataColumnListComponent } from 'ng2-alfresco-core';
import { DataColumn, DataRowEvent, DataSorting, DataTableAdapter, ObjectDataColumn, ObjectDataRow, ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
import { ProcessFilterParamRepresentationModel } from '../models/filter-process.model';
import { ProcessInstance } from '../models/process-instance.model';
import { processPresetsDefaultModel } from '../models/process-preset.model';
import { ProcessService } from '../services/process.service';
@Component({
@ -47,6 +48,9 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit
@Input()
name: string;
@Input()
presetColumn: string;
requestNode: ProcessFilterParamRepresentationModel;
@Input()
@ -63,16 +67,14 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit
currentInstanceId: string;
isLoading: boolean = true;
layoutPresets = {};
private defaultSchema: DataColumn[] = [
{ type: 'text', key: 'name', title: 'ADF_PROCESS_LIST.PROPERTIES.NAME', cssClass: 'full-width name-column', sortable: true },
{ type: 'text', key: 'created', title: 'ADF_PROCESS_LIST.PROPERTIES.CREATED', cssClass: 'hidden', sortable: true }
];
constructor(private processService: ProcessService) {
constructor(private processService: ProcessService,
private appConfig: AppConfigService) {
}
ngAfterContentInit() {
this.loadLayoutPresets();
this.setupSchema();
if (this.appId) {
@ -92,12 +94,12 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit
}
if (!this.data) {
this.data = new ObjectDataTableAdapter([], schema.length > 0 ? schema : this.defaultSchema);
this.data = new ObjectDataTableAdapter([], schema.length > 0 ? schema : this.getLayoutPreset(this.presetColumn));
} else {
if (schema && schema.length > 0) {
this.data.setColumns(schema);
} else if (this.data.getColumns().length === 0) {
this.data.setColumns(this.defaultSchema);
this.presetColumn ? this.setupDefaultColumns(this.presetColumn) : this.setupDefaultColumns();
}
}
}
@ -287,4 +289,26 @@ export class ProcessInstanceListComponent implements OnChanges, AfterContentInit
};
return new ProcessFilterParamRepresentationModel(requestNode);
}
setupDefaultColumns(preset: string = 'default'): void {
if (this.data) {
const columns = this.getLayoutPreset(preset);
this.data.setColumns(columns);
}
}
private loadLayoutPresets(): void {
const externalSettings = this.appConfig.get('adf-process-list.presets', null);
if (externalSettings) {
this.layoutPresets = Object.assign({}, processPresetsDefaultModel, externalSettings);
} else {
this.layoutPresets = processPresetsDefaultModel;
}
}
private getLayoutPreset(name: string = 'default'): DataColumn[] {
return (this.layoutPresets[name] || this.layoutPresets['default']).map(col => new ObjectDataColumn(col));
}
}

View File

@ -0,0 +1,34 @@
/*!
* @license
* Copyright 2016 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 let processPresetsDefaultModel = {
'default': [
{
'key': 'name',
'type': 'text',
'title': 'ADF_PROCESS_LIST.PROPERTIES.NAME',
'sortable': true
},
{
'key': 'created',
'type': 'text',
'title': 'ADF_PROCESS_LIST.PROPERTIES.CREATED',
'cssClass': 'hidden',
'sortable': true
}
]
};

View File

@ -84,9 +84,50 @@ describe('TaskListComponent', () => {
error: 'wrong request'
};
let fakeCutomSchema = [
{
'key': 'fakeName',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.FAKE',
'sortable': true
},
{
'key': 'fakeTaskName',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.TASK_FAKE',
'sortable': true
}
];
let fakeColumnSchema = {
'default': [
{
'key': 'name',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.NAME',
'sortable': true
},
{
'key': 'created',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.CREATED',
'cssClass': 'hidden',
'sortable': true
},
{
'key': 'assignee',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.ASSIGNEE',
'cssClass': 'hidden',
'sortable': true
}
]
, fakeCutomSchema };
let component: TaskListComponent;
let fixture: ComponentFixture<TaskListComponent>;
let taskListService: TaskListService;
let appConfig: AppConfigService;
beforeEach(async(() => {
TestBed.configureTestingModule({
@ -107,13 +148,34 @@ describe('TaskListComponent', () => {
}));
beforeEach(() => {
let appConfig: AppConfigService = TestBed.get(AppConfigService);
appConfig = TestBed.get(AppConfigService);
appConfig.config.bpmHost = 'http://localhost:9876/bpm';
fixture = TestBed.createComponent(TaskListComponent);
component = fixture.componentInstance;
taskListService = TestBed.get(TaskListService);
appConfig.config = Object.assign(appConfig.config, {
'adf-task-list': {
'presets': {
'fakeCutomSchema': [
{
'key': 'fakeName',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.FAKE',
'sortable': true
},
{
'key': 'fakeTaskName',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.TASK_FAKE',
'sortable': true
}
]
}
}
}
);
});
@ -128,7 +190,7 @@ describe('TaskListComponent', () => {
it('should use the default schemaColumn as default', () => {
component.ngAfterContentInit();
expect(component.data.getColumns()).toBeDefined();
expect(component.data.getColumns().length).toEqual(2);
expect(component.data.getColumns().length).toEqual(3);
});
it('should use the schemaColumn passed in input', () => {
@ -144,6 +206,19 @@ describe('TaskListComponent', () => {
expect(component.data.getColumns().length).toEqual(1);
});
it('should use the custom schemaColumn from app.config.json', () => {
component.ngAfterContentInit();
fixture.detectChanges();
expect(component.layoutPresets).toEqual(fakeColumnSchema);
});
it('should fetch custom schemaColumn when the input presetColumn is defined', () => {
component.presetColumn = 'fakeCutomColumns';
fixture.detectChanges();
expect(component.data.getColumns()).toBeDefined();
expect(component.data.getColumns().length).toEqual(3);
});
it('should return an empty task list when no input parameters are passed', () => {
component.ngAfterContentInit();
expect(component.data).toBeDefined();

View File

@ -16,11 +16,12 @@
*/
import { AfterContentInit, Component, ContentChild, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { DataColumnListComponent } from 'ng2-alfresco-core';
import { DataColumn, DataRowEvent, DataTableAdapter, ObjectDataRow, ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
import { AppConfigService, DataColumnListComponent } from 'ng2-alfresco-core';
import { DataColumn, DataRowEvent, DataTableAdapter, ObjectDataColumn, ObjectDataRow, ObjectDataTableAdapter } from 'ng2-alfresco-datatable';
import { Observable } from 'rxjs/Rx';
import { TaskQueryRequestRepresentationModel } from '../models/filter.model';
import { TaskListModel } from '../models/task-list.model';
import { taskPresetsDefaultModel } from '../models/task-preset.model';
import { TaskListService } from './../services/tasklist.service';
const DEFAULT_SIZE = 5;
@ -65,6 +66,9 @@ export class TaskListComponent implements OnChanges, OnInit, AfterContentInit {
@Input()
selectionMode: string = 'none'; // none|single|multiple
@Input()
presetColumn: string;
@Input()
multiselect: boolean = false;
@ -82,6 +86,7 @@ export class TaskListComponent implements OnChanges, OnInit, AfterContentInit {
currentInstanceId: string;
selectedInstances: any[];
layoutPresets = {};
@Input()
page: number = 0;
@ -102,12 +107,8 @@ export class TaskListComponent implements OnChanges, OnInit, AfterContentInit {
hasCustomDataSource: boolean = false;
isStreamLoaded = false;
private defaultSchemaColumn: DataColumn[] = [
{ type: 'text', key: 'name', title: 'ADF_TASK_LIST.PROPERTIES.NAME', cssClass: 'full-width name-column', sortable: true },
{ type: 'text', key: 'created', title: 'ADF_TASK_LIST.PROPERTIES.CREATED', cssClass: 'hidden', sortable: true }
];
constructor(private taskListService: TaskListService) {
constructor(private taskListService: TaskListService,
private appConfig: AppConfigService) {
}
initStream() {
@ -135,6 +136,7 @@ export class TaskListComponent implements OnChanges, OnInit, AfterContentInit {
}
ngAfterContentInit() {
this.loadLayoutPresets();
this.setupSchema();
}
@ -150,12 +152,13 @@ export class TaskListComponent implements OnChanges, OnInit, AfterContentInit {
}
if (!this.data) {
this.data = new ObjectDataTableAdapter([], schema.length > 0 ? schema : this.defaultSchemaColumn);
this.data = new ObjectDataTableAdapter([], schema.length > 0 ? schema : this.presetColumn ? this.getLayoutPreset(this.presetColumn) : this.getLayoutPreset());
} else {
if (schema && schema.length > 0) {
this.data.setColumns(schema);
} else if (this.data.getColumns().length === 0) {
this.data.setColumns(this.defaultSchemaColumn);
this.presetColumn ? this.setupDefaultColumns(this.presetColumn) : this.setupDefaultColumns();
}
}
}
@ -324,4 +327,26 @@ export class TaskListComponent implements OnChanges, OnInit, AfterContentInit {
};
return new TaskQueryRequestRepresentationModel(requestNode);
}
setupDefaultColumns(preset: string = 'default'): void {
if (this.data) {
const columns = this.getLayoutPreset(preset);
this.data.setColumns(columns);
}
}
private loadLayoutPresets(): void {
const externalSettings = this.appConfig.get('adf-task-list.presets', null);
if (externalSettings) {
this.layoutPresets = Object.assign({}, taskPresetsDefaultModel, externalSettings);
} else {
this.layoutPresets = taskPresetsDefaultModel;
}
}
private getLayoutPreset(name: string = 'default'): DataColumn[] {
return (this.layoutPresets[name] || this.layoutPresets['default']).map(col => new ObjectDataColumn(col));
}
}

View File

@ -0,0 +1,41 @@
/*!
* @license
* Copyright 2016 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 let taskPresetsDefaultModel = {
'default': [
{
'key': 'name',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.NAME',
'sortable': true
},
{
'key': 'created',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.CREATED',
'cssClass': 'hidden',
'sortable': true
},
{
'key': 'assignee',
'type': 'text',
'title': 'ADF_TASK_LIST.PROPERTIES.ASSIGNEE',
'cssClass': 'hidden',
'sortable': true
}
]
};