From 6fdd419476ff1de52264e93798946fe049f2b289 Mon Sep 17 00:00:00 2001 From: siva kumar Date: Thu, 10 Jan 2019 16:45:34 +0530 Subject: [PATCH] [ADF-3841] Improve edit-task-filter-cloud by adding inputs to control filters, sort and actions (#4113) * [ADF-3841] Improve edit-task-filter-cloud by adding inputs to control filters, sort and actions * Refactored editTask * Added translate keys related to the edittask filter * Created TaskFilterProperties model * * Refactored Edit Task Filter * Added method to fetch all running applications * * Added date validation error message * * Added css mixin * * Removed dateError flag * Used formController error * * Added default filter properties * * Refactored appsProcessCloudService * Added flexlayout to align all filter properties * * Modified unit test for the recent changes* Added few unit tests* Updated editTaskFilter documentation * * Rebased with the latest changes * updated doc * Updated translate keys --- .../cloud/tasks-cloud-demo.component.html | 1 - .../edit-task-filter-cloud.component.md | 53 ++- .../components/app-list-cloud.component.ts | 2 +- .../services/apps-process-cloud.service.ts | 31 +- .../src/lib/i18n/en.json | 22 +- .../src/lib/styles/_index.scss | 2 + .../edit-task-filter-cloud.component.html | 104 +++--- .../edit-task-filter-cloud.component.scss | 31 ++ .../edit-task-filter-cloud.component.spec.ts | 130 +++++-- .../edit-task-filter-cloud.component.ts | 324 ++++++++++++++++-- .../task-filters/models/filter-cloud.model.ts | 61 ++++ .../task-filters/task-filters-cloud.module.ts | 5 +- 12 files changed, 629 insertions(+), 137 deletions(-) diff --git a/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.html b/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.html index f0d8ca9059..63ea2f8616 100644 --- a/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.html +++ b/demo-shell/src/app/components/app-layout/cloud/tasks-cloud-demo.component.html @@ -5,7 +5,6 @@ (action)="onTaskFilterAction($event)" (filterChange)="onFilterChange($event)"> -
``` +By default these below properties are displayed: + +**_state_**, **_assignment_**, **_sort_**, **_order_**. + +However, you can also choose which properties to show using a input property +`filterProperties`: + +Populate the filterProperties in the component class: + +```ts +import { UserProcessModel } from '@alfresco/adf-core'; + +export class SomeComponent implements OnInit { + + filterProperties: string[] = [ + "appName", + "processInstanceId", + "createdDateTo", + "lastModifiedTo"]; + + onFilterChange(filter: TaskFilterCloudModel) { + console.log('On filter change: ', filter); + } + + onAction($event: FilterActionType) { + console.log('Clicked action: ', $event); + } +``` + +With this configuration, only the four listed properties will be shown. + +```html + + +``` + + +All Available properties are: + +**_appName_**, **_state_**, **_assignment_**, **_sort_**, **_order_**, **_processDefinitionId_**, **_processInstanceId_**, **_dueAfter_**, **_dueBefore_**, **_claimedDateFrom_**, **_claimedDateTo_**, **_createdDateFrom_**, **_createdDateTo_**, **_taskName_**, **_parentTaskId_**, **_priority_**, **_standAlone_**, **_lastModifiedFrom_**, **_lastModifiedTo_**, **_owner_**, **_dueDateFrom_**, **_dueDateTo_**. + ![edit-task-filter-cloud](../docassets/images/edit-task-filter-cloud.component.png) diff --git a/lib/process-services-cloud/src/lib/app/components/app-list-cloud.component.ts b/lib/process-services-cloud/src/lib/app/components/app-list-cloud.component.ts index 850dfcc21c..113d46b792 100644 --- a/lib/process-services-cloud/src/lib/app/components/app-list-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/app/components/app-list-cloud.component.ts @@ -30,7 +30,7 @@ public static LAYOUT_LIST: string = 'LIST'; public static LAYOUT_GRID: string = 'GRID'; - public static RUNNING_STATUS: string = 'Running'; + public static RUNNING_STATUS: string = 'RUNNING'; @ContentChild(EmptyCustomContentDirective) emptyCustomContent: EmptyCustomContentDirective; diff --git a/lib/process-services-cloud/src/lib/app/services/apps-process-cloud.service.ts b/lib/process-services-cloud/src/lib/app/services/apps-process-cloud.service.ts index bc41d0e903..a819b0d8d3 100644 --- a/lib/process-services-cloud/src/lib/app/services/apps-process-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/app/services/apps-process-cloud.service.ts @@ -16,7 +16,7 @@ */ import { Injectable } from '@angular/core'; -import { Observable, from, throwError } from 'rxjs'; +import { Observable, from, throwError, of } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; import { AlfrescoApiService } from '@alfresco/adf-core'; import { AppConfigService, LogService } from '@alfresco/adf-core'; @@ -26,22 +26,21 @@ import { ApplicationInstanceModel } from '../models/application-instance.model'; @Injectable() export class AppsProcessCloudService { - contextRoot = ''; - constructor( private apiService: AlfrescoApiService, private logService: LogService, - private appConfig: AppConfigService) { - this.contextRoot = this.appConfig.get('bpmHost', ''); - } + private appConfigService: AppConfigService) {} /** * Gets a list of deployed apps for this user by status. * @returns The list of deployed apps */ getDeployedApplicationsByStatus(status: string): Observable { + if (status === '') { + return of([]); + } const api: Oauth2Auth = this.apiService.getInstance().oauth2Auth; - const path = `${this.contextRoot}/alfresco-deployment-service/v1/applications`; + const path = this.getApplicationUrl(); const pathParams = {}, queryParams = {}, headerParams = {}, formParams = {}, bodyParam = {}, contentTypes = ['application/json'], accepts = ['application/json']; @@ -49,17 +48,19 @@ export class AppsProcessCloudService { return from(api.callCustomApi(path, 'GET', pathParams, queryParams, headerParams, formParams, bodyParam, contentTypes, accepts)) .pipe( - map((apps: Array<{}>) => { - return apps.filter((app: ApplicationInstanceModel) => app.status === status) - .map((app) => { - return new ApplicationInstanceModel(app); - }); - } - ), - catchError((err) => this.handleError(err)) + map((applications: ApplicationInstanceModel[]) => { + return applications.map((application) => { + return new ApplicationInstanceModel(application); + }); + }), + catchError((err) => this.handleError(err)) ); } + private getApplicationUrl() { + return `${this.appConfigService.get('bpmHost')}/alfresco-deployment-service/v1/applications`; + } + private handleError(error?: any) { this.logService.error(error); return throwError(error || 'Server error'); diff --git a/lib/process-services-cloud/src/lib/i18n/en.json b/lib/process-services-cloud/src/lib/i18n/en.json index 910da9db0c..2986c5c904 100644 --- a/lib/process-services-cloud/src/lib/i18n/en.json +++ b/lib/process-services-cloud/src/lib/i18n/en.json @@ -92,10 +92,30 @@ "DELETE": "Delete filter" }, "LABEL": { + "APP_NAME": "ApplicationName", + "PROCESS_DEF_ID": "ProcessDefinitionId", "STATUS": "Status", "ASSIGNMENT": "Assignment", "COLUMN": "Column", - "DIRECTION": "Direction" + "DIRECTION": "Direction", + "PROCESS_INSTANCE_ID": "ProcessInstanceId", + "DUE_BEFORE": "DueBefore", + "DUE_AFTER": "DueAfter", + "CLAIMED_DATE_FROM": "ClaimedDateFrom", + "CLAIMED_DATE_TO": "ClaimedDateTo", + "CREATED_DATE_FORM": "CreatedDateFrom", + "CREATED_DATE_TO": "CreatedDateTo", + "TASK_NAME": "TaskName", + "PARENT_TASK_ID": "ParentTaskId", + "PRIORITY": "Priority", + "STAND_ALONE": "StandAlone", + "LAST_MODIFIED_FROM": "LastModifiedFrom", + "LAST_MODIFIED_TO": "LastModifiedTo", + "OWNER": "Owner", + "DUE_DATE_TO": "DueDateTo", + "DUE_DATE_FROM": "DueDateFrom" + + }, "DIALOG": { "TITLE": "Save filter as", diff --git a/lib/process-services-cloud/src/lib/styles/_index.scss b/lib/process-services-cloud/src/lib/styles/_index.scss index d2beeb8557..6b5c25aa7a 100644 --- a/lib/process-services-cloud/src/lib/styles/_index.scss +++ b/lib/process-services-cloud/src/lib/styles/_index.scss @@ -1,6 +1,7 @@ @import './../app/components/app-details-cloud.component'; @import './../app/components/app-list-cloud.component'; @import './../task/task-filters/components/task-filters-cloud.component.scss'; +@import './../task/task-filters/components/edit-task-filter-cloud.component.scss'; @import './../process/process-list/components/process-list-cloud.component.scss'; @import './../task/start-task/components/start-task-cloud.component.scss'; @import './../task/start-task/components/people-cloud/people-cloud.component.scss'; @@ -10,6 +11,7 @@ @include adf-cloud-app-list-theme($theme); @include adf-cloud-app-details-theme($theme); @include adf-cloud-task-filters-theme($theme); + @include adf-cloud-edit-task-filters-theme($theme); @include adf-process-filters-cloud-theme($theme); @include adf-start-task-cloud-theme($theme); @include adf-cloud-people-theme($theme); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html index 9ef9afd3a1..2762237df8 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html @@ -1,67 +1,65 @@ - + - {{taskFilter.name | translate}} - - {{ 'ADF_CLOUD_EDIT_TASK_FILTER.TITLE' | translate}} + {{taskFilter.name | translate}} + + {{ 'ADF_CLOUD_EDIT_TASK_FILTER.TITLE' | translate}} +
+ + + + + +
-
- - - - {{ state.label }} +
+ + + + + {{ propertyOption.label }} - + + [formControlName]="taskFilterProperty.key" + type="text" + placeholder="{{taskFilterProperty.label | translate}}" + [attr.data-automation-id]="'adf-cloud-edit-task-property-' + taskFilterProperty.key"/> - - + + {{taskFilterProperty.label | translate}} + + + +
+
+
{{'ADF_TASK_LIST.START_TASK.FORM.ERROR.DATE'|translate}}
+ warning +
+
- - - - - - - {{ column.label }} - - - - - - - {{ direction }} - - - -
- - - -
-
+ +
diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.scss b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.scss index e69de29bb2..6934382cf3 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.scss +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.scss @@ -0,0 +1,31 @@ +@mixin adf-cloud-edit-task-filters-theme($theme) { + + $warn: map-get($theme, warn); + + .adf-edit-task-filter-date-error-container { + position: absolute; + height: 20px; + margin-top: 12px; + width: 100%; + + & > div { + display: flex; + flex-flow: row; + justify-content: flex-start; + } + + .adf-error-text { + padding-right: 8px; + height: 16px; + font-size: 10px; + line-height: 1.33; + color: mat-color($warn); + width: auto; + } + + .adf-error-icon { + font-size: 16px; + color: mat-color($warn); + } + } +} diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts index 9b84d4efff..00572c06cf 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts @@ -19,22 +19,27 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleChange } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { setupTestBed, IdentityUserService } from '@alfresco/adf-core'; +import { setupTestBed } from '@alfresco/adf-core'; +import { MatDialog } from '@angular/material'; +import { of } from 'rxjs'; + import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; +import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; +import { fakeApplicationInstance } from '../../../app/mock/app-model.mock'; import { TaskFilterCloudModel } from '../models/filter-cloud.model'; import { TaskFiltersCloudModule } from '../task-filters-cloud.module'; import { EditTaskFilterCloudComponent } from './edit-task-filter-cloud.component'; import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; -import { MatDialog } from '@angular/material'; -import { of } from 'rxjs'; import { TaskFilterDialogCloudComponent } from './task-filter-dialog-cloud.component'; describe('EditTaskFilterCloudComponent', () => { let component: EditTaskFilterCloudComponent; let service: TaskFilterCloudService; - let identityService: IdentityUserService; + let appsService: AppsProcessCloudService; let fixture: ComponentFixture; let dialog: MatDialog; + let getTaskFilterSpy: jasmine.Spy; + let getDeployedApplicationsByStatusSpy: jasmine.Spy; let fakeFilter = new TaskFilterCloudModel({ name: 'FakeInvolvedTasks', @@ -57,14 +62,15 @@ describe('EditTaskFilterCloudComponent', () => { fixture = TestBed.createComponent(EditTaskFilterCloudComponent); component = fixture.componentInstance; service = TestBed.get(TaskFilterCloudService); - identityService = TestBed.get(IdentityUserService); + appsService = TestBed.get(AppsProcessCloudService); dialog = TestBed.get(MatDialog); spyOn(dialog, 'open').and.returnValue({ afterClosed() { return of({ action: TaskFilterDialogCloudComponent.ACTION_SAVE, icon: 'icon', name: 'fake-name' }); }}); - spyOn(service, 'getTaskFilterById').and.returnValue(fakeFilter); + getTaskFilterSpy = spyOn(service, 'getTaskFilterById').and.returnValue(fakeFilter); + getDeployedApplicationsByStatusSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance)); }); it('should create EditTaskFilterCloudComponent', () => { @@ -93,7 +99,7 @@ describe('EditTaskFilterCloudComponent', () => { expect(title).toBeDefined(); expect(subTitle).toBeDefined(); expect(title.innerText).toEqual('FakeInvolvedTasks'); - expect(subTitle.innerText).toEqual('ADF_CLOUD_EDIT_TASK_FILTER.TITLE'); + expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_TASK_FILTER.TITLE'); }); describe('EditTaskFilter form', () => { @@ -104,11 +110,11 @@ describe('EditTaskFilterCloudComponent', () => { fixture.detectChanges(); }); - it('should define editTaskFilter form ', () => { + it('should defined editTaskFilter form ', () => { expect(component.editTaskFilterForm).toBeDefined(); }); - it('should create editTaskFilter form with given input ', async(() => { + it('should create editTaskFilter form with default properties', async(() => { fixture.detectChanges(); fixture.whenStable().then(() => { const stateController = component.editTaskFilterForm.get('state'); @@ -129,6 +135,7 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should disable save button if the task filter is not changed', async(() => { + component.showFilterActions = true; let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); fixture.detectChanges(); @@ -139,6 +146,7 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should disable saveAs button if the task filter is not changed', async(() => { + component.showFilterActions = true; let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); fixture.detectChanges(); @@ -149,9 +157,11 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should enable delete button by default', async(() => { + component.showFilterActions = true; fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); + fixture.detectChanges(); fixture.whenStable().then(() => { let deleteButton = fixture.debugElement.nativeElement.querySelector('#adf-delete-id'); expect(deleteButton.disabled).toBe(false); @@ -159,21 +169,22 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should display current task filter details', async(() => { - let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); - expansionPanel.click(); fixture.detectChanges(); fixture.whenStable().then(() => { - let stateElement = fixture.debugElement.nativeElement.querySelector('#adf-task-filter-state-id'); - let assignmentElement = fixture.debugElement.nativeElement.querySelector('#adf-task-filter-assignment-id'); - let sortElement = fixture.debugElement.nativeElement.querySelector('#adf-task-filter-sort-id'); - let orderElement = fixture.debugElement.nativeElement.querySelector('#adf-task-filter-order-id'); + let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + let stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-state"]'); + let assignmentElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-assignment"]'); + let sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"]'); + let orderElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-order"]'); expect(stateElement).toBeDefined(); expect(assignmentElement).toBeDefined(); expect(sortElement).toBeDefined(); expect(orderElement).toBeDefined(); - expect(stateElement.innerText.trim()).toBe('ADF_CLOUD_EDIT_TASK_FILTER.LABEL.STATUS'); - expect(sortElement.innerText.trim()).toBe('ADF_CLOUD_EDIT_TASK_FILTER.LABEL.COLUMN'); - expect(orderElement.innerText.trim()).toBe('ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DIRECTION'); + expect(stateElement.textContent.trim()).toBe('CREATED'); + expect(sortElement.textContent.trim()).toBe('ID'); + expect(orderElement.textContent.trim()).toBe('ASC'); }); })); @@ -181,7 +192,8 @@ describe('EditTaskFilterCloudComponent', () => { fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const stateElement = fixture.debugElement.query(By.css('#adf-task-filter-state-id .mat-select-trigger')).nativeElement; + fixture.detectChanges(); + let stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-state"] .mat-select-trigger'); stateElement.click(); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -194,7 +206,8 @@ describe('EditTaskFilterCloudComponent', () => { fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const sortElement = fixture.debugElement.query(By.css('#adf-task-filter-sort-id .mat-select-trigger')).nativeElement; + fixture.detectChanges(); + let sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"]'); sortElement.click(); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -207,7 +220,8 @@ describe('EditTaskFilterCloudComponent', () => { fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const orderElement = fixture.debugElement.query(By.css('#adf-task-filter-order-id .mat-select-trigger')).nativeElement; + fixture.detectChanges(); + let orderElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-order"]'); orderElement.click(); fixture.detectChanges(); fixture.whenStable().then(() => { @@ -215,6 +229,64 @@ describe('EditTaskFilterCloudComponent', () => { expect(orderOptions.length).toEqual(2); }); })); + + it('should able to fetch running applications', async(() => { + component.appName = 'mock-app-name'; + component.filterProperties = ['appName', 'processInstanceId', 'dueBefore']; + let change = new SimpleChange(undefined, 'mock-task-id', true); + component.ngOnChanges({ 'id': change }); + const appController = component.editTaskFilterForm.get('appName'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(appController).toBeDefined(); + expect(appController.value).toBe('mock-app-name' ); + expect(getDeployedApplicationsByStatusSpy).toHaveBeenCalled(); + }); + })); + + it('should able to build a editTaskFilter form with default properties if input is empty', async(() => { + component.filterProperties = []; + fixture.detectChanges(); + fixture.whenStable().then(() => { + const stateController = component.editTaskFilterForm.get('state'); + const sortController = component.editTaskFilterForm.get('sort'); + const orderController = component.editTaskFilterForm.get('order'); + fixture.detectChanges(); + expect(component.taskFilterProperties).toBeDefined(); + expect(component.taskFilterProperties.length).toBe(4); + expect(component.editTaskFilterForm).toBeDefined(); + expect(stateController).toBeDefined(); + expect(sortController).toBeDefined(); + expect(orderController).toBeDefined(); + expect(stateController.value).toBe('CREATED'); + expect(sortController.value).toBe('id'); + expect(orderController.value).toBe('ASC'); + }); + })); + + it('should able to build a editTaskFilter form with given input properties', async(() => { + getTaskFilterSpy.and.returnValue({ processInstanceId: 'process-instance-id', dueBefore: 'Fri Jan 04 2019 19:16:32 GMT+0530 (IST)' }); + component.appName = 'mock-app-name'; + component.filterProperties = ['appName', 'processInstanceId', 'dueBefore']; + let change = new SimpleChange(undefined, 'mock-task-id', true); + component.ngOnChanges({ 'id': change }); + const appController = component.editTaskFilterForm.get('appName'); + const dueDateController = component.editTaskFilterForm.get('dueBefore'); + const processInsIdController = component.editTaskFilterForm.get('processInstanceId'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(getDeployedApplicationsByStatusSpy).toHaveBeenCalled(); + expect(component.taskFilterProperties).toBeDefined(); + expect(component.editTaskFilterForm).toBeDefined(); + expect(component.taskFilterProperties.length).toBe(3); + expect(appController).toBeDefined(); + expect(dueDateController).toBeDefined(); + expect(processInsIdController).toBeDefined(); + expect(appController.value).toBe('mock-app-name'); + expect(processInsIdController.value).toBe('process-instance-id'); + }); + })); }); describe('edit filter actions', () => { @@ -222,16 +294,18 @@ describe('EditTaskFilterCloudComponent', () => { beforeEach(() => { let change = new SimpleChange(undefined, '10', true); component.ngOnChanges({ 'id': change }); + component.filterProperties = ['state']; }); it('should emit save event and save the filter on click save button', async(() => { - spyOn(identityService, 'getCurrentUserInfo').and.returnValue({username: 'currentUser'}); + component.showFilterActions = true; const saveFilterSpy = spyOn(service, 'updateFilter').and.returnValue(fakeFilter); let saveSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const stateElement = fixture.debugElement.query(By.css('#adf-task-filter-state-id .mat-select-trigger')).nativeElement; + fixture.detectChanges(); + let stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); stateElement.click(); fixture.detectChanges(); const stateOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); @@ -247,13 +321,14 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should emit delete event and delete the filter on click of delete button', async(() => { - spyOn(identityService, 'getCurrentUserInfo').and.returnValue({username: 'currentUser'}); + component.showFilterActions = true; const deleteFilterSpy = spyOn(service, 'deleteFilter').and.callThrough(); let deleteSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const stateElement = fixture.debugElement.query(By.css('#adf-task-filter-state-id .mat-select-trigger')).nativeElement; + fixture.detectChanges(); + let stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); stateElement.click(); fixture.detectChanges(); let deleteButton = fixture.debugElement.nativeElement.querySelector('#adf-delete-id'); @@ -266,13 +341,14 @@ describe('EditTaskFilterCloudComponent', () => { })); it('should emit saveAs event and add filter on click saveAs button', async(() => { - spyOn(identityService, 'getCurrentUserInfo').and.returnValue({username: 'currentUser'}); + component.showFilterActions = true; const saveAsFilterSpy = spyOn(service, 'addFilter').and.callThrough(); let saveAsSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); let expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); expansionPanel.click(); - const stateElement = fixture.debugElement.query(By.css('#adf-task-filter-state-id .mat-select-trigger')).nativeElement; + fixture.detectChanges(); + let stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); stateElement.click(); fixture.detectChanges(); const saveButton = fixture.debugElement.nativeElement.querySelector('#adf-save-as-id'); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts index e584233fd7..0ff7399e0d 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts @@ -16,37 +16,53 @@ */ import { Component, OnChanges, Input, Output, EventEmitter, SimpleChanges } from '@angular/core'; -import { FormGroup, FormBuilder } from '@angular/forms'; -import { TaskFilterCloudModel, FilterActionType } from './../models/filter-cloud.model'; +import { AbstractControl , FormGroup, FormBuilder } from '@angular/forms'; +import { TaskFilterCloudModel, FilterActionType, TaskFilterProperties, FilterOptions } from './../models/filter-cloud.model'; import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; import { MatDialog } from '@angular/material'; import { TaskFilterDialogCloudComponent } from './task-filter-dialog-cloud.component'; import { TranslationService } from '@alfresco/adf-core'; -import { debounceTime } from 'rxjs/operators'; +import { debounceTime, map, filter } from 'rxjs/operators'; +import { of, Observable } from 'rxjs'; +import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; +import { ApplicationInstanceModel } from '../../../app/models/application-instance.model'; +import moment from 'moment-es6'; @Component({ selector: 'adf-cloud-edit-task-filter', templateUrl: './edit-task-filter-cloud.component.html', - styleUrls: ['./edit-task-filter-cloud.component.scss'] + styleUrls: ['./edit-task-filter-cloud.component.scss'] }) export class EditTaskFilterCloudComponent implements OnChanges { public static ACTION_SAVE = 'SAVE'; public static ACTION_SAVE_AS = 'SAVE_AS'; public static ACTION_DELETE = 'DELETE'; + public static APP_RUNNING_STATUS: string = 'RUNNING'; + public static MIN_VALUE = 1; + public static DEFAULT_TASK_FILTER_PROPERTIES = ['state', 'assignment', 'sort', 'order']; + public FORMAT_DATE: string = 'DD/MM/YYYY'; - /** Name of the app. */ + /** (required) Name of the app. */ @Input() appName: string; - /** ID of the task filter. */ + /** (required) ID of the task filter. */ @Input() id: string; - taskFilter: TaskFilterCloudModel; - changedTaskFilter: TaskFilterCloudModel; + @Input() + filterProperties: string[] = EditTaskFilterCloudComponent.DEFAULT_TASK_FILTER_PROPERTIES; // default ['state', 'assignment', 'sort', 'order'] - /** Emitted when a task filter property changes. */ + /** Toggles the filter actions. */ + @Input() + toggleFilterActions = true; + + /** Toggles the title. */ + @Input() + showTitle = true; + + /** Emitted when an task filter property changes. */ @Output() filterChange: EventEmitter = new EventEmitter(); @@ -54,12 +70,15 @@ export class EditTaskFilterCloudComponent implements OnChanges { @Output() action: EventEmitter = new EventEmitter(); + taskFilter: TaskFilterCloudModel; + changedTaskFilter: TaskFilterCloudModel; + columns = [ - {key: 'id', label: 'ID'}, - {key: 'name', label: 'NAME'}, - {key: 'createdDate', label: 'Created Date'}, - {key: 'priority', label: 'PRIORITY'}, - {key: 'processDefinitionId', label: 'PROCESS DEFINITION ID'} + {value: 'id', label: 'ID'}, + {value: 'name', label: 'NAME'}, + {value: 'createdDate', label: 'Created Date'}, + {value: 'priority', label: 'PRIORITY'}, + {value: 'processDefinitionId', label: 'PROCESS DEFINITION ID'} ]; status = [ @@ -72,39 +91,48 @@ export class EditTaskFilterCloudComponent implements OnChanges { {label: 'DELETED', value: 'DELETED'} ]; - directions = ['ASC', 'DESC']; + directions = [ + { label: 'ASC', value: 'ASC' }, + { label: 'DESC', value: 'DESC' } + ]; + formHasBeenChanged = false; editTaskFilterForm: FormGroup; + taskFilterProperties: any[] = []; + showFilterActions: boolean = false; constructor( private formBuilder: FormBuilder, public dialog: MatDialog, private translateService: TranslationService, - private taskFilterCloudService: TaskFilterCloudService) {} + private taskFilterCloudService: TaskFilterCloudService, + private appsProcessCloudService: AppsProcessCloudService) { + } ngOnChanges(changes: SimpleChanges) { const id = changes['id']; if (id && id.currentValue !== id.previousValue) { this.retrieveTaskFilter(); - this.buildForm(); + this.initTaskFilterProperties(this.taskFilter); + this.buildForm(this.taskFilterProperties); } } - buildForm() { + retrieveTaskFilter() { + this.taskFilter = new TaskFilterCloudModel(this.taskFilterCloudService.getTaskFilterById(this.appName, this.id)); + } + + buildForm(taskFilterProperties: TaskFilterProperties[]) { this.formHasBeenChanged = false; - this.editTaskFilterForm = this.formBuilder.group({ - state: this.taskFilter.state, - appName: this.taskFilter.appName, - processDefinitionId: this.taskFilter.processDefinitionId, - assignment: this.taskFilter.assignment, - sort: this.taskFilter.sort, - order: this.taskFilter.order - }); + this.editTaskFilterForm = this.formBuilder.group(this.getFormControlsConfig(taskFilterProperties)); this.onFilterChange(); } - retrieveTaskFilter() { - this.taskFilter = this.taskFilterCloudService.getTaskFilterById(this.appName, this.id); + getFormControlsConfig(taskFilterProperties: TaskFilterProperties[]): any { + const properties = taskFilterProperties.map((property: TaskFilterProperties) => { + return { [property.key]: property.value }; + }); + return properties.reduce(((result, current) => Object.assign(result, current)), {}); } /** @@ -112,14 +140,58 @@ export class EditTaskFilterCloudComponent implements OnChanges { */ onFilterChange() { this.editTaskFilterForm.valueChanges - .pipe(debounceTime(300)) + .pipe(debounceTime(500), + filter(() => this.isFormValid())) .subscribe((formValues: TaskFilterCloudModel) => { - this.changedTaskFilter = new TaskFilterCloudModel(Object.assign({}, this.taskFilter, formValues)); - this.formHasBeenChanged = !this.compareFilters(this.changedTaskFilter, this.taskFilter); - this.filterChange.emit(this.changedTaskFilter); + this.changedTaskFilter = new TaskFilterCloudModel(Object.assign({}, this.taskFilter, formValues)); + this.formHasBeenChanged = !this.compareFilters(this.changedTaskFilter, this.taskFilter); + this.filterChange.emit(this.changedTaskFilter); }); } + initTaskFilterProperties(taskFilter: TaskFilterCloudModel) { + if (this.filterProperties && this.filterProperties.length > 0) { + const defaultProperties = this.defaultTaskFilterProperties(taskFilter); + this.taskFilterProperties = defaultProperties.filter((filterProperty) => this.isValidProperty(this.filterProperties, filterProperty)); + } else { + this.taskFilterProperties = EditTaskFilterCloudComponent.DEFAULT_TASK_FILTER_PROPERTIES; + } + } + + private isValidProperty(filterProperties: string[], filterProperty: any): boolean { + return filterProperties ? filterProperties.indexOf(filterProperty.key) >= 0 : true; + } + + isFormValid(): boolean { + return this.editTaskFilterForm.valid; + } + + getPropertyController(property: TaskFilterProperties): AbstractControl { + return this.editTaskFilterForm.get(property.key); + } + + onDateChanged(newDateValue: any, dateProperty: TaskFilterProperties) { + if (newDateValue) { + let momentDate; + + if (typeof newDateValue === 'string') { + momentDate = moment(newDateValue, this.FORMAT_DATE, true); + } else { + momentDate = newDateValue; + } + + if (momentDate.isValid()) { + this.getPropertyController(dateProperty).setValue(momentDate.toDate()); + } else { + this.getPropertyController(dateProperty).setErrors({ invalid: true }); + } + } + } + + hasError(property: TaskFilterProperties): boolean { + return this.getPropertyController(property).errors && this.getPropertyController(property).errors.invalid; + } + /** * Check if both filters are same */ @@ -127,6 +199,19 @@ export class EditTaskFilterCloudComponent implements OnChanges { return JSON.stringify(editedQuery).toLowerCase() === JSON.stringify(currentQuery).toLowerCase(); } + getRunningApplications(): Observable { + return this.appsProcessCloudService.getDeployedApplicationsByStatus(EditTaskFilterCloudComponent.APP_RUNNING_STATUS).pipe( + map((applications: ApplicationInstanceModel[]) => { + if (applications && applications.length > 0) { + let options: FilterOptions[] = []; + applications.map((application) => { + options.push({ label: application.name, value: application.name }); + }); + return options; + } + })); + } + onSave() { this.taskFilterCloudService.updateFilter(this.changedTaskFilter); this.action.emit({actionType: EditTaskFilterCloudComponent.ACTION_SAVE, id: this.changedTaskFilter.id}); @@ -155,14 +240,14 @@ export class EditTaskFilterCloudComponent implements OnChanges { id: filterId, key: 'custom-' + filterKey }; - const filter = Object.assign({}, this.changedTaskFilter, newFilter); - this.taskFilterCloudService.addFilter(filter); - this.action.emit({actionType: EditTaskFilterCloudComponent.ACTION_SAVE_AS, id: filter.id}); + const resultFilter = Object.assign({}, this.changedTaskFilter, newFilter); + this.taskFilterCloudService.addFilter(resultFilter); + this.action.emit({actionType: EditTaskFilterCloudComponent.ACTION_SAVE_AS, id: resultFilter.id}); } }); } - getSanitizeFilterName(filterName) { + getSanitizeFilterName(filterName): string { const nameWithHyphen = this.replaceSpaceWithHyphen(filterName.trim()); return nameWithHyphen.toLowerCase(); } @@ -171,4 +256,171 @@ export class EditTaskFilterCloudComponent implements OnChanges { const regExt = new RegExp(' ', 'g'); return name.replace(regExt, '-'); } + + toggleActions(): boolean { + return this.toggleFilterActions; + } + + onExpand(event: any) { + this.showFilterActions = true; + } + + onClose(event: any) { + this.showFilterActions = false; + } + + isDateType(property: TaskFilterProperties): boolean { + return property.type === 'date'; + } + + isSelectType(property: TaskFilterProperties): boolean { + return property.type === 'select'; + } + + isTextType(property: TaskFilterProperties): boolean { + return property.type === 'text'; + } + + defaultTaskFilterProperties(currentTaskFilter: TaskFilterCloudModel): TaskFilterProperties[] { + return [ + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.APP_NAME', + type: 'select', + key: 'appName', + value: this.appName || '', + options: this.getRunningApplications() + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.STATUS', + type: 'select', + key: 'state', + value: currentTaskFilter.state || this.status[0].value, + options: of(this.status) + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.ASSIGNMENT', + type: 'text', + key: 'assignment', + value: currentTaskFilter.assignment || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PROCESS_DEF_ID', + type: 'text', + key: 'processDefinitionId', + value: currentTaskFilter.processDefinitionId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.COLUMN', + type: 'select', + key: 'sort', + value: currentTaskFilter.sort || this.columns[0].value, + options: of(this.columns) + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DIRECTION', + type: 'select', + key: 'order', + value: currentTaskFilter.order || this.directions[0].value, + options: of(this.directions) + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PROCESS_INSTANCE_ID', + type: 'text', + key: 'processInstanceId', + value: currentTaskFilter.processInstanceId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DUE_AFTER', + type: 'date', + key: 'dueAfter', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DUE_BEFORE', + type: 'date', + key: 'dueBefore', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.CLAIMED_DATE_FROM', + type: 'date', + key: 'claimedDateFrom', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.CLAIMED_DATE_TO', + type: 'date', + key: 'claimedDateTo', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.CREATED_DATE_FORM', + type: 'date', + key: 'createdDateFrom', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.CREATED_DATE_TO', + type: 'date', + key: 'createdDateTo', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.TASK_NAME', + type: 'text', + key: 'taskName', + value: currentTaskFilter.taskName || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PARENT_TASK_ID', + type: 'text', + key: 'parentTaskId', + value: currentTaskFilter.parentTaskId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PRIORITY', + type: 'text', + key: 'priority', + value: currentTaskFilter.priority || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.STAND_ALONE', + type: 'text', + key: 'standAlone', + value: currentTaskFilter.standAlone || '' + }), + + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.LAST_MODIFIED_FROM', + type: 'date', + key: 'lastModifiedFrom', + value: '' + }), + + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.LAST_MODIFIED_TO', + type: 'date', + key: 'lastModifiedTo', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.OWNER', + type: 'text', + key: 'owner', + value: currentTaskFilter.owner || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DUE_DATE_FROM', + type: 'date', + key: 'dueDateFrom', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DUE_DATE_TO', + type: 'date', + key: 'dueDateTo', + value: '' + }) + ]; + } } diff --git a/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts b/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts index 0a1b89ea0e..a0bc0a3340 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { Observable } from 'rxjs'; + export class TaskFilterCloudModel { id: string; name: string; @@ -27,6 +29,23 @@ export class TaskFilterCloudModel { sort: string; assignment: string; order: string; + description: string; + dueAfter: Date; + dueBefore: Date; + owner: string; + processInstanceId: string; + claimedDateFrom: Date; + claimedDateTo: Date; + createdDateFrom: Date; + createdDateTo: Date; + dueDateFrom: Date; + dueDateTo: Date; + taskName: string; + parentTaskId: string; + priority: number; + standAlone: any; + lastModifiedFrom: Date; + lastModifiedTo: Date; constructor(obj?: any) { if (obj) { @@ -41,14 +60,32 @@ export class TaskFilterCloudModel { this.sort = obj.sort || null; this.assignment = obj.assignment || null; this.order = obj.order || null; + this.description = obj.description || null; + this.dueAfter = obj.dueAfter || null; + this.dueBefore = obj.dueBefore || null; + this.processInstanceId = obj.processInstanceId || null; + this.claimedDateFrom = obj.claimedDateFrom || null; + this.claimedDateTo = obj.claimedDateTo || null; + this.createdDateFrom = obj.createdDateFrom || null; + this.createdDateTo = obj.createdDateTo || null; + this.dueDateFrom = obj.dueDateFrom || null; + this.dueDateTo = obj.dueDateTo || null; + this.taskName = obj.taskName || null; + this.parentTaskId = obj.parentTaskId || null; + this.priority = obj.priority || null; + this.standAlone = obj.standAlone || null; + this.lastModifiedFrom = obj.lastModifiedFrom || null; + this.lastModifiedTo = obj.lastModifiedTo || null; } } } export class FilterParamsModel { + id?: string; name?: string; key?: string; index?: number; + constructor(obj?: any) { if (obj) { this.id = obj.id || null; @@ -58,7 +95,31 @@ export class FilterParamsModel { } } } + export interface FilterActionType { actionType: string; id: string; } + +export interface FilterOptions { + label?: string; + value?: string; +} + +export class TaskFilterProperties { + label: string; + type: string; // text|date|select + value: string; + key: string; + options$: Observable; + + constructor(obj?: any) { + if (obj) { + this.label = obj.label || null; + this.type = obj.type || null; + this.value = obj.value || null; + this.key = obj.key || null; + this.options$ = obj.options || null; + } + } +} diff --git a/lib/process-services-cloud/src/lib/task/task-filters/task-filters-cloud.module.ts b/lib/process-services-cloud/src/lib/task/task-filters/task-filters-cloud.module.ts index 930b510afb..2e381c2b4f 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/task-filters-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/task-filters-cloud.module.ts @@ -27,6 +27,8 @@ import { TaskFilterCloudService } from './services/task-filter-cloud.service'; import { HttpClientModule } from '@angular/common/http'; import { EditTaskFilterCloudComponent } from './components/edit-task-filter-cloud.component'; import { TaskFilterDialogCloudComponent } from './components/task-filter-dialog-cloud.component'; +import { AppListCloudModule } from './../../app/app-list-cloud.module'; + @NgModule({ imports: [ FormsModule, @@ -40,7 +42,8 @@ import { TaskFilterDialogCloudComponent } from './components/task-filter-dialog- useClass: TranslateLoaderService } }), - MaterialModule + MaterialModule, + AppListCloudModule ], declarations: [TaskFiltersCloudComponent, EditTaskFilterCloudComponent, TaskFilterDialogCloudComponent], exports: [TaskFiltersCloudComponent, EditTaskFilterCloudComponent],