From abc376ff33b48479792aa800d95c778349728166 Mon Sep 17 00:00:00 2001 From: mcchrys <48274621+mcchrys@users.noreply.github.com> Date: Mon, 8 Jul 2019 20:59:47 +0530 Subject: [PATCH] [ADF-4711] User Preferences in Task Filter (#4891) * [ADF-4711] User Preferences in Task Filter --- .../cloud/cloud-filters-demo.component.ts | 2 +- .../edit-task-filter-cloud.component.html | 135 +++++---- .../edit-task-filter-cloud.component.scss | 7 + .../edit-task-filter-cloud.component.spec.ts | 55 +++- .../edit-task-filter-cloud.component.ts | 103 ++++--- .../task-filters-cloud.component.spec.ts | 3 +- .../task-filters-cloud.component.ts | 17 +- .../mock/task-filters-cloud.mock.ts | 164 ++++++++++ .../task-filter-cloud.service.spec.ts | 210 +++++++++++++ .../services/task-filter-cloud.service.ts | 281 ++++++++++++------ 10 files changed, 776 insertions(+), 201 deletions(-) create mode 100644 lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.spec.ts diff --git a/demo-shell/src/app/components/cloud/cloud-filters-demo.component.ts b/demo-shell/src/app/components/cloud/cloud-filters-demo.component.ts index 2801f72d28..a97919d5f6 100644 --- a/demo-shell/src/app/components/cloud/cloud-filters-demo.component.ts +++ b/demo-shell/src/app/components/cloud/cloud-filters-demo.component.ts @@ -62,7 +62,7 @@ export class CloudFiltersDemoComponent implements OnInit { } onTaskFilterSelected(filter) { - this.cloudLayoutService.setCurrentTaskFilterParam({id: filter.id}); + this.cloudLayoutService.setCurrentTaskFilterParam({id: filter && filter.id ? filter.id : ''}); const currentFilter = Object.assign({}, filter); this.router.navigate([`/cloud/${this.appName}/tasks/`], { queryParams: currentFilter }); } 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 0f17cab0f7..524502f201 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,68 +1,77 @@ - + - - {{taskFilter.name | translate}} - - {{ 'ADF_CLOUD_EDIT_TASK_FILTER.TITLE' | translate}} -
- - - -
-
-
-
-
- - - - - {{ propertyOption.label }} - - - - - - - - {{taskFilterProperty.label | translate}} - - - -
-
-
{{'ADF_TASK_LIST.START_TASK.FORM.ERROR.DATE'|translate}}
- warning -
+ + + {{taskFilter.name | translate}} + + {{ 'ADF_CLOUD_EDIT_TASK_FILTER.TITLE' | translate}} +
+ + +
- -
- - {{taskFilterProperty.label | translate}} - -
+
-
- + +
+ +
+
+ + +
+
+ + + + + {{ propertyOption.label }} + + + + + + + + {{taskFilterProperty.label | translate}} + + + +
+
+
{{'ADF_TASK_LIST.START_TASK.FORM.ERROR.DATE'|translate}}
+ warning +
+
+
+
+ + {{taskFilterProperty.label | translate}} + +
+
+
+
+
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 a0d88621bc..5736c4b188 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 @@ -34,4 +34,11 @@ color: mat-color($warn); } } + + .adf { + &-cloud-edit-process-filter-loading-margin { + margin-left: calc((100% - 100px) / 2); + margin-right: calc((100% - 100px) / 2); + } + } } 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 d4f3147038..721d6a0b7a 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 @@ -29,6 +29,7 @@ import { fakeApplicationInstance } from '../../../app/mock/app-model.mock'; import { TaskFiltersCloudModule } from '../task-filters-cloud.module'; import { EditTaskFilterCloudComponent } from './edit-task-filter-cloud.component'; import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; +import { UserPreferenceCloudService } from '../../../services/user-preference.cloud.service'; import { TaskFilterDialogCloudComponent } from './task-filter-dialog-cloud.component'; import { fakeFilter, fakeAllTaskFilter } from '../mock/task-filters-cloud.mock'; import { AbstractControl } from '@angular/forms'; @@ -45,7 +46,7 @@ describe('EditTaskFilterCloudComponent', () => { setupTestBed({ imports: [ProcessServiceCloudTestingModule, TaskFiltersCloudModule], - providers: [MatDialog] + providers: [MatDialog, UserPreferenceCloudService] }); beforeEach(() => { @@ -59,7 +60,7 @@ describe('EditTaskFilterCloudComponent', () => { icon: 'icon', name: 'fake-name' }); }}); - getTaskFilterSpy = spyOn(service, 'getTaskFilterById').and.returnValue(fakeFilter); + getTaskFilterSpy = spyOn(service, 'getTaskFilterById').and.returnValue(of(fakeFilter)); getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance)); fixture.detectChanges(); }); @@ -68,7 +69,7 @@ describe('EditTaskFilterCloudComponent', () => { expect(component instanceof EditTaskFilterCloudComponent).toBeTruthy(); }); - it('should fetch task filter by taskId', async(() => { + it('should fetch task filter by taskId', () => { const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); component.ngOnChanges({ 'id': taskFilterIDchange}); fixture.detectChanges(); @@ -80,9 +81,9 @@ describe('EditTaskFilterCloudComponent', () => { expect(component.taskFilter.order).toEqual('ASC'); expect(component.taskFilter.sort).toEqual('id'); }); - })); + }); - it('should display filter name as title', () => { + it('should display filter name as title', async(() => { const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); component.ngOnChanges({ 'id': taskFilterIDchange}); fixture.detectChanges(); @@ -92,7 +93,37 @@ describe('EditTaskFilterCloudComponent', () => { expect(subTitle).toBeDefined(); expect(title.innerText).toEqual('FakeInvolvedTasks'); expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_TASK_FILTER.TITLE'); - }); + })); + + it('should not display mat-spinner if isloading set to false', async(() => { + const taskFilterIDchange = new SimpleChange(null, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIDchange }); + fixture.detectChanges(); + const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-title-id'); + const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-sub-title-id'); + const matSpinnerElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-edit-task-filter-loading-margin'); + + fixture.whenStable().then(() => { + expect(matSpinnerElement).toBeNull(); + expect(title).toBeDefined(); + expect(subTitle).toBeDefined(); + expect(title.innerText).toEqual('FakeInvolvedTasks'); + expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_TASK_FILTER.TITLE'); + }); + })); + + it('should display mat-spinner if isloading set to true', async(() => { + component.isLoading = true; + const taskFilterIDchange = new SimpleChange(null, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIDchange }); + fixture.detectChanges(); + + const matSpinnerElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-edit-task-filter-loading-margin'); + + fixture.whenStable().then(() => { + expect(matSpinnerElement).toBeDefined(); + }); + })); describe('EditTaskFilter form', () => { @@ -196,7 +227,7 @@ describe('EditTaskFilterCloudComponent', () => { it('should select \'All\' option in Task Status if All filter is set', async(() => { - getTaskFilterSpy.and.returnValue(fakeAllTaskFilter); + getTaskFilterSpy.and.returnValue(of(fakeAllTaskFilter)); const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); component.ngOnChanges({ 'id': taskFilterIDchange}); @@ -303,7 +334,11 @@ describe('EditTaskFilterCloudComponent', () => { it('should display sort properties when sort properties are specified', async(() => { component.sortProperties = ['id', 'name', 'processInstanceId']; - getTaskFilterSpy.and.returnValue({ sort: 'my-custom-sort', processInstanceId: 'process-instance-id', priority: '12' }); + getTaskFilterSpy.and.returnValue(of({ + sort: 'my-custom-sort', + processInstanceId: 'process-instance-id', + priority: '12' + })); fixture.detectChanges(); const taskFilterIDchange = new SimpleChange(undefined, 'mock-task-filter-id', true); component.ngOnChanges({ 'id': taskFilterIDchange}); @@ -451,7 +486,7 @@ describe('EditTaskFilterCloudComponent', () => { it('should emit save event and save the filter on click save button', async(() => { component.toggleFilterActions = true; - const saveFilterSpy = spyOn(service, 'updateFilter').and.returnValue(fakeFilter); + const saveFilterSpy = spyOn(service, 'updateFilter'); const saveSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); @@ -474,7 +509,7 @@ describe('EditTaskFilterCloudComponent', () => { it('should emit delete event and delete the filter on click of delete button', async(() => { component.toggleFilterActions = true; - const deleteFilterSpy = spyOn(service, 'deleteFilter').and.callThrough(); + const deleteFilterSpy = spyOn(service, 'deleteFilter'); const deleteSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); 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 0003f983ba..2261df355f 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 @@ -15,31 +15,32 @@ * limitations under the License. */ -import { Component, OnChanges, Input, Output, EventEmitter, SimpleChanges, OnInit } from '@angular/core'; +import { Component, OnChanges, Input, Output, EventEmitter, SimpleChanges, OnInit, OnDestroy } from '@angular/core'; import { AbstractControl, FormGroup, FormBuilder } from '@angular/forms'; -import { TaskFilterCloudModel, TaskFilterProperties, FilterOptions, TaskFilterAction } from './../models/filter-cloud.model'; -import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; import { MatDialog, DateAdapter } from '@angular/material'; -import { TaskFilterDialogCloudComponent } from './task-filter-dialog-cloud.component'; -import { TranslationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; -import { debounceTime, filter } from 'rxjs/operators'; -import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; -import { ApplicationInstanceModel } from '../../../app/models/application-instance.model'; +import { debounceTime, filter, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; import moment from 'moment-es6'; import { Moment } from 'moment'; +import { TaskFilterCloudModel, TaskFilterProperties, FilterOptions, TaskFilterAction } from './../models/filter-cloud.model'; +import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; +import { TaskFilterDialogCloudComponent } from './task-filter-dialog-cloud.component'; +import { TranslationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; +import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; +import { ApplicationInstanceModel } from '../../../app/models/application-instance.model'; + @Component({ selector: 'adf-cloud-edit-task-filter', templateUrl: './edit-task-filter-cloud.component.html', styleUrls: ['./edit-task-filter-cloud.component.scss'] }) -export class EditTaskFilterCloudComponent implements OnInit, OnChanges { +export class EditTaskFilterCloudComponent implements OnInit, OnChanges, OnDestroy { public static ACTION_SAVE = 'save'; public static ACTION_SAVE_AS = 'saveAs'; public static ACTION_DELETE = 'delete'; public static APP_RUNNING_STATUS: string = 'RUNNING'; - public static MIN_VALUE = 1; public static APPLICATION_NAME: string = 'appName'; public static LAST_MODIFIED: string = 'lastModified'; public static SORT: string = 'sort'; @@ -109,6 +110,9 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges { taskFilterActions: TaskFilterAction[] = []; toggleFilterActions: boolean = false; + private onDestroy$ = new Subject(); + isLoading: boolean = false; + constructor( private formBuilder: FormBuilder, public dialog: MatDialog, @@ -128,14 +132,13 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges { ngOnChanges(changes: SimpleChanges) { const id = changes['id']; if (id && id.currentValue !== id.previousValue) { - this.taskFilterProperties = this.createAndFilterProperties(); - this.taskFilterActions = this.createAndFilterActions(); - this.buildForm(this.taskFilterProperties); + this.retrieveTaskFilterAndBuildForm(); } } - retrieveTaskFilter(): TaskFilterCloudModel { - return new TaskFilterCloudModel(this.taskFilterCloudService.getTaskFilterById(this.appName, this.id)); + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); } buildForm(taskFilterProperties: TaskFilterProperties[]) { @@ -177,7 +180,25 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges { formValues.lastModifiedTo = lastModifiedToFilterValue.toDate(); } } - createAndFilterProperties(): TaskFilterProperties[] { + + /** + * Fetches task filter by application name and filter id and creates filter properties, build form + */ + retrieveTaskFilterAndBuildForm() { + this.isLoading = true; + this.taskFilterCloudService.getTaskFilterById(this.appName, this.id) + .pipe(takeUntil(this.onDestroy$)).subscribe((response) => { + this.isLoading = false; + this.taskFilter = new TaskFilterCloudModel(response); + this.taskFilterProperties = this.createAndFilterProperties(); + this.taskFilterActions = this.createAndFilterActions(); + this.buildForm(this.taskFilterProperties); + }, (error) => { + this.isLoading = false; + }); + } + + createAndFilterProperties() { this.checkMandatoryFilterProperties(); if (this.checkForApplicationNameProperty()) { @@ -185,7 +206,6 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges { this.getRunningApplications(); } - this.taskFilter = this.retrieveTaskFilter(); const defaultProperties = this.createTaskFilterProperties(this.taskFilter); let filteredProperties = defaultProperties.filter((filterProperty: TaskFilterProperties) => this.isValidProperty(this.filterProperties, filterProperty)); @@ -196,7 +216,6 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges { if (this.hasLastModifiedProperty()) { filteredProperties = [...filteredProperties, ...this.createLastModifiedProperty()]; } - return filteredProperties; } @@ -285,15 +304,16 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges { } /** - * Check if both filters are same + * Return true if both filters are same + * @param editedQuery, @param currentQuery */ - compareFilters(editedQuery, currentQuery): boolean { + compareFilters(editedQuery: TaskFilterCloudModel, currentQuery: TaskFilterCloudModel): boolean { return JSON.stringify(editedQuery).toLowerCase() === JSON.stringify(currentQuery).toLowerCase(); } getRunningApplications() { this.appsProcessCloudService.getDeployedApplicationsByStatus(EditTaskFilterCloudComponent.APP_RUNNING_STATUS) - .subscribe((applications: ApplicationInstanceModel[]) => { + .pipe(takeUntil(this.onDestroy$)).subscribe((applications: ApplicationInstanceModel[]) => { if (applications && applications.length > 0) { applications.map((application) => { this.applicationNames.push({ label: application.name, value: application.name }); @@ -313,16 +333,20 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges { } save(saveAction: TaskFilterAction) { - this.taskFilterCloudService.updateFilter(this.changedTaskFilter); - saveAction.filter = this.changedTaskFilter; - this.action.emit(saveAction); - this.formHasBeenChanged = this.compareFilters(this.changedTaskFilter, this.taskFilter); + this.taskFilterCloudService.updateFilter(this.changedTaskFilter) + .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { + saveAction.filter = this.changedTaskFilter; + this.action.emit(saveAction); + this.formHasBeenChanged = this.compareFilters(this.changedTaskFilter, this.taskFilter); + }); } delete(deleteAction: TaskFilterAction) { - this.taskFilterCloudService.deleteFilter(this.taskFilter); - deleteAction.filter = this.taskFilter; - this.action.emit(deleteAction); + this.taskFilterCloudService.deleteFilter(this.taskFilter) + .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { + deleteAction.filter = this.taskFilter; + this.action.emit(deleteAction); + }); } saveAs(saveAsAction: TaskFilterAction) { @@ -343,21 +367,30 @@ export class EditTaskFilterCloudComponent implements OnInit, OnChanges { id: filterId, key: 'custom-' + filterKey }; - const resultFilter = Object.assign({}, this.changedTaskFilter, newFilter); - this.taskFilterCloudService.addFilter(resultFilter); - saveAsAction.filter = resultFilter; - this.action.emit(saveAsAction); - + const resultFilter: TaskFilterCloudModel = Object.assign({}, this.changedTaskFilter, newFilter); + this.taskFilterCloudService.addFilter(resultFilter) + .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { + saveAsAction.filter = resultFilter; + this.action.emit(saveAsAction); + }); } }); } - getSanitizeFilterName(filterName): string { + /** + * Return filter name + * @param filterName + */ + getSanitizeFilterName(filterName: string): string { const nameWithHyphen = this.replaceSpaceWithHyphen(filterName.trim()); return nameWithHyphen.toLowerCase(); } - replaceSpaceWithHyphen(name) { + /** + * Return name with hyphen + * @param name + */ + replaceSpaceWithHyphen(name: string): string { const regExt = new RegExp(' ', 'g'); return name.replace(regExt, '-'); } diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.spec.ts index fdd952cae4..b2e75e1eed 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.spec.ts @@ -26,6 +26,7 @@ import { By } from '@angular/platform-browser'; import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; import { TaskFiltersCloudModule } from '../task-filters-cloud.module'; import { fakeGlobalFilter } from '../mock/task-filters-cloud.mock'; +import { UserPreferenceCloudService } from '../../../services/user-preference.cloud.service'; describe('TaskFiltersCloudComponent', () => { @@ -52,7 +53,7 @@ describe('TaskFiltersCloudComponent', () => { setupTestBed({ imports: [ProcessServiceCloudTestingModule, TaskFiltersCloudModule], - providers: [TaskFilterCloudService] + providers: [TaskFilterCloudService, UserPreferenceCloudService] }); beforeEach(() => { diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts index 0f7164048f..4e09b917b5 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts @@ -15,17 +15,19 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; -import { Observable } from 'rxjs'; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDestroy } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; import { TaskFilterCloudModel, FilterParamsModel } from '../models/filter-cloud.model'; import { TranslationService } from '@alfresco/adf-core'; +import { takeUntil } from 'rxjs/operators'; + @Component({ selector: 'adf-cloud-task-filters', templateUrl: './task-filters-cloud.component.html', styleUrls: ['task-filters-cloud.component.scss'] }) -export class TaskFiltersCloudComponent implements OnChanges { +export class TaskFiltersCloudComponent implements OnChanges, OnDestroy { /** Display filters available to the current user for the application with the specified name. */ @Input() appName: string; @@ -59,6 +61,8 @@ export class TaskFiltersCloudComponent implements OnChanges { filters: TaskFilterCloudModel [] = []; + private onDestroy$ = new Subject(); + constructor(private taskFilterCloudService: TaskFilterCloudService, private translationService: TranslationService) { } @@ -72,13 +76,18 @@ export class TaskFiltersCloudComponent implements OnChanges { } } + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + /** * Return the filter list filtered by appName */ getFilters(appName: string) { this.filters$ = this.taskFilterCloudService.getTaskListFilters(appName); - this.filters$.subscribe( + this.filters$.pipe(takeUntil(this.onDestroy$)).subscribe( (res: TaskFilterCloudModel[]) => { this.resetFilter(); this.filters = Object.assign([], res); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts b/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts index dc84ffdcce..b8a70bd993 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts @@ -67,3 +67,167 @@ export let fakeAllTaskFilter = new TaskFilterCloudModel({ order: 'ASC', sort: 'id' }); + +export const fakeTaskCloudPreferenceList = { + list: { + entries: [ + { + entry: { + key: 'task-filters-fakeAppName-mock-username', + value: JSON.stringify([ + { + name: 'FAKE_TASK_1', + id: '1', + key: 'all-fake-task', + icon: 'adjust', + appName: 'fakeAppName', + sort: 'startDate', + status: 'ALL', + order: 'DESC' + }, + { + name: 'FAKE_TASK_2', + id: '2', + key: 'run-fake-task', + icon: 'adjust', + appName: 'fakeAppName', + sort: 'startDate', + status: 'RUNNING', + order: 'DESC' + }, + { + name: 'FAKE_TASK_3', + id: '3', + key: 'complete-fake-task', + icon: 'adjust', + appName: 'fakeAppName', + sort: 'startDate', + status: 'COMPLETED', + order: 'DESC' + } + ]) + } + }, + { + entry: { + key: 'mock-key-2', + value: { + name: 'FAKE_TASK_2', + id: '2', + key: 'run-fake-task', + icon: 'adjust', + appName: 'fakeAppName', + sort: 'startDate', + status: 'RUNNING', + order: 'DESC' + } + } + }, + { + entry: { + key: 'mock-key-3', + value: { + name: 'FAKE_TASK_3', + id: '3', + key: 'complete-fake-task', + icon: 'adjust', + appName: 'fakeAppName', + sort: 'startDate', + status: 'COMPLETED`', + order: 'DESC' + } + } + } + ], + pagination: { + skipCount: 0, + maxItems: 100, + count: 3, + hasMoreItems: false, + totalItems: 3 + } + } +}; + +export const fakeEmptyTaskCloudPreferenceList = { + list: { + entries: [], + pagination: { + skipCount: 0, + maxItems: 100, + count: 0, + hasMoreItems: false, + totalItems: 0 + } + } +}; + +export const fakePreferenceWithNoTaskFilterPreference = { + list: { + entries: [ + { + entry: { + key: 'my-mock-key-1', + value: 'my-mock-value-2' + } + }, + { + entry: { + key: 'my-mock-key-2', + value: 'my-mock-key-2' + } + } + ], + pagination: { + skipCount: 0, + maxItems: 100, + count: 4, + hasMoreItems: false, + totalItems: 2 + } + } +}; + +export const fakeTaskFilter = new TaskFilterCloudModel({ + name: 'FAKE_TASK_1', + id: '1', + key: 'all-fake-task', + icon: 'adjust', + appName: 'fakeAppName', + sort: 'startDate', + order: 'DESC', + status: 'ALL' +}); + +export const fakeTaskCloudFilters = [ + { + name: 'FAKE_TASK_1', + id: '1', + key: 'all-fake-task', + icon: 'adjust', + appName: 'fakeAppName', + sort: 'startDate', + status: 'ALL', + order: 'DESC' + }, + { + name: 'FAKE_TASK_2', + id: '2', + key: 'run-fake-task', + icon: 'adjust', + appName: 'fakeAppName', + sort: 'startDate', + status: 'RUNNING', + order: 'DESC' + }, + { + name: 'FAKE_TASK_3', + id: '3', + key: 'complete-fake-task', + icon: 'adjust', + appName: 'fakeAppName', + sort: 'startDate', + status: 'COMPLETED', + order: 'DESC' + } +]; diff --git a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.spec.ts new file mode 100644 index 0000000000..1458e2a3fd --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.spec.ts @@ -0,0 +1,210 @@ +/*! + * @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 { async, TestBed } from '@angular/core/testing'; +import { setupTestBed, CoreModule, IdentityUserService } from '@alfresco/adf-core'; +import { of } from 'rxjs'; +import { TaskFilterCloudService } from './task-filter-cloud.service'; +import { UserPreferenceCloudService } from '../../../services/user-preference.cloud.service'; +import { + fakeTaskCloudPreferenceList, + fakeTaskCloudFilters, + fakeEmptyTaskCloudPreferenceList, + fakePreferenceWithNoTaskFilterPreference, + fakeTaskFilter +} from '../mock/task-filters-cloud.mock'; + +describe('Task Filter Cloud Service', () => { + let service: TaskFilterCloudService; + let userPreferenceCloudService: UserPreferenceCloudService; + let identityUserService: IdentityUserService; + let getPreferencesSpy: jasmine.Spy; + let getPreferenceByKeySpy: jasmine.Spy; + let createPreferenceSpy: jasmine.Spy; + let updatePreferenceSpy: jasmine.Spy; + let getCurrentUserInfoSpy: jasmine.Spy; + + const identityUserMock = { username: 'fakeusername', firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' }; + + setupTestBed({ + imports: [ + CoreModule.forRoot() + ], + providers: [TaskFilterCloudService, UserPreferenceCloudService, IdentityUserService] + }); + + beforeEach(async(() => { + service = TestBed.get(TaskFilterCloudService); + userPreferenceCloudService = TestBed.get(UserPreferenceCloudService); + identityUserService = TestBed.get(IdentityUserService); + createPreferenceSpy = spyOn(userPreferenceCloudService, 'createPreference').and.returnValue(of(fakeTaskCloudFilters)); + updatePreferenceSpy = spyOn(userPreferenceCloudService, 'updatePreference').and.returnValue(of(fakeTaskCloudFilters)); + getPreferencesSpy = spyOn(userPreferenceCloudService, 'getPreferences').and.returnValue(of(fakeTaskCloudPreferenceList)); + getPreferenceByKeySpy = spyOn(userPreferenceCloudService, 'getPreferenceByKey').and.returnValue(of(fakeTaskCloudFilters)); + getCurrentUserInfoSpy = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(identityUserMock); + })); + + it('should create TaskFilterCloudService instance', () => { + expect(service).toBeDefined(); + }); + + it('should create task filter key by using appName and the username', (done) => { + service.getTaskListFilters('fakeAppName').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(getCurrentUserInfoSpy).toHaveBeenCalled(); + done(); + }); + }); + + it('should create default task filters if there are no task filter preferences', (done) => { + getPreferencesSpy.and.returnValue(of(fakeEmptyTaskCloudPreferenceList)); + service.getTaskListFilters('fakeAppName').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + + expect(res[0].appName).toBe('fakeAppName'); + expect(res[0].id).toBe('1'); + expect(res[0].name).toBe('FAKE_TASK_1'); + expect(res[0].status).toBe('ALL'); + + expect(res[1].appName).toBe('fakeAppName'); + expect(res[1].id).toBe('2'); + expect(res[1].name).toBe('FAKE_TASK_2'); + expect(res[1].status).toBe('RUNNING'); + + expect(res[2].appName).toBe('fakeAppName'); + expect(res[2].id).toBe('3'); + expect(res[2].name).toBe('FAKE_TASK_3'); + expect(res[2].status).toBe('COMPLETED'); + done(); + }); + expect(createPreferenceSpy).toHaveBeenCalled(); + }); + + it('should return the task filters if filters available', (done) => { + service.getTaskListFilters('fakeAppName').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + + expect(res[0].appName).toBe('fakeAppName'); + expect(res[0].id).toBe('1'); + expect(res[0].name).toBe('FAKE_TASK_1'); + expect(res[0].status).toBe('ALL'); + + expect(res[1].appName).toBe('fakeAppName'); + expect(res[1].id).toBe('2'); + expect(res[1].name).toBe('FAKE_TASK_2'); + expect(res[1].status).toBe('RUNNING'); + + expect(res[2].appName).toBe('fakeAppName'); + expect(res[2].id).toBe('3'); + expect(res[2].name).toBe('FAKE_TASK_3'); + expect(res[2].status).toBe('COMPLETED'); + done(); + }); + expect(getPreferencesSpy).toHaveBeenCalled(); + }); + + it('should create the task filters if the user preference does not have task filters', (done) => { + getPreferencesSpy.and.returnValue(of(fakePreferenceWithNoTaskFilterPreference)); + service.getTaskListFilters('fakeAppName').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + + expect(res[0].appName).toBe('fakeAppName'); + expect(res[0].id).toBe('1'); + expect(res[0].name).toBe('FAKE_TASK_1'); + expect(res[0].status).toBe('ALL'); + + expect(res[1].appName).toBe('fakeAppName'); + expect(res[1].id).toBe('2'); + expect(res[1].name).toBe('FAKE_TASK_2'); + expect(res[1].status).toBe('RUNNING'); + + expect(res[2].appName).toBe('fakeAppName'); + expect(res[2].id).toBe('3'); + expect(res[2].name).toBe('FAKE_TASK_3'); + expect(res[2].status).toBe('COMPLETED'); + done(); + }); + expect(getPreferencesSpy).toHaveBeenCalled(); + expect(createPreferenceSpy).toHaveBeenCalled(); + }); + + it('should return filter by task filter id', (done) => { + service.getTaskFilterById('fakeAppName', '2').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.appName).toBe('fakeAppName'); + expect(res.id).toBe('2'); + expect(res.name).toBe('FAKE_TASK_2'); + expect(res.status).toBe('RUNNING'); + done(); + }); + expect(getPreferenceByKeySpy).toHaveBeenCalled(); + }); + + it('should add task filter if filter is not exist in the filters', (done) => { + getPreferenceByKeySpy.and.returnValue(of([])); + service.getTaskFilterById('fakeAppName', '2').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.appName).toBe('fakeAppName'); + expect(res.id).toBe('2'); + expect(res.name).toBe('FAKE_TASK_2'); + expect(res.status).toBe('RUNNING'); + done(); + }); + }); + + it('should update filter', (done) => { + createPreferenceSpy.and.returnValue(of(fakeTaskCloudFilters)); + service.updateFilter(fakeTaskFilter).subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + expect(res[0].appName).toBe('fakeAppName'); + expect(res[1].appName).toBe('fakeAppName'); + expect(res[2].appName).toBe('fakeAppName'); + done(); + }); + }); + + it('should create task filter when trying to update in case filter is not exist in the filters', (done) => { + getPreferenceByKeySpy.and.returnValue(of([])); + service.updateFilter(fakeTaskFilter).subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + expect(res[0].appName).toBe('fakeAppName'); + expect(res[1].appName).toBe('fakeAppName'); + expect(res[2].appName).toBe('fakeAppName'); + done(); + }); + expect(createPreferenceSpy).toHaveBeenCalled(); + }); + + it('should delete filter', (done) => { + service.deleteFilter(fakeTaskFilter).subscribe((res: any) => { + expect(res).toBeDefined(); + done(); + }); + expect(updatePreferenceSpy).toHaveBeenCalled(); + }); +}); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts index 0f94394d60..e60837181c 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts @@ -15,10 +15,12 @@ * limitations under the License. */ -import { StorageService, JwtHelperService } from '@alfresco/adf-core'; +import { IdentityUserService, IdentityUserModel } from '@alfresco/adf-core'; import { Injectable } from '@angular/core'; -import { Observable, BehaviorSubject } from 'rxjs'; +import { Observable, of, BehaviorSubject, throwError } from 'rxjs'; import { TaskFilterCloudModel } from '../models/filter-cloud.model'; +import { UserPreferenceCloudService } from '../../../services/public-api'; +import { switchMap, map, catchError } from 'rxjs/operators'; @Injectable() export class TaskFilterCloudService { @@ -26,38 +28,85 @@ export class TaskFilterCloudService { private filtersSubject: BehaviorSubject; filters$: Observable; - constructor(private storage: StorageService, private jwtHelperService: JwtHelperService) { + constructor( private identityUserService: IdentityUserService, + private preferenceService: UserPreferenceCloudService) { this.filtersSubject = new BehaviorSubject([]); this.filters$ = this.filtersSubject.asObservable(); } /** - * Creates and returns the default filters for a process app. + * Creates and returns the default task filters for an app. * @param appName Name of the target app - * @returns Observable of default filters just created + * @returns Observable of default filters task filters just created or created filters */ private createDefaultFilters(appName: string) { - const myTasksFilter = this.getMyTasksFilterInstance(appName); - this.addFilter(myTasksFilter); - - const completedTasksFilter = this.getCompletedTasksFilterInstance(appName); - this.addFilter(completedTasksFilter); + const key: string = this.prepareKey(appName); + this.preferenceService.getPreferences(appName).pipe( + switchMap((response: any) => { + const preferences = (response && response.list && response.list.entries) ? response.list.entries : []; + if (!this.hasPreferences(preferences)) { + return this.createTaskFilters(appName, key, this.defaultTaskFilters(appName)); + } else if (!this.hasTaskFilters(preferences, key)) { + return this.createTaskFilters(appName, key, this.defaultTaskFilters(appName)); + } else { + return of(this.findFiltersByKeyInPrefrences(preferences, key)); + } + }), + catchError((err) => this.handleTaskError(err)) + ).subscribe((filters) => { + this.addFiltersToStream(filters); + }); } /** - * Gets all task filters for a process app. + * Checks user preference are empty or not + * @param preferences User preferences of the target app + * @returns Boolean value if the preferences are not empty + */ + private hasPreferences(preferences: any): boolean { + return preferences && preferences.length > 0; + } + + /** + * Checks for task filters in given user preferences + * @param preferences User preferences of the target app + * @param key Key of the task filters + * @param filters Details of create filter + * @returns Boolean value if the preference has task filters + */ + private hasTaskFilters(preferences: any, key: string): boolean { + const filters = preferences.find((filter: any) => { return filter.entry.key === key; }); + return (filters && filters.entry) ? JSON.parse(filters.entry.value).length > 0 : false; + } + + /** + * Calls create preference api to create task filters + * @param appName Name of the target app + * @param key Key of the task instance filters + * @param filters Details of new task filter + * @returns Observable of created task filters + */ + private createTaskFilters(appName: string, key: string, filters: TaskFilterCloudModel[]): Observable { + return this.preferenceService.createPreference(appName, key, filters); + } + + /** + * Calls get preference api to get task filter by preference key + * @param appName Name of the target app + * @param key Key of the task filters + * @returns Observable of task filters + */ + private getTaskFiltersByKey(appName: string, key: string): Observable { + return this.preferenceService.getPreferenceByKey(appName, key); + } + + /** + * Gets all task filters for a task app. * @param appName Name of the target app * @returns Observable of task filter details */ getTaskListFilters(appName?: string): Observable { - const username = this.getUsername(); - const key = `task-filters-${appName}-${username}`; - const filters = JSON.parse(this.storage.getItem(key) || '[]'); - if (filters.length === 0) { - this.createDefaultFilters(appName); - } else { - this.addFiltersToStream(filters); - } + this.createDefaultFilters(appName); return this.filters$; } @@ -67,68 +116,109 @@ export class TaskFilterCloudService { * @param id ID of the task * @returns Details of the task filter */ - getTaskFilterById(appName: string, id: string): TaskFilterCloudModel { - const username = this.getUsername(); - const key = `task-filters-${appName}-${username}`; - let filters = []; - filters = JSON.parse(this.storage.getItem(key)) || []; - return filters.filter((filterTmp: TaskFilterCloudModel) => id === filterTmp.id)[0]; + getTaskFilterById(appName: string, id: string): any { + const key: string = this.prepareKey(appName); + return this.getTaskFiltersByKey(appName, key).pipe( + switchMap((filters: TaskFilterCloudModel[]) => { + if (filters && filters.length === 0) { + return this.createTaskFilters(appName, key, this.defaultTaskFilters(appName)); + } else { + return of(filters); + } + }), + map((filters: TaskFilterCloudModel[]) => { + return filters.filter((filter: TaskFilterCloudModel) => { + return filter.id === id; + })[0]; + }), + catchError((err) => this.handleTaskError(err)) + ); } /** * Adds a new task filter. * @param filter The new filter to add - * @returns Details of task filter just added + * @returns Obervable of task instance filters with newly added filter */ - addFilter(filter: TaskFilterCloudModel) { - const username = this.getUsername(); - const key = `task-filters-${filter.appName}-${username}`; - const filters = JSON.parse(this.storage.getItem(key) || '[]'); - - filters.push(filter); - - this.storage.setItem(key, JSON.stringify(filters)); - - this.addFiltersToStream(filters); + addFilter(newFilter: TaskFilterCloudModel) { + const key: string = this.prepareKey(newFilter.appName); + return this.getTaskFiltersByKey(newFilter.appName, key).pipe( + switchMap((filters: TaskFilterCloudModel[]) => { + if (filters && filters.length === 0) { + return this.createTaskFilters(newFilter.appName, key, [newFilter]); + } else { + filters.push(newFilter); + return this.preferenceService.updatePreference(newFilter.appName, key, filters); + } + }), + map((filters: TaskFilterCloudModel[]) => { + this.addFiltersToStream(filters); + return filters; + }), + catchError((err) => this.handleTaskError(err)) + ); } - private addFiltersToStream(filters: TaskFilterCloudModel []) { + private addFiltersToStream(filters: TaskFilterCloudModel[]) { this.filtersSubject.next(filters); } /** * Updates a task filter. * @param filter The filter to update + * @returns Observable of task instance filters with updated filter */ - updateFilter(filter: TaskFilterCloudModel) { - const username = this.getUsername(); - const key = `task-filters-${filter.appName}-${username}`; - if (key) { - const filters = JSON.parse(this.storage.getItem(key) || '[]'); - const itemIndex = filters.findIndex((flt: TaskFilterCloudModel) => flt.id === filter.id); - filters[itemIndex] = filter; - this.storage.setItem(key, JSON.stringify(filters)); - this.addFiltersToStream(filters); - } + updateFilter(updatedFilter: TaskFilterCloudModel): Observable { + const key: string = this.prepareKey(updatedFilter.appName); + return this.getTaskFiltersByKey(updatedFilter.appName, key).pipe( + switchMap((filters: any) => { + if (filters && filters.length === 0) { + return this.createTaskFilters(updatedFilter.appName, key, [updatedFilter]); + } else { + const itemIndex = filters.findIndex((filter: TaskFilterCloudModel) => filter.id === updatedFilter.id); + filters[itemIndex] = updatedFilter; + return this.updateProcessFilters(updatedFilter.appName, key, filters); + } + }), + map((updatedFilters: TaskFilterCloudModel[]) => { + this.addFiltersToStream(updatedFilters); + return updatedFilters; + }), + catchError((err) => this.handleTaskError(err)) + ); } /** * Deletes a task filter * @param filter The filter to delete + * @returns Observable of task instance filters without deleted filter */ - deleteFilter(filter: TaskFilterCloudModel) { - const username = this.getUsername(); - const key = `task-filters-${filter.appName}-${username}`; - if (key) { - let filters: TaskFilterCloudModel[] = JSON.parse(this.storage.getItem(key) || '[]'); - filters = filters.filter((item) => item.id !== filter.id); - this.storage.setItem(key, JSON.stringify(filters)); - if (filters.length === 0) { - this.createDefaultFilters(filter.appName); - } else { + deleteFilter(deletedFilter: TaskFilterCloudModel): Observable { + const key = this.prepareKey(deletedFilter.appName); + return this.getTaskFiltersByKey(deletedFilter.appName, key).pipe( + switchMap((filters: any) => { + if (filters && filters.length > 0) { + filters = filters.filter((filter: TaskFilterCloudModel) => filter.id !== deletedFilter.id); + return this.updateProcessFilters(deletedFilter.appName, key, filters); + } + }), + map((filters: TaskFilterCloudModel[]) => { this.addFiltersToStream(filters); - } - } + return filters; + }), + catchError((err) => this.handleTaskError(err)) + ); + } + + /** + * Calls update preference api to update task filter + * @param appName Name of the target app + * @param key Key of the task filters + * @param filters Details of update filter + * @returns Observable of updated task filters + */ + private updateProcessFilters(appName: string, key: string, filters: TaskFilterCloudModel[]): Observable { + return this.preferenceService.updatePreference(appName, key, filters); } /** @@ -136,43 +226,60 @@ export class TaskFilterCloudService { * @returns Username string */ getUsername(): string { - return this.jwtHelperService.getValueFromLocalAccessToken(JwtHelperService.USER_PREFERRED_USERNAME); + const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); + return user.username; } /** - * Creates and returns a filter for "My Tasks" task instances. + * Creates a uniq key with appName and username * @param appName Name of the target app - * @returns The newly created filter + * @returns String of task filters preference key */ - getMyTasksFilterInstance(appName: string): TaskFilterCloudModel { - const username = this.getUsername(); - return new TaskFilterCloudModel({ - name: 'ADF_CLOUD_TASK_FILTERS.MY_TASKS', - key: 'my-tasks', - icon: 'inbox', - appName: appName, - status: 'ASSIGNED', - assignee: username, - sort: 'createdDate', - order: 'DESC' - }); + private prepareKey(appName: string): string { + return `task-filters-${appName}-${this.getUsername()}`; } /** - * Creates and returns a filter for "Completed" task instances. + * Finds and returns the task filters from preferences * @param appName Name of the target app - * @returns The newly created filter + * @returns Array of TaskFilterCloudModel */ - getCompletedTasksFilterInstance(appName: string): TaskFilterCloudModel { - return new TaskFilterCloudModel({ - name: 'ADF_CLOUD_TASK_FILTERS.COMPLETED_TASKS', - key: 'completed-tasks', - icon: 'done', - appName: appName, - status: 'COMPLETED', - assignee: '', - sort: 'createdDate', - order: 'DESC' - }); + private findFiltersByKeyInPrefrences(preferences: any, key: string): TaskFilterCloudModel[] { + const result = preferences.find((filter: any) => { return filter.entry.key === key; }); + return result && result.entry ? JSON.parse(result.entry.value) : []; + } + + private handleTaskError(error: any) { + return throwError(error || 'Server error'); + } + + /** + * Creates and returns the default filters for a task app. + * @param appName Name of the target app + * @returns Array of TaskFilterCloudModel + */ + private defaultTaskFilters(appName: string): TaskFilterCloudModel[] { + return [ + new TaskFilterCloudModel({ + name: 'ADF_CLOUD_TASK_FILTERS.MY_TASKS', + key: 'my-tasks', + icon: 'inbox', + appName: appName, + status: 'ASSIGNED', + assignee: this.getUsername(), + sort: 'createdDate', + order: 'DESC' + }), + new TaskFilterCloudModel({ + name: 'ADF_CLOUD_TASK_FILTERS.COMPLETED_TASKS', + key: 'completed-tasks', + icon: 'done', + appName: appName, + status: 'COMPLETED', + assignee: '', + sort: 'createdDate', + order: 'DESC' + }) + ]; } }