From b88888b5e41a459a8d40583b91091d1e3e323270 Mon Sep 17 00:00:00 2001 From: siva kumar Date: Fri, 5 Jul 2019 16:22:36 +0530 Subject: [PATCH] [ADF-4614] User preferences in process filters (#4889) * * Created UserPreferenceCloudService. * * Refactored processfiltercloud service. * Refactored user-preferncecloud service. * Added unit tests to the services. * * Updated unit tests * FIxed Synx problem. * * Fixed edit-filter error. * * Fixed failing edit-process-filter unit test. * * Added comments in processfilter/prefernces services. * * Fixed memory leak* Added a spinner in the edit-process-filter component * * Fixed comments. * * Removed unnecessary filter property --- .../cloud/cloud-filters-demo.component.ts | 2 +- .../src/lib/mock/user-preference.mock.ts | 95 ++++++ .../src/lib/process-services-cloud.module.ts | 4 +- .../edit-process-filter-cloud.component.html | 119 +++---- .../edit-process-filter-cloud.component.scss | 8 + ...dit-process-filter-cloud.component.spec.ts | 48 ++- .../edit-process-filter-cloud.component.ts | 65 ++-- .../process-filters-cloud.component.spec.ts | 3 +- .../process-filters-cloud.component.ts | 17 +- .../mock/process-filters.cloud.mock.ts | 191 +++++++++++ .../process-filter-cloud.service.spec.ts | 210 +++++++++++++ .../services/process-filter-cloud.service.ts | 296 ++++++++++++------ .../src/lib/services/public-api.ts | 1 + .../user-preference.cloud.service.spec.ts | 201 ++++++++++++ .../services/user-preference.cloud.service.ts | 150 +++++++++ 15 files changed, 1219 insertions(+), 191 deletions(-) create mode 100644 lib/process-services-cloud/src/lib/mock/user-preference.mock.ts create mode 100644 lib/process-services-cloud/src/lib/process/process-filters/mock/process-filters.cloud.mock.ts create mode 100644 lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.spec.ts create mode 100644 lib/process-services-cloud/src/lib/services/user-preference.cloud.service.spec.ts create mode 100644 lib/process-services-cloud/src/lib/services/user-preference.cloud.service.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 7f27a2aa3a..2801f72d28 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 @@ -68,7 +68,7 @@ export class CloudFiltersDemoComponent implements OnInit { } onProcessFilterSelected(filter) { - this.cloudLayoutService.setCurrentProcessFilterParam({id: filter.id}); + this.cloudLayoutService.setCurrentProcessFilterParam({id: filter && filter.id ? filter.id : ''}); const currentFilter = Object.assign({}, filter); this.router.navigate([`/cloud/${this.appName}/processes/`], { queryParams: currentFilter }); } diff --git a/lib/process-services-cloud/src/lib/mock/user-preference.mock.ts b/lib/process-services-cloud/src/lib/mock/user-preference.mock.ts new file mode 100644 index 0000000000..45aff12608 --- /dev/null +++ b/lib/process-services-cloud/src/lib/mock/user-preference.mock.ts @@ -0,0 +1,95 @@ +/*! + * @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. + */ + +export const mockPreferences = { + list: { + entries: [ + { + entry: { + key: 'mock-preference-key-1', + value: [ + { username: 'mock-username-1', firstName: 'mock-firstname-1' }, + { username: 'mock-username-2', firstName: 'mock-firstname-2' } + ] + } + }, + { + entry: { + key: 'mock-preference-key-2', + value: 'my mock preference value' + } + }, + { + entry: { + key: 'mock-preference-key-3', + value: { + name: 'my-filter', + id: '3', + key: 'my-filter', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + state: 'MOCK-COMPLETED', + order: 'DESC' + } + } + } + ], + pagination: { + skipCount: 0, + maxItems: 100, + count: 3, + hasMoreItems: false, + totalItems: 3 + } + } +}; + +export const fakeEmptyPreferences = { + list: { + entries: [], + pagination: { + skipCount: 0, + maxItems: 100, + count: 0, + hasMoreItems: false, + totalItems: 0 + } + } +}; + +export const createMockPreference = { + name: 'create-preference', + id: '1', + key: 'my-preference', + icon: 'adjust', + appName: 'mock-appName' +}; + +export const updateMockPreference = { + name: 'update-preference', + id: '1', + key: 'update-preference', + icon: 'adjust', + appName: 'mock-appName' +}; + +export const getMockPreference = + [ + { username: 'mock-username-1', firstName: 'mock-firstname-1', appName: 'mock-appName' }, + { username: 'mock-username-2', firstName: 'mock-firstname-2', appName: 'mock-appName' } + ]; diff --git a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts index 01aa3ed579..c16fb19b41 100644 --- a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts @@ -24,6 +24,7 @@ import { GroupCloudModule } from './group/group-cloud.module'; import { FormCloudModule } from './form/form-cloud.module'; import { TaskFormModule } from './task/task-form/task-form.module'; import { BaseCloudService } from './services/base-cloud.service'; +import { UserPreferenceCloudService } from './services/user-preference.cloud.service'; @NgModule({ imports: [ @@ -44,7 +45,8 @@ import { BaseCloudService } from './services/base-cloud.service'; source: 'assets/adf-process-services-cloud' } }, - BaseCloudService + BaseCloudService, + UserPreferenceCloudService ], exports: [ AppListCloudModule, diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.html b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.html index f506e6ccb6..e921429b53 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.html +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.html @@ -1,60 +1,69 @@ - + - - {{processFilter.name | translate}} - - {{ 'ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE' | translate}} -
- - + + + {{processFilter.name | translate}} + + {{ 'ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE' | translate}} +
+ + + +
+
+
+ +
+ +
+
+
+ +
+
+ + + + + {{ propertyOption.label }} + + + + + + + + {{processFilterProperty.label | translate}} + + + +
+
+
{{'ADF_CLOUD_EDIT_PROCESS_FILTER.ERROR.DATE' | translate}}
+ warning +
+
+
- - - -
- - - - - {{ propertyOption.label }} - - - - - - - - {{processFilterProperty.label | translate}} - - - -
-
-
{{'ADF_CLOUD_EDIT_PROCESS_FILTER.ERROR.DATE' | translate}}
- warning -
-
-
-
-
-
+ +
diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.scss b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.scss index 498ba1f835..63a7de9a7e 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.scss +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.scss @@ -28,4 +28,12 @@ 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/process/process-filters/components/edit-process-filter-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts index 696dd114cb..1ac66263bf 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.spec.ts @@ -32,6 +32,7 @@ import { AppsProcessCloudService } from '../../../app/services/apps-process-clou import { fakeApplicationInstance } from './../../../app/mock/app-model.mock'; import moment from 'moment-es6'; import { AbstractControl } from '@angular/forms'; +import { UserPreferenceCloudService } from '../../../services/user-preference.cloud.service'; describe('EditProcessFilterCloudComponent', () => { let component: EditProcessFilterCloudComponent; @@ -55,7 +56,7 @@ describe('EditProcessFilterCloudComponent', () => { setupTestBed({ imports: [ProcessServiceCloudTestingModule, ProcessFiltersCloudModule], - providers: [MatDialog] + providers: [MatDialog, UserPreferenceCloudService] }); beforeEach(() => { @@ -73,7 +74,7 @@ describe('EditProcessFilterCloudComponent', () => { }); } }); - getProcessFilterByIdSpy = spyOn(service, 'getProcessFilterById').and.returnValue(fakeFilter); + getProcessFilterByIdSpy = spyOn(service, 'getFilterById').and.returnValue(of(fakeFilter)); getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance)); fixture.detectChanges(); }); @@ -101,7 +102,7 @@ describe('EditProcessFilterCloudComponent', () => { }); })); - it('should display filter name as title', () => { + it('should display filter name as title', async(() => { const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); component.ngOnChanges({ 'id': processFilterIDchange }); fixture.detectChanges(); @@ -114,7 +115,37 @@ describe('EditProcessFilterCloudComponent', () => { expect(title.innerText).toEqual('FakeRunningProcess'); expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE'); }); - }); + })); + + it('should not display mat-spinner if isloading set to false', async(() => { + const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIDchange }); + fixture.detectChanges(); + const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-title-id'); + const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-sub-title-id'); + const matSpinnerElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-edit-process-filter-loading-margin'); + + fixture.whenStable().then(() => { + expect(matSpinnerElement).toBeNull(); + expect(title).toBeDefined(); + expect(subTitle).toBeDefined(); + expect(title.innerText).toEqual('FakeRunningProcess'); + expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE'); + }); + })); + + it('should display mat-spinner if isloading set to true', async(() => { + component.isLoading = true; + const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': processFilterIDchange }); + fixture.detectChanges(); + + const matSpinnerElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-edit-process-filter-loading-margin'); + + fixture.whenStable().then(() => { + expect(matSpinnerElement).toBeDefined(); + }); + })); describe('EditProcessFilter form', () => { @@ -333,13 +364,13 @@ describe('EditProcessFilterCloudComponent', () => { })); it('should display sort properties when sort properties are specified', async(() => { - getProcessFilterByIdSpy.and.returnValue({ + getProcessFilterByIdSpy.and.returnValue(of({ id: 'filter-id', processName: 'process-name', sort: 'my-custom-sort', processDefinitionId: 'process-definition-id', priority: '12' - }); + })); component.sortProperties = ['id', 'processName', 'processDefinitionId']; fixture.detectChanges(); const processFilterIdchange = new SimpleChange(null, 'mock-process-filter-id', true); @@ -367,12 +398,13 @@ describe('EditProcessFilterCloudComponent', () => { beforeEach(() => { const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); component.ngOnChanges({ 'id': processFilterIDchange }); + getProcessFilterByIdSpy.and.returnValue(of(fakeFilter)); fixture.detectChanges(); }); 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').and.returnValue(of(fakeFilter)); const saveSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); @@ -395,7 +427,7 @@ describe('EditProcessFilterCloudComponent', () => { 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').and.returnValue(of()); const deleteSpy: jasmine.Spy = spyOn(component.action, 'emit'); fixture.detectChanges(); diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts index d873ecaa80..6835c3b6f3 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/edit-process-filter-cloud.component.ts @@ -15,10 +15,11 @@ * limitations under the License. */ -import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges, OnDestroy } from '@angular/core'; import { FormGroup, FormBuilder, AbstractControl } from '@angular/forms'; import { MatDialog, DateAdapter } from '@angular/material'; -import { debounceTime, filter } from 'rxjs/operators'; +import { debounceTime, filter, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; import moment from 'moment-es6'; import { Moment } from 'moment'; @@ -34,7 +35,7 @@ import { ProcessFilterDialogCloudComponent } from './process-filter-dialog-cloud templateUrl: './edit-process-filter-cloud.component.html', styleUrls: ['./edit-process-filter-cloud.component.scss'] }) -export class EditProcessFilterCloudComponent implements OnInit, OnChanges { +export class EditProcessFilterCloudComponent implements OnInit, OnChanges, OnDestroy { public static ACTION_SAVE = 'save'; public static ACTION_SAVE_AS = 'saveAs'; @@ -106,6 +107,9 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges { processFilterActions: ProcessFilterAction[] = []; toggleFilterActions: boolean = false; + private onDestroy$ = new Subject(); + isLoading: boolean = false; + constructor( private formBuilder: FormBuilder, public dialog: MatDialog, @@ -124,9 +128,7 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges { ngOnChanges(changes: SimpleChanges) { const id = changes['id']; if (id && id.currentValue !== id.previousValue) { - this.processFilterProperties = this.createAndFilterProperties(); - this.processFilterActions = this.createAndFilterActions(); - this.buildForm(this.processFilterProperties); + this.retrieveProcessFilterAndBuildForm(); } } @@ -147,10 +149,20 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges { } /** - * Return process instance filter by application name and filter id + * Fetches process instance filter by application name and filter id and creates filter properties, build form */ - retrieveProcessFilter(): ProcessFilterCloudModel { - return new ProcessFilterCloudModel(this.processFilterCloudService.getProcessFilterById(this.appName, this.id)); + retrieveProcessFilterAndBuildForm() { + this.isLoading = true; + this.processFilterCloudService.getFilterById(this.appName, this.id) + .pipe(takeUntil(this.onDestroy$)).subscribe((response) => { + this.isLoading = false; + this.processFilter = new ProcessFilterCloudModel(response); + this.processFilterProperties = this.createAndFilterProperties(); + this.processFilterActions = this.createAndFilterActions(); + this.buildForm(this.processFilterProperties); + }, (error) => { + this.isLoading = false; + }); } /** @@ -173,7 +185,6 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges { this.applicationNames = []; this.getRunningApplications(); } - this.processFilter = this.retrieveProcessFilter(); const defaultProperties = this.createProcessFilterProperties(this.processFilter); let filteredProperties = defaultProperties.filter((filterProperty: ProcessFilterProperties) => this.isValidProperty(this.filterProperties, filterProperty)); if (!this.hasSortProperty()) { @@ -182,7 +193,6 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges { if (this.hasLastModifiedProperty()) { filteredProperties = [...filteredProperties, ...this.createLastModifiedProperty()]; } - return filteredProperties; } @@ -283,7 +293,7 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges { getRunningApplications() { this.appsProcessCloudService.getDeployedApplicationsByStatus(EditProcessFilterCloudComponent.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 }); @@ -306,19 +316,23 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges { * Save a process instance filter */ save(saveAction: ProcessFilterAction) { - this.processFilterCloudService.updateFilter(this.changedProcessFilter); - saveAction.filter = this.changedProcessFilter; - this.action.emit(saveAction); - this.formHasBeenChanged = this.compareFilters(this.changedProcessFilter, this.processFilter); + this.processFilterCloudService.updateFilter(this.changedProcessFilter) + .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { + saveAction.filter = this.changedProcessFilter; + this.action.emit(saveAction); + this.formHasBeenChanged = this.compareFilters(this.changedProcessFilter, this.processFilter); + }); } /** * Delete a process instance filter */ delete(deleteAction: ProcessFilterAction) { - this.processFilterCloudService.deleteFilter(this.processFilter); - deleteAction.filter = this.processFilter; - this.action.emit(deleteAction); + this.processFilterCloudService.deleteFilter(this.processFilter) + .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { + deleteAction.filter = this.processFilter; + this.action.emit(deleteAction); + }); } /** @@ -343,9 +357,11 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges { key: 'custom-' + filterKey }; const resultFilter: ProcessFilterCloudModel = Object.assign({}, this.changedProcessFilter, newFilter); - this.processFilterCloudService.addFilter(resultFilter); - saveAsAction.filter = resultFilter; - this.action.emit(saveAsAction); + this.processFilterCloudService.addFilter(resultFilter) + .pipe(takeUntil(this.onDestroy$)).subscribe((res) => { + saveAsAction.filter = resultFilter; + this.action.emit(saveAsAction); + }); } }); } @@ -515,4 +531,9 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges { }) ]; } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } } diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts index 249f2dff48..161f3e0f53 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-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 { ProcessFiltersCloudModule } from '../process-filters-cloud.module'; import { FilterParamsModel } from '../../../task/task-filters/models/filter-cloud.model'; +import { UserPreferenceCloudService } from '../../../services/user-preference.cloud.service'; describe('ProcessFiltersCloudComponent', () => { @@ -75,7 +76,7 @@ describe('ProcessFiltersCloudComponent', () => { setupTestBed({ imports: [ProcessServiceCloudTestingModule, ProcessFiltersCloudModule], - providers: [ProcessFilterCloudService] + providers: [ProcessFilterCloudService, UserPreferenceCloudService] }); beforeEach(() => { diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.ts index 879cfd597b..7b0a50a134 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.ts @@ -15,18 +15,20 @@ * 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 { ProcessFilterCloudService } from '../services/process-filter-cloud.service'; import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model'; import { TranslationService } from '@alfresco/adf-core'; import { FilterParamsModel } from '../../../task/task-filters/models/filter-cloud.model'; +import { takeUntil } from 'rxjs/operators'; + @Component({ selector: 'adf-cloud-process-filters', templateUrl: './process-filters-cloud.component.html', styleUrls: ['process-filters-cloud.component.scss'] }) -export class ProcessFiltersCloudComponent implements OnChanges { +export class ProcessFiltersCloudComponent implements OnChanges, OnDestroy { /** (required) The application name */ @Input() @@ -58,6 +60,8 @@ export class ProcessFiltersCloudComponent implements OnChanges { filters: ProcessFilterCloudModel [] = []; + private onDestroy$ = new Subject(); + constructor( private processFilterCloudService: ProcessFilterCloudService, private translationService: TranslationService ) { } @@ -78,7 +82,7 @@ export class ProcessFiltersCloudComponent implements OnChanges { getFilters(appName: string) { this.filters$ = this.processFilterCloudService.getProcessFilters(appName); - this.filters$.subscribe( + this.filters$.pipe(takeUntil(this.onDestroy$)).subscribe( (res: ProcessFilterCloudModel[]) => { this.resetFilter(); this.filters = Object.assign([], res); @@ -163,4 +167,9 @@ export class ProcessFiltersCloudComponent implements OnChanges { this.filters = []; this.currentFilter = undefined; } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } } diff --git a/lib/process-services-cloud/src/lib/process/process-filters/mock/process-filters.cloud.mock.ts b/lib/process-services-cloud/src/lib/process/process-filters/mock/process-filters.cloud.mock.ts new file mode 100644 index 0000000000..84278df9b8 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/process-filters/mock/process-filters.cloud.mock.ts @@ -0,0 +1,191 @@ +/*! + * @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 { ProcessFilterCloudModel } from '../models/process-filter-cloud.model'; + +export const fakeProcessCloudFilterEntries = { + list: { + entries: [ + { + entry: { + key: 'process-filters-mock-appName-mock-username', + value: JSON.stringify([ + { + name: 'MOCK_PROCESS_NAME_1', + id: '1', + key: 'all-mock-process', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + status: 'MOCK_ALL', + order: 'DESC' + }, + { + name: 'MOCK_PROCESS_NAME_2', + id: '2', + key: 'run-mock-process', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + status: 'MOCK-RUNNING', + order: 'DESC' + }, + { + name: 'MOCK_PROCESS_NAME_3', + id: '3', + key: 'complete-mock-process', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + status: 'MOCK-COMPLETED', + order: 'DESC' + } + ]) + } + }, + { + entry: { + key: 'mock-key-2', + value: { + name: 'MOCK_PROCESS_NAME_2', + id: '2', + key: 'run-mock-process', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + status: 'MOCK-RUNNING', + order: 'DESC' + } + } + }, + { + entry: { + key: 'mock-key-3', + value: { + name: 'MOCK_PROCESS_NAME_3', + id: '3', + key: 'complete-mock-process', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + status: 'MOCK-COMPLETED', + order: 'DESC' + } + } + } + ], + pagination: { + skipCount: 0, + maxItems: 100, + count: 3, + hasMoreItems: false, + totalItems: 3 + } + } +}; + +export const fakeEmptyProcessCloudFilterEntries = { + list: { + entries: [], + pagination: { + skipCount: 0, + maxItems: 100, + count: 0, + hasMoreItems: false, + totalItems: 0 + } + } +}; + +export const fakeProcessCloudFilterWithDifferentEntries = { + 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 fakeProcessFilter: ProcessFilterCloudModel = { + name: 'MOCK_PROCESS_NAME_1', + id: '1', + key: 'all-mock-process', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + status: 'MOCK_ALL', + order: 'DESC', + index: 2, + processName: 'process-name', + processInstanceId: 'processinstanceid', + initiator: 'mockuser', + processDefinitionId: 'processDefid', + processDefinitionKey: 'processDefKey', + lastModified: null, + lastModifiedTo: null, + lastModifiedFrom: null +}; + +export const fakeProcessCloudFilters = [ + { + name: 'MOCK_PROCESS_NAME_1', + id: '1', + key: 'all-mock-process', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + status: 'MOCK_ALL', + order: 'DESC' + }, + { + name: 'MOCK_PROCESS_NAME_2', + id: '2', + key: 'run-mock-process', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + status: 'MOCK-RUNNING', + order: 'DESC' + }, + { + name: 'MOCK_PROCESS_NAME_3', + id: '3', + key: 'complete-mock-process', + icon: 'adjust', + appName: 'mock-appName', + sort: 'startDate', + status: 'MOCK-COMPLETED', + order: 'DESC' + } +]; diff --git a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.spec.ts new file mode 100644 index 0000000000..10926dab12 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/process-filters/services/process-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 { ProcessFilterCloudService } from './process-filter-cloud.service'; +import { UserPreferenceCloudService } from '../../../services/user-preference.cloud.service'; +import { + fakeProcessCloudFilterEntries, + fakeProcessCloudFilters, + fakeEmptyProcessCloudFilterEntries, + fakeProcessCloudFilterWithDifferentEntries, + fakeProcessFilter +} from '../mock/process-filters.cloud.mock'; + +describe('Process Filter Cloud Service', () => { + let service: ProcessFilterCloudService; + let userPreferenceCloudService: UserPreferenceCloudService; + let identityUserService: IdentityUserService; + let getPreferencesSpy: jasmine.Spy; + let getPreferenceByKeySpy: jasmine.Spy; + let updatePreferenceSpy: jasmine.Spy; + let createPreferenceSpy: jasmine.Spy; + let getCurrentUserInfoSpy: jasmine.Spy; + + const identityUserMock = { username: 'mock-username', firstName: 'fake-identity-first-name', lastName: 'fake-identity-last-name', email: 'fakeIdentity@email.com' }; + + setupTestBed({ + imports: [ + CoreModule.forRoot() + ], + providers: [ProcessFilterCloudService, UserPreferenceCloudService, IdentityUserService] + }); + + beforeEach(async(() => { + service = TestBed.get(ProcessFilterCloudService); + userPreferenceCloudService = TestBed.get(UserPreferenceCloudService); + identityUserService = TestBed.get(IdentityUserService); + createPreferenceSpy = spyOn(userPreferenceCloudService, 'createPreference').and.returnValue(of(fakeProcessCloudFilters)); + updatePreferenceSpy = spyOn(userPreferenceCloudService, 'updatePreference').and.returnValue(of(fakeProcessCloudFilters)); + getPreferenceByKeySpy = spyOn(userPreferenceCloudService, 'getPreferenceByKey').and.returnValue(of(fakeProcessCloudFilters)); + getPreferencesSpy = spyOn(userPreferenceCloudService, 'getPreferences').and.returnValue(of(fakeProcessCloudFilterEntries)); + getCurrentUserInfoSpy = spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(identityUserMock); + })); + + it('should create ProcessFilterCloudService instance', () => { + expect(service).toBeDefined(); + }); + + it('should create processfilter key by using appName and the username', (done) => { + service.getProcessFilters('mock-appName').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(getCurrentUserInfoSpy).toHaveBeenCalled(); + done(); + }); + }); + + it('should create default process filters', (done) => { + getPreferencesSpy.and.returnValue(of(fakeEmptyProcessCloudFilterEntries)); + service.getProcessFilters('mock-appName').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + + expect(res[0].appName).toBe('mock-appName'); + expect(res[0].id).toBe('1'); + expect(res[0].name).toBe('MOCK_PROCESS_NAME_1'); + expect(res[0].status).toBe('MOCK_ALL'); + + expect(res[1].appName).toBe('mock-appName'); + expect(res[1].id).toBe('2'); + expect(res[1].name).toBe('MOCK_PROCESS_NAME_2'); + expect(res[1].status).toBe('MOCK-RUNNING'); + + expect(res[2].appName).toBe('mock-appName'); + expect(res[2].id).toBe('3'); + expect(res[2].name).toBe('MOCK_PROCESS_NAME_3'); + expect(res[2].status).toBe('MOCK-COMPLETED'); + done(); + }); + expect(createPreferenceSpy).toHaveBeenCalled(); + }); + + it('should fetch the process filters if filters are available', (done) => { + service.getProcessFilters('mock-appName').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + + expect(res[0].appName).toBe('mock-appName'); + expect(res[0].id).toBe('1'); + expect(res[0].name).toBe('MOCK_PROCESS_NAME_1'); + expect(res[0].status).toBe('MOCK_ALL'); + + expect(res[1].appName).toBe('mock-appName'); + expect(res[1].id).toBe('2'); + expect(res[1].name).toBe('MOCK_PROCESS_NAME_2'); + expect(res[1].status).toBe('MOCK-RUNNING'); + + expect(res[2].appName).toBe('mock-appName'); + expect(res[2].id).toBe('3'); + expect(res[2].name).toBe('MOCK_PROCESS_NAME_3'); + expect(res[2].status).toBe('MOCK-COMPLETED'); + done(); + }); + expect(getPreferencesSpy).toHaveBeenCalled(); + }); + + it('should create the process filters in case the filters are not exist in the user preferences', (done) => { + getPreferencesSpy.and.returnValue(of(fakeProcessCloudFilterWithDifferentEntries)); + service.getProcessFilters('mock-appName').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + + expect(res[0].appName).toBe('mock-appName'); + expect(res[0].id).toBe('1'); + expect(res[0].name).toBe('MOCK_PROCESS_NAME_1'); + expect(res[0].status).toBe('MOCK_ALL'); + + expect(res[1].appName).toBe('mock-appName'); + expect(res[1].id).toBe('2'); + expect(res[1].name).toBe('MOCK_PROCESS_NAME_2'); + expect(res[1].status).toBe('MOCK-RUNNING'); + + expect(res[2].appName).toBe('mock-appName'); + expect(res[2].id).toBe('3'); + expect(res[2].name).toBe('MOCK_PROCESS_NAME_3'); + expect(res[2].status).toBe('MOCK-COMPLETED'); + done(); + }); + expect(getPreferencesSpy).toHaveBeenCalled(); + expect(createPreferenceSpy).toHaveBeenCalled(); + }); + + it('should return filter by process filter id', (done) => { + service.getFilterById('mock-appName', '2').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.appName).toBe('mock-appName'); + expect(res.id).toBe('2'); + expect(res.name).toBe('MOCK_PROCESS_NAME_2'); + expect(res.status).toBe('MOCK-RUNNING'); + done(); + }); + expect(getPreferenceByKeySpy).toHaveBeenCalled(); + }); + + it('should add process filter if filter is not exist in the filters', (done) => { + getPreferenceByKeySpy.and.returnValue(of([])); + service.getFilterById('mock-appName', '2').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.appName).toBe('mock-appName'); + expect(res.id).toBe('2'); + expect(res.name).toBe('MOCK_PROCESS_NAME_2'); + expect(res.status).toBe('MOCK-RUNNING'); + done(); + }); + }); + + it('should update filter', (done) => { + service.updateFilter(fakeProcessFilter).subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + expect(res[0].appName).toBe('mock-appName'); + expect(res[1].appName).toBe('mock-appName'); + expect(res[2].appName).toBe('mock-appName'); + done(); + }); + }); + + it('should create process filter when trying to update in case filter is not exist in the filters', (done) => { + getPreferenceByKeySpy.and.returnValue(of([])); + service.updateFilter(fakeProcessFilter).subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(3); + expect(res[0].appName).toBe('mock-appName'); + expect(res[1].appName).toBe('mock-appName'); + expect(res[2].appName).toBe('mock-appName'); + done(); + }); + expect(createPreferenceSpy).toHaveBeenCalled(); + }); + + it('should delete filter', (done) => { + service.deleteFilter(fakeProcessFilter).subscribe((res: any) => { + expect(res).toBeDefined(); + done(); + }); + expect(updatePreferenceSpy).toHaveBeenCalled(); + }); +}); diff --git a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts index c74d578214..e6db30639a 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/services/process-filter-cloud.service.ts @@ -15,10 +15,12 @@ * limitations under the License. */ -import { StorageService, IdentityUserService, IdentityUserModel } 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 { ProcessFilterCloudModel } from '../models/process-filter-cloud.model'; +import { UserPreferenceCloudService } from '../../../services/public-api'; +import { switchMap, map, catchError } from 'rxjs/operators'; @Injectable() export class ProcessFilterCloudService { @@ -27,41 +29,43 @@ export class ProcessFilterCloudService { filters$: Observable; constructor( - private storage: StorageService, + private preferenceService: UserPreferenceCloudService, private identityUserService: IdentityUserService) { this.filtersSubject = new BehaviorSubject([]); this.filters$ = this.filtersSubject.asObservable(); } /** - * Creates and returns the default filters for a process app. + * Creates and returns the default process instance filters for a app. * @param appName Name of the target app - * @returns Observable of default filters just created + * @returns Observable of default process instance filters just created or created filters */ private createDefaultFilters(appName: string) { - const allProcessesFilter = this.getAllProcessesFilter(appName); - this.addFilter(allProcessesFilter); - const runningProcessesFilter = this.getRunningProcessesFilter(appName); - this.addFilter(runningProcessesFilter); - const completedProcessesFilter = this.getCompletedProcessesFilter(appName); - this.addFilter(completedProcessesFilter); + 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.createProcessFilters(appName, key, this.defaultProcessFilters(appName)); + } else if (!this.hasProcessFilters(preferences, key)) { + return this.createProcessFilters(appName, key, this.defaultProcessFilters(appName)); + } else { + return of(this.findFiltersByKeyInPrefrences(preferences, key)); + } + }), + catchError((err) => this.handleProcessError(err)) + ).subscribe((filters) => { + this.addFiltersToStream(filters); + }); } /** * Gets all process instance filters for a process app. * @param appName Name of the target app - * @returns Observable of process filter details + * @returns Observable of process filters details */ getProcessFilters(appName: string): Observable { - const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); - const key = `process-filters-${appName}-${user.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$; } @@ -69,119 +73,213 @@ export class ProcessFilterCloudService { * Get process instance filter for given filter id * @param appName Name of the target app * @param id Id of the target process instance filter - * @returns Details of process filter + * @returns Observable of process instance filter details */ - getProcessFilterById(appName: string, id: string): ProcessFilterCloudModel { - const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); - const key = `process-filters-${appName}-${user.username}`; - let filters = []; - filters = JSON.parse(this.storage.getItem(key)) || []; - return filters.filter((filterTmp: ProcessFilterCloudModel) => id === filterTmp.id)[0]; + getFilterById(appName: string, id: string): Observable { + const key: string = this.prepareKey(appName); + return this.getProcessFiltersByKey(appName, key).pipe( + switchMap((filters: ProcessFilterCloudModel[]) => { + if (filters && filters.length === 0) { + return this.createProcessFilters(appName, key, this.defaultProcessFilters(appName)); + } else { + return of(filters); + } + }), + map((filters: ProcessFilterCloudModel[]) => { + return filters.filter((filter: ProcessFilterCloudModel) => { + return filter.id === id; + })[0]; + }), + catchError((err) => this.handleProcessError(err)) + ); } /** * Adds a new process instance filter * @param filter The new filter to add - * @returns Details of process filter just added + * @returns Obervable of process instance filters with newly added filter */ - addFilter(filter: ProcessFilterCloudModel) { - const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); - const key = `process-filters-${filter.appName}-${user.username}`; - const storedFilters = JSON.parse(this.storage.getItem(key) || '[]'); - - storedFilters.push(filter); - this.storage.setItem(key, JSON.stringify(storedFilters)); - - this.addFiltersToStream(storedFilters); + addFilter(newFilter: ProcessFilterCloudModel): Observable { + const key: string = this.prepareKey(newFilter.appName); + return this.getProcessFiltersByKey(newFilter.appName, key).pipe( + switchMap((filters: ProcessFilterCloudModel[]) => { + if (filters && filters.length === 0) { + return this.createProcessFilters(newFilter.appName, key, [newFilter]); + } else { + filters.push(newFilter); + return this.preferenceService.updatePreference(newFilter.appName, key, filters); + } + }), + map((filters: ProcessFilterCloudModel[]) => { + this.addFiltersToStream(filters); + return filters; + }), + catchError((err) => this.handleProcessError(err)) + ); } /** * Update process instance filter * @param filter The new filter to update + * @returns Observable of process instance filters with updated filter */ - updateFilter(filter: ProcessFilterCloudModel) { - const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); - const key = `process-filters-${filter.appName}-${user.username}`; - if (key) { - const filters = JSON.parse(this.storage.getItem(key) || '[]'); - const itemIndex = filters.findIndex((flt: ProcessFilterCloudModel) => flt.id === filter.id); - filters[itemIndex] = filter; - this.storage.setItem(key, JSON.stringify(filters)); - this.addFiltersToStream(filters); - } + updateFilter(updatedFilter: ProcessFilterCloudModel): Observable { + const key: string = this.prepareKey(updatedFilter.appName); + return this.getProcessFiltersByKey(updatedFilter.appName, key).pipe( + switchMap((filters: any) => { + if (filters && filters.length === 0) { + return this.createProcessFilters(updatedFilter.appName, key, [updatedFilter]); + } else { + const itemIndex = filters.findIndex((filter: ProcessFilterCloudModel) => filter.id === updatedFilter.id); + filters[itemIndex] = updatedFilter; + return this.updateProcessFilters(updatedFilter.appName, key, filters); + } + }), + map((updatedFilters: ProcessFilterCloudModel[]) => { + this.addFiltersToStream(updatedFilters); + return updatedFilters; + }), + catchError((err) => this.handleProcessError(err)) + ); } /** * Delete process instance filter * @param filter The new filter to delete + * @returns Observable of process instance filters without deleted filter */ - deleteFilter(filter: ProcessFilterCloudModel) { - const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); - const key = `process-filters-${filter.appName}-${user.username}`; - if (key) { - let filters = 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: ProcessFilterCloudModel): Observable { + const key = this.prepareKey(deletedFilter.appName); + return this.getProcessFiltersByKey(deletedFilter.appName, key).pipe( + switchMap((filters: any) => { + if (filters && filters.length > 0) { + filters = filters.filter((filter: ProcessFilterCloudModel) => filter.id !== deletedFilter.id); + return this.updateProcessFilters(deletedFilter.appName, key, filters); + } + }), + map((filters: ProcessFilterCloudModel[]) => { this.addFiltersToStream(filters); - } - } + return filters; + }), + catchError((err) => this.handleProcessError(err)) + ); } /** - * Creates and returns a filter for "All" Process instances. - * @param appName Name of the target app - * @returns The newly created filter + * Checks user preference are empty or not + * @param preferences User preferences of the target app + * @returns Boolean value if the preferences are not empty */ - getAllProcessesFilter(appName: string): ProcessFilterCloudModel { - return new ProcessFilterCloudModel({ - name: 'ADF_CLOUD_PROCESS_FILTERS.ALL_PROCESSES', - key: 'all-processes', - icon: 'adjust', - appName: appName, - sort: 'startDate', - status: '', - order: 'DESC' - }); + private hasPreferences(preferences: any): boolean { + return preferences && preferences.length > 0; } /** - * Creates and returns a filter for "Running" Process instances. - * @param appName Name of the target app - * @returns The newly created filter + * Checks for process instance filters in given user preferences + * @param preferences User preferences of the target app + * @param key Key of the process instance filters + * @param filters Details of create filter + * @returns Boolean value if the preference has process instance filters */ - getRunningProcessesFilter(appName: string): ProcessFilterCloudModel { - return new ProcessFilterCloudModel({ - name: 'ADF_CLOUD_PROCESS_FILTERS.RUNNING_PROCESSES', - icon: 'inbox', - key: 'running-processes', - appName: appName, - sort: 'startDate', - status: 'RUNNING', - order: 'DESC' - }); + private hasProcessFilters(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; } /** - * Creates and returns a filter for "Completed" Process instances. + * Calls create preference api to create process instance filters * @param appName Name of the target app - * @returns The newly created filter + * @param key Key of the process instance filters + * @param filters Details of new process instance filter + * @returns Observable of created process instance filters */ - getCompletedProcessesFilter(appName: string): ProcessFilterCloudModel { - return new ProcessFilterCloudModel({ - name: 'ADF_CLOUD_PROCESS_FILTERS.COMPLETED_PROCESSES', - icon: 'done', - key: 'completed-processes', - appName: appName, - sort: 'startDate', - status: 'COMPLETED', - order: 'DESC' - }); + private createProcessFilters(appName: string, key: string, filters: ProcessFilterCloudModel[]): Observable { + return this.preferenceService.createPreference(appName, key, filters); } - private addFiltersToStream(filters: ProcessFilterCloudModel []) { + /** + * Calls get preference api to get process instance filter by preference key + * @param appName Name of the target app + * @param key Key of the process instance filters + * @returns Observable of process instance filters + */ + private getProcessFiltersByKey(appName: string, key: string): Observable { + return this.preferenceService.getPreferenceByKey(appName, key); + } + + /** + * Calls update preference api to update process instance filter + * @param appName Name of the target app + * @param key Key of the process instance filters + * @param filters Details of update filter + * @returns Observable of updated process instance filters + */ + private updateProcessFilters(appName: string, key: string, filters: ProcessFilterCloudModel[]): Observable { + return this.preferenceService.updatePreference(appName, key, filters); + } + + /** + * Creates a uniq key with appName and username + * @param appName Name of the target app + * @returns String of process instance filters preference key + */ + private prepareKey(appName: string): string { + const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); + return `process-filters-${appName}-${user.username}`; + } + + /** + * Finds and returns the process instance filters from preferences + * @param appName Name of the target app + * @returns Array of ProcessFilterCloudModel + */ + private findFiltersByKeyInPrefrences(preferences: any, key: string): ProcessFilterCloudModel[] { + const result = preferences.find((filter: any) => { return filter.entry.key === key; }); + return result && result.entry ? JSON.parse(result.entry.value) : []; + } + + private addFiltersToStream(filters: ProcessFilterCloudModel[]) { this.filtersSubject.next(filters); } + + private handleProcessError(error: any) { + return throwError(error || 'Server error'); + } + + /** + * Creates and returns the default filters for a process app. + * @param appName Name of the target app + * @returns Array of ProcessFilterCloudModel + */ + private defaultProcessFilters(appName: string): ProcessFilterCloudModel[] { + return [ + new ProcessFilterCloudModel({ + name: 'ADF_CLOUD_PROCESS_FILTERS.ALL_PROCESSES', + key: 'all-processes', + icon: 'adjust', + appName: appName, + sort: 'startDate', + status: '', + order: 'DESC' + }), + new ProcessFilterCloudModel({ + name: 'ADF_CLOUD_PROCESS_FILTERS.RUNNING_PROCESSES', + icon: 'inbox', + key: 'running-processes', + appName: appName, + sort: 'startDate', + status: 'RUNNING', + order: 'DESC' + }), + new ProcessFilterCloudModel({ + name: 'ADF_CLOUD_PROCESS_FILTERS.COMPLETED_PROCESSES', + icon: 'done', + key: 'completed-processes', + appName: appName, + sort: 'startDate', + status: 'COMPLETED', + order: 'DESC' + }) + ]; + } } diff --git a/lib/process-services-cloud/src/lib/services/public-api.ts b/lib/process-services-cloud/src/lib/services/public-api.ts index a1ee695bf0..ec1caf78ed 100644 --- a/lib/process-services-cloud/src/lib/services/public-api.ts +++ b/lib/process-services-cloud/src/lib/services/public-api.ts @@ -16,3 +16,4 @@ */ export * from './identity-user.service'; +export * from './user-preference.cloud.service'; diff --git a/lib/process-services-cloud/src/lib/services/user-preference.cloud.service.spec.ts b/lib/process-services-cloud/src/lib/services/user-preference.cloud.service.spec.ts new file mode 100644 index 0000000000..0896cfa643 --- /dev/null +++ b/lib/process-services-cloud/src/lib/services/user-preference.cloud.service.spec.ts @@ -0,0 +1,201 @@ +/*! + * @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 { TestBed, async } from '@angular/core/testing'; + +import { UserPreferenceCloudService } from './user-preference.cloud.service'; +import { setupTestBed, CoreModule, AlfrescoApiServiceMock, AppConfigService, LogService, AlfrescoApiService } from '@alfresco/adf-core'; +import { mockPreferences, getMockPreference, createMockPreference, updateMockPreference } from '../mock/user-preference.mock'; + +describe('PreferenceService', () => { + let service: UserPreferenceCloudService; + let alfrescoApiMock: AlfrescoApiServiceMock; + let getInstanceSpy: jasmine.Spy; + + const errorResponse = { + error: 'Mock Error', + state: 404, stateText: 'Not Found' + }; + + function apiMock(mockResponse) { + return { + oauth2Auth: { + callCustomApi: () => { + return Promise.resolve(mockResponse); + } + } + }; + } + + const apiErrorMock = { + oauth2Auth: { + callCustomApi: () => Promise.reject(errorResponse) + } + }; + + setupTestBed({ + imports: [ + CoreModule.forRoot() + ], + providers: [ + UserPreferenceCloudService, AppConfigService, LogService, + { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock } + ] + }); + + beforeEach(async(() => { + service = TestBed.get(UserPreferenceCloudService); + alfrescoApiMock = TestBed.get(AlfrescoApiService); + service.contextRoot = 'http://{{domain}}.com'; + getInstanceSpy = spyOn(alfrescoApiMock, 'getInstance').and.returnValue(apiMock(mockPreferences)); + })); + + it('should create UserPreferenceCloudService instance', () => { + expect(service).toBeTruthy(); + }); + + it('should return the preferences', (done) => { + service.getPreferences('mock-app-name').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.list.entries.length).toBe(3); + expect(res.list.entries[0].entry.key).toBe('mock-preference-key-1'); + expect(res.list.entries[0].entry.value.length).toBe(2); + expect(res.list.entries[0].entry.value[0].username).toBe('mock-username-1'); + expect(res.list.entries[0].entry.value[0].firstName).toBe('mock-firstname-1'); + + expect(res.list.entries[1].entry.key).toBe('mock-preference-key-2'); + expect(res.list.entries[1].entry.value).toBe('my mock preference value'); + + expect(res.list.entries[2].entry.key).toBe('mock-preference-key-3'); + expect(res.list.entries[2].entry.value.appName).toBe('mock-appName'); + expect(res.list.entries[2].entry.value.state).toBe('MOCK-COMPLETED'); + done(); + }); + }); + + it('Should not fetch preferences if error occurred', () => { + getInstanceSpy.and.returnValue(apiErrorMock); + service.getPreferences('mock-app-name') + .subscribe( + (preferences) => fail('expected an error, not preferences'), + (error) => { + expect(error.state).toEqual(404); + expect(error.stateText).toEqual('Not Found'); + expect(error.error).toEqual('Mock Error'); + } + ); + }); + + it('should return the preference by key', (done) => { + getInstanceSpy.and.returnValue(apiMock(getMockPreference)); + service.getPreferenceByKey('mock-app-name', 'mock-preference-key').subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.length).toBe(2); + expect(res[0].appName).toBe('mock-appName'); + expect(res[0].firstName).toBe('mock-firstname-1'); + expect(res[1].appName).toBe('mock-appName'); + expect(res[1].username).toBe('mock-username-2'); + done(); + }); + }); + + it('Should not fetch preference by key if error occurred', () => { + getInstanceSpy.and.returnValue(apiErrorMock); + service.getPreferenceByKey('mock-app-name', 'mock-preference-key') + .subscribe( + (preference) => fail('expected an error, not preference'), + (error) => { + expect(error.state).toEqual(404); + expect(error.stateText).toEqual('Not Found'); + expect(error.error).toEqual('Mock Error'); + } + ); + }); + + it('should create preference', (done) => { + getInstanceSpy.and.returnValue(apiMock(createMockPreference)); + service.createPreference('mock-app-name', 'mock-preference-key', createMockPreference).subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res).toBe(createMockPreference); + expect(res.appName).toBe('mock-appName'); + expect(res.name).toBe('create-preference'); + done(); + }); + }); + + it('Should not create preference if error occurred', () => { + getInstanceSpy.and.returnValue(apiErrorMock); + service.createPreference('mock-app-name', 'mock-preference-key', createMockPreference) + .subscribe( + (preference) => fail('expected an error, not to create preference'), + (error) => { + expect(error.state).toEqual(404); + expect(error.stateText).toEqual('Not Found'); + expect(error.error).toEqual('Mock Error'); + } + ); + }); + + it('should update preference', (done) => { + getInstanceSpy.and.returnValue(apiMock(updateMockPreference)); + service.updatePreference('mock-app-name', 'mock-preference-key', updateMockPreference).subscribe((res: any) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res).toBe(updateMockPreference); + expect(res.appName).toBe('mock-appName'); + expect(res.name).toBe('update-preference'); + done(); + }); + }); + + it('Should not update preference if error occurred', () => { + getInstanceSpy.and.returnValue(apiErrorMock); + service.createPreference('mock-app-name', 'mock-preference-key', updateMockPreference) + .subscribe( + (preference) => fail('expected an error, not to update preference'), + (error) => { + expect(error.state).toEqual(404); + expect(error.stateText).toEqual('Not Found'); + expect(error.error).toEqual('Mock Error'); + } + ); + }); + + it('should delete preference', (done) => { + getInstanceSpy.and.returnValue(apiMock('')); + service.deletePreference('mock-app-name', 'mock-preference-key').subscribe((res: any) => { + expect(res).toBeDefined(); + done(); + }); + }); + + it('Should not delete preference if error occurred', () => { + getInstanceSpy.and.returnValue(apiErrorMock); + service.deletePreference('mock-app-name', 'mock-preference-key') + .subscribe( + (preference) => fail('expected an error, not to delete preference'), + (error) => { + expect(error.state).toEqual(404); + expect(error.stateText).toEqual('Not Found'); + expect(error.error).toEqual('Mock Error'); + } + ); + }); +}); diff --git a/lib/process-services-cloud/src/lib/services/user-preference.cloud.service.ts b/lib/process-services-cloud/src/lib/services/user-preference.cloud.service.ts new file mode 100644 index 0000000000..64079992e9 --- /dev/null +++ b/lib/process-services-cloud/src/lib/services/user-preference.cloud.service.ts @@ -0,0 +1,150 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable } from '@angular/core'; +import { BaseCloudService } from './base-cloud.service'; +import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core'; +import { from, throwError, Observable } from 'rxjs'; +import { catchError } from 'rxjs/operators'; + +@Injectable() +export class UserPreferenceCloudService extends BaseCloudService { + + contentTypes = ['application/json']; + accepts = ['application/json']; + + constructor( + private alfrescoApiService: AlfrescoApiService, + private appConfigService: AppConfigService, + private logService: LogService) { + super(); + } + + /** + * Gets user preferences + * @param appName Name of the target app + * @returns List of user preferences + */ + getPreferences(appName: string): Observable { + if (appName || appName === '') { + const uri = this.buildPreferenceServiceUri(appName); + return from(this.alfrescoApiService.getInstance() + .oauth2Auth.callCustomApi(uri, 'GET', + null, null, null, + null, null, this.contentTypes, + this.accepts, null, null) + ); + } else { + this.logService.error('Appname is mandatory for querying preferences'); + return throwError('Appname not configured'); + } + } + + /** + * Gets user preference. + * @param appName Name of the target app + * @param key Key of the target preference + * @returns Observable of user preferences + */ + getPreferenceByKey(appName: string, key: string): Observable { + if (appName || appName === '') { + const uri = this.buildPreferenceServiceUri(appName) + '/' + `${key}`; + return from( + this.alfrescoApiService.getInstance() + .oauth2Auth.callCustomApi(uri, 'GET', + null, null, null, + null, null, this.contentTypes, + this.accepts, null, null) + ).pipe(catchError((error) => throwError(error))); + } else { + this.logService.error('Appname and key are mandatory for querying preference'); + return throwError('Appname not configured'); + } + } + + /** + * Creates user preference. + * @param appName Name of the target app + * @param key Key of the target preference + * @newPreference Details of new user preference + * @returns Observable of created user preferences + */ + createPreference(appName: string, key: string, newPreference: any): Observable { + if (appName || appName === '') { + const uri = this.buildPreferenceServiceUri(appName) + '/' + `${key}`; + const requestPayload = JSON.stringify(newPreference); + return from(this.alfrescoApiService.getInstance() + .oauth2Auth.callCustomApi(uri, 'PUT', + null, null, + null, null, requestPayload, + this.contentTypes, this.accepts, + Object, null, null) + ).pipe( + catchError((err) => this.handleProcessError(err)) + ); + } else { + this.logService.error('Appname and key are mandatory for creating preference'); + return throwError('Appname not configured'); + } + } + + /** + * Updates user preference. + * @param appName Name of the target app + * @param key Key of the target preference + * @param updatedPreference Details of updated preference + * @returns Observable of updated user preferences + */ + updatePreference(appName: string, key: string, updatedPreference: any): Observable { + return this.createPreference(appName, key, updatedPreference); + } + + /** + * Deletes user preference by given preference key. + * @param appName Name of the target app + * @param key Key of the target preference + * @returns Observable of delete operation status + */ + deletePreference(appName: string, key: string): Observable { + if (appName || appName === '') { + const uri = this.buildPreferenceServiceUri(appName) + '/' + `${key}`; + return from(this.alfrescoApiService.getInstance() + .oauth2Auth.callCustomApi(uri, 'DELETE', + null, null, null, + null, null, this.contentTypes, + this.accepts, null, null, null) + ); + } else { + this.logService.error('Appname and key are mandatory to delete preference'); + return throwError('Appname not configured'); + } + } + + /** + * Creates preference uri + * @param appName Name of the target app + * @returns String of preference service uri + */ + private buildPreferenceServiceUri(appName: string): string { + this.contextRoot = this.appConfigService.get('bpmHost', ''); + return `${this.getBasePath(appName)}/preference/v1/preferences`; + } + + private handleProcessError(error: any) { + return throwError(error || 'Server error'); + } +}