diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.html b/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.html index 81e5da9857..c11b3fabdd 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.html +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.html @@ -1,5 +1,5 @@ -
{{ 'FILTERS.MESSAGES.NONE' | translate }}
-
+
{{ 'FILTERS.MESSAGES.NONE' | translate }}
+
{ + let fakeGlobalProcesses = [ + { + id: 1, name: 'fake-long-name-fake-long-name-fake-long-name-fak50-long-name', + processDefinitionId: 'fakeprocess:5:7507', + processDefinitionKey: 'fakeprocess', + processDefinitionName: 'Fake Process Name', + description: null, category: null, + started: '2017-11-09T12:37:25.184+0000', + startedBy: { + id: 3, firstName: 'tenant2', lastName: 'tenantLastname', email: 'tenant2@tenant' + } + }, + { + id: 2, name: '', description: null, category: null, + started: '2015-11-09T12:37:25.184+0000', + startedBy: { + id: 3, firstName: 'tenant2', lastName: 'tenantLastname', email: 'tenant2@tenant' + } + } + ]; + + let componentHandler: any; let fixture: ComponentFixture; let component: ActivitiProcessInstanceListComponent; - let element: DebugElement; let service: ActivitiProcessService; - let mockFilter = new FilterRepresentationModel({ - appId: '1', - filter: { - name: '', - state: '', - sort: '' - } - }); - beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ CoreModule, DataTableModule ], - declarations: [ ActivitiProcessInstanceListComponent ], // declare the test component + declarations: [ ActivitiProcessInstanceListComponent ], providers: [ ActivitiProcessService, {provide: AlfrescoTranslationService, useClass: TranslationMock} @@ -56,8 +67,13 @@ describe('ActivitiProcessInstanceListComponent', () => { }).compileComponents().then(() => { fixture = TestBed.createComponent(ActivitiProcessInstanceListComponent); component = fixture.componentInstance; - element = fixture.debugElement; - service = element.injector.get(ActivitiProcessService); + service = fixture.debugElement.injector.get(ActivitiProcessService); + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered', + 'upgradeElement' + ]); + window['componentHandler'] = componentHandler; }); })); @@ -80,37 +96,194 @@ describe('ActivitiProcessInstanceListComponent', () => { expect(component.data.getColumns().length).toEqual(1); }); - it('should fetch process instances when a filter is provided', () => { - let getProcessInstancesSpy = spyOn(service, 'getProcessInstances').and.returnValue(Observable.of([])); - component.filter = mockFilter; - fixture.detectChanges(); - expect(getProcessInstancesSpy).toHaveBeenCalled(); + it('should return an empty process list when no input parameters are passed', () => { + component.ngOnInit(); + expect(component.data).toBeDefined(); + expect(component.isListEmpty()).toBeTruthy(); }); - it('should NOT fetch process instances if filter not provided', () => { - let getProcessInstancesSpy = spyOn(service, 'getProcessInstances').and.returnValue(Observable.of([])); + it('should return the process instances list', (done) => { + spyOn(service, 'getProcessInstances').and.returnValue(Observable.of(fakeGlobalProcesses)); + component.appId = '1'; + component.state = 'open'; + component.processDefinitionKey = null; + component.onSuccess.subscribe( (res) => { + expect(res).toBeDefined(); + expect(component.data).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + expect(component.data.getRows().length).toEqual(2); + expect(component.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(component.data.getRows()[1].getValue('name')).toEqual('No name'); + done(); + }); fixture.detectChanges(); - expect(getProcessInstancesSpy).not.toHaveBeenCalled(); + }); + + it('should return the process instances list filtered by processDefinitionKey', (done) => { + spyOn(service, 'getProcessInstances').and.returnValue(Observable.of(fakeGlobalProcesses)); + component.appId = '1'; + component.state = 'open'; + component.processDefinitionKey = 'fakeprocess'; + component.onSuccess.subscribe( (res) => { + expect(res).toBeDefined(); + expect(component.data).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + expect(component.data.getRows().length).toEqual(2); + expect(component.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(component.data.getRows()[1].getValue('name')).toEqual('No name'); + done(); + }); + fixture.detectChanges(); + }); + + it('should return a currentId null when the processList is empty', () => { + component.selectFirst(); + expect(component.getCurrentId()).toBeNull(); + }); + + it('should throw an exception when the response is wrong', (done) => { + spyOn(service, 'getProcessInstances').and.returnValue(Observable.throw('Fake server error')); + component.state = 'open'; + component.onError.subscribe( (err) => { + expect(err).toBeDefined(); + expect(err).toBe('Fake server error'); + done(); + }); + fixture.detectChanges(); + }); + + it('should reload processes when reload() is called', (done) => { + spyOn(service, 'getProcessInstances').and.returnValue(Observable.throw('Fake server error')); + component.state = 'open'; + component.ngOnInit(); + component.onSuccess.subscribe( (res) => { + expect(res).toBeDefined(); + expect(component.data).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + expect(component.data.getRows().length).toEqual(2); + expect(component.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(component.data.getRows()[1].getValue('name')).toEqual('No name'); + done(); + }); + component.reload(); + }); + + it('should emit row click event', (done) => { + let row = new ObjectDataRow({ + id: 999 + }); + let rowEvent = {value: row}; + + component.rowClick.subscribe(taskId => { + expect(taskId).toEqual(999); + expect(component.getCurrentId()).toEqual(999); + done(); + }); + + component.onRowClick(rowEvent); }); describe('component changes', () => { - it('should fetch new process instances when filter changed', () => { - component.filter = new FilterRepresentationModel({}); - fixture.detectChanges(); - let getProcessInstancesSpy = spyOn(service, 'getProcessInstances').and.returnValue(Observable.of([])); - component.ngOnChanges({ filter: new SimpleChange(mockFilter, mockFilter) }); - expect(getProcessInstancesSpy).toHaveBeenCalled(); + beforeEach(() => { + spyOn(service, 'getProcessInstances').and.returnValue(Observable.of(fakeGlobalProcesses)); + component.data = new ObjectDataTableAdapter( + [], + [ + {type: 'text', key: 'fake-id', title: 'Name'} + ] + ); }); - it('should NOT fetch new process instances when properties apart from filter changed', () => { - component.filter = mockFilter; - fixture.detectChanges(); - let getProcessInstancesSpy = spyOn(service, 'getProcessInstances').and.returnValue(Observable.of([])); + it('should NOT reload the process list when no parameters changed', () => { + expect(component.isListEmpty()).toBeTruthy(); component.ngOnChanges({}); - expect(getProcessInstancesSpy).not.toHaveBeenCalled(); + expect(component.isListEmpty()).toBeTruthy(); }); - }); + it('should reload the list when the appId parameter changes', (done) => { + const appId = '1'; + let change = new SimpleChange(null, appId); + component.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.data).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + expect(component.data.getRows().length).toEqual(2); + expect(component.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(component.data.getRows()[1].getValue('name')).toEqual('No name'); + done(); + }); + + component.ngOnChanges({'appId': change}); + }); + + it('should reload the list when the processDefinitionKey parameter changes', (done) => { + const processDefinitionKey = 'fakeprocess'; + let change = new SimpleChange(null, processDefinitionKey); + + component.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.data).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + expect(component.data.getRows().length).toEqual(2); + expect(component.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(component.data.getRows()[1].getValue('name')).toEqual('No name'); + done(); + }); + + component.ngOnChanges({'processDefinitionKey': change}); + }); + + it('should reload the list when the state parameter changes', (done) => { + const state = 'open'; + let change = new SimpleChange(null, state); + + component.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.data).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + expect(component.data.getRows().length).toEqual(2); + expect(component.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(component.data.getRows()[1].getValue('name')).toEqual('No name'); + done(); + }); + + component.ngOnChanges({'state': change}); + }); + + it('should reload the list when the sort parameter changes', (done) => { + const sort = 'desc'; + let change = new SimpleChange(null, sort); + + component.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.data).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + expect(component.data.getRows().length).toEqual(2); + expect(component.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(component.data.getRows()[1].getValue('name')).toEqual('No name'); + done(); + }); + + component.ngOnChanges({'sort': change}); + }); + + it('should reload the process list when the name parameter changes', (done) => { + const name = 'FakeTaskName'; + let change = new SimpleChange(null, name); + + component.onSuccess.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.data).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + expect(component.data.getRows().length).toEqual(2); + expect(component.data.getRows()[0].getValue('name')).toEqual('fake-long-name-fake-long-name-fake-long-name-fak50...'); + expect(component.data.getRows()[1].getValue('name')).toEqual('No name'); + done(); + }); + + component.ngOnChanges({'name': change}); + }); + }); }); diff --git a/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.ts b/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.ts index 958e677bff..0f1276093e 100644 --- a/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.ts +++ b/ng2-components/ng2-activiti-processlist/src/components/activiti-processlist.component.ts @@ -15,10 +15,10 @@ * limitations under the License. */ -import { Component, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; import { AlfrescoTranslationService } from 'ng2-alfresco-core'; -import { ObjectDataTableAdapter, DataRowEvent, DataTableAdapter, ObjectDataRow } from 'ng2-alfresco-datatable'; -import { TaskQueryRequestRepresentationModel, FilterRepresentationModel } from 'ng2-activiti-tasklist'; +import { ObjectDataTableAdapter, DataTableAdapter, DataRowEvent, ObjectDataRow } from 'ng2-alfresco-datatable'; +import { TaskQueryRequestRepresentationModel } from 'ng2-activiti-tasklist'; import { ActivitiProcessService } from '../services/activiti-process.service'; @Component({ @@ -30,7 +30,21 @@ import { ActivitiProcessService } from '../services/activiti-process.service'; export class ActivitiProcessInstanceListComponent implements OnInit, OnChanges { @Input() - filter: FilterRepresentationModel; + appId: string; + + @Input() + processDefinitionKey: string; + + @Input() + state: string; + + @Input() + sort: string; + + @Input() + name: string; + + requestNode: TaskQueryRequestRepresentationModel; @Input() data: DataTableAdapter; @@ -44,11 +58,10 @@ export class ActivitiProcessInstanceListComponent implements OnInit, OnChanges { @Output() onError: EventEmitter = new EventEmitter(); - errorMessage: string; - currentProcessInstanceId: string; + currentInstanceId: string; private defaultSchemaColumn: any[] = [ - {type: 'text', key: 'id', title: 'Id', sortable: true}, + {type: 'text', key: 'id', title: 'Id'}, {type: 'text', key: 'name', title: 'Name', cssClass: 'full-width name-column', sortable: true}, {type: 'text', key: 'started', title: 'Started', sortable: true}, {type: 'text', key: 'startedBy.email', title: 'Started By', sortable: true} @@ -68,14 +81,39 @@ export class ActivitiProcessInstanceListComponent implements OnInit, OnChanges { } ngOnChanges(changes: SimpleChanges) { - let filter = changes['filter']; - if (filter && filter.currentValue) { - let requestNode = this.convertProcessInstanceToTaskQuery(filter.currentValue); - this.load(requestNode); - return; + if (this.isPropertyChanged(changes)) { + this.reload(); } } + private isPropertyChanged(changes: SimpleChanges): boolean { + let changed: boolean = false; + + let appId = changes['appId']; + let processDefinitionKey = changes['processDefinitionKey']; + let state = changes['state']; + let sort = changes['sort']; + let name = changes['name']; + + if (appId && appId.currentValue) { + changed = true; + } else if (processDefinitionKey && processDefinitionKey.currentValue) { + changed = true; + } else if (state && state.currentValue) { + changed = true; + } else if (sort && sort.currentValue) { + changed = true; + } else if (name && name.currentValue) { + changed = true; + } + return changed; + } + + public reload() { + this.requestNode = this.createRequestNode(); + this.load(this.requestNode); + } + /** * Return an initDefaultSchemaColumns instance with the default Schema Column * @returns {ObjectDataTableAdapter} @@ -90,62 +128,61 @@ export class ActivitiProcessInstanceListComponent implements OnInit, OnChanges { private load(requestNode: TaskQueryRequestRepresentationModel) { this.processService.getProcessInstances(requestNode) .subscribe( - (processInstances) => { - let processRow = this.createDataRow(processInstances); - this.renderProcessInstances(processRow); - this.selectFirstProcess(); - this.onSuccess.emit(processInstances); + (response) => { + let instancesRow = this.createDataRow(response); + this.renderInstances(instancesRow); + this.selectFirst(); + this.onSuccess.emit(response); }, error => { - this.errorMessage = error; this.onError.emit(error); }); } /** * Create an array of ObjectDataRow - * @param processes + * @param instances * @returns {ObjectDataRow[]} */ - private createDataRow(processes: any[]): ObjectDataRow[] { - let processRows: ObjectDataRow[] = []; - processes.forEach((row) => { - processRows.push(new ObjectDataRow({ + private createDataRow(instances: any[]): ObjectDataRow[] { + let instancesRows: ObjectDataRow[] = []; + instances.forEach((row) => { + instancesRows.push(new ObjectDataRow({ id: row.id, name: row.name, started: row.started })); }); - return processRows; + return instancesRows; } /** - * Render the process list + * Render the instances list * - * @param processInstances + * @param instances */ - private renderProcessInstances(processInstances: any[]) { - processInstances = this.optimizeProcessNames(processInstances); - this.data.setRows(processInstances); + private renderInstances(instances: any[]) { + instances = this.optimizeNames(instances); + this.data.setRows(instances); } /** - * Select the first process of a process list if present + * Select the first instance of a list if present */ - private selectFirstProcess() { + selectFirst() { if (!this.isListEmpty()) { - this.currentProcessInstanceId = this.data.getRows()[0].getValue('id'); + this.currentInstanceId = this.data.getRows()[0].getValue('id'); } else { - this.currentProcessInstanceId = null; + this.currentInstanceId = null; } } /** - * Return the current process + * Return the current id * @returns {string} */ - getCurrentProcessId(): string { - return this.currentProcessInstanceId; + getCurrentId(): string { + return this.currentInstanceId; } /** @@ -163,40 +200,33 @@ export class ActivitiProcessInstanceListComponent implements OnInit, OnChanges { */ onRowClick(event: DataRowEvent) { let item = event; - this.currentProcessInstanceId = item.value.getValue('id'); - this.rowClick.emit(this.currentProcessInstanceId); + this.currentInstanceId = item.value.getValue('id'); + this.rowClick.emit(this.currentInstanceId); } /** - * Optimize process name field - * @param tasks + * Optimize name field + * @param istances * @returns {any[]} */ - private optimizeProcessNames(tasks: any[]) { - tasks = tasks.map(t => { + private optimizeNames(istances: any[]) { + istances = istances.map(t => { t.obj.name = t.obj.name || 'No name'; if (t.obj.name.length > 50) { t.obj.name = t.obj.name.substring(0, 50) + '...'; } return t; }); - return tasks; + return istances; } - public reload() { - if (this.filter) { - let requestNode = this.convertProcessInstanceToTaskQuery(this.filter); - this.load(requestNode); - } - } - - private convertProcessInstanceToTaskQuery(processFilter: FilterRepresentationModel) { + private createRequestNode() { let requestNode = { - appDefinitionId: processFilter.appId, - processDefinitionKey: processFilter.filter.processDefinitionKey, - text: processFilter.filter.name, - state: processFilter.filter.state, - sort: processFilter.filter.sort + appDefinitionId: this.appId, + processDefinitionKey: this.processDefinitionKey, + text: this.name, + state: this.state, + sort: this.sort }; return new TaskQueryRequestRepresentationModel(requestNode); } diff --git a/ng2-components/ng2-activiti-processlist/src/models/process-instance-filter.model.ts b/ng2-components/ng2-activiti-processlist/src/models/process-instance-filter.model.ts index b5ac36df06..3b65d66160 100644 --- a/ng2-components/ng2-activiti-processlist/src/models/process-instance-filter.model.ts +++ b/ng2-components/ng2-activiti-processlist/src/models/process-instance-filter.model.ts @@ -17,6 +17,7 @@ export class ProcessFilterRequestRepresentation { processDefinitionId: string; + processDefinitionKey: string; appDefinitionId: string; state: string; sort: string; @@ -25,6 +26,7 @@ export class ProcessFilterRequestRepresentation { constructor(obj?: any) { this.processDefinitionId = obj && obj.processDefinitionId || null; + this.processDefinitionKey = obj && obj.processDefinitionKey || null; this.appDefinitionId = obj && obj.appDefinitionId || null; this.state = obj && obj.state || null; this.sort = obj && obj.sort || null; diff --git a/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.spec.ts b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.spec.ts index eb60c100f0..103a9463ab 100644 --- a/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.spec.ts +++ b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.spec.ts @@ -61,6 +61,14 @@ describe('ActivitiProcessService', () => { let getProcessInstances: jasmine.Spy; + let filter: ProcessFilterRequestRepresentation = new ProcessFilterRequestRepresentation({ + processDefinitionId: '1', + appDefinitionId: '1', + page: 1, + sort: 'created-asc', + state: 'completed' + }); + beforeEach(() => { getProcessInstances = spyOn(alfrescoApi.activiti.processApi, 'getProcessInstances') .and @@ -68,13 +76,13 @@ describe('ActivitiProcessService', () => { }); it('should return the correct number of instances', async(() => { - service.getProcessInstances(null).subscribe((instances) => { + service.getProcessInstances(filter).subscribe((instances) => { expect(instances.length).toBe(1); }); })); it('should return the correct instance data', async(() => { - service.getProcessInstances(null).subscribe((instances) => { + service.getProcessInstances(filter).subscribe((instances) => { let instance = instances[0]; expect(instance.id).toBe(exampleProcess.id); expect(instance.name).toBe(exampleProcess.name); @@ -83,27 +91,11 @@ describe('ActivitiProcessService', () => { })); it('should call service to fetch process instances', () => { - service.getProcessInstances(null); + service.getProcessInstances(filter); expect(getProcessInstances).toHaveBeenCalled(); }); - it('should call service with default parameters if no filter specified', () => { - service.getProcessInstances(null); - expect(getProcessInstances).toHaveBeenCalledWith(new ProcessFilterRequestRepresentation({ - page: 0, - sort: 'created-desc', - state: 'all' - })); - }); - it('should call service with supplied parameters', () => { - let filter: ProcessFilterRequestRepresentation = new ProcessFilterRequestRepresentation({ - processDefinitionId: '1', - appDefinitionId: '1', - page: 1, - sort: 'created-asc', - state: 'completed' - }); service.getProcessInstances(filter); expect(getProcessInstances).toHaveBeenCalledWith(filter); }); diff --git a/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.ts b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.ts index 9bcf9c0c4a..4a7b6d27fa 100644 --- a/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.ts +++ b/ng2-components/ng2-activiti-processlist/src/services/activiti-process.service.ts @@ -46,15 +46,15 @@ export class ActivitiProcessService { .catch(this.handleError); } - getProcessInstances(requestNode?: ProcessFilterRequestRepresentation): Observable { - requestNode = requestNode || new ProcessFilterRequestRepresentation({ - page: 0, - sort: 'created-desc', - state: 'all' - }); + getProcessInstances(requestNode: ProcessFilterRequestRepresentation): Observable { return Observable.fromPromise(this.authService.getAlfrescoApi().activiti.processApi.getProcessInstances(requestNode)) - .map(this.extractData) - .catch(this.handleError); + .map((res: any) => { + if (requestNode.processDefinitionKey) { + return res.data.filter(p => p.processDefinitionKey === requestNode.processDefinitionKey); + } else { + return res.data; + } + }).catch(this.handleError); } getProcessFilters(appId: number): Observable {