[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
This commit is contained in:
siva kumar
2019-07-05 16:22:36 +05:30
committed by Eugenio Romano
parent 10673ef6f6
commit b88888b5e4
15 changed files with 1219 additions and 191 deletions

View File

@@ -68,7 +68,7 @@ export class CloudFiltersDemoComponent implements OnInit {
} }
onProcessFilterSelected(filter) { onProcessFilterSelected(filter) {
this.cloudLayoutService.setCurrentProcessFilterParam({id: filter.id}); this.cloudLayoutService.setCurrentProcessFilterParam({id: filter && filter.id ? filter.id : ''});
const currentFilter = Object.assign({}, filter); const currentFilter = Object.assign({}, filter);
this.router.navigate([`/cloud/${this.appName}/processes/`], { queryParams: currentFilter }); this.router.navigate([`/cloud/${this.appName}/processes/`], { queryParams: currentFilter });
} }

View File

@@ -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' }
];

View File

@@ -24,6 +24,7 @@ import { GroupCloudModule } from './group/group-cloud.module';
import { FormCloudModule } from './form/form-cloud.module'; import { FormCloudModule } from './form/form-cloud.module';
import { TaskFormModule } from './task/task-form/task-form.module'; import { TaskFormModule } from './task/task-form/task-form.module';
import { BaseCloudService } from './services/base-cloud.service'; import { BaseCloudService } from './services/base-cloud.service';
import { UserPreferenceCloudService } from './services/user-preference.cloud.service';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -44,7 +45,8 @@ import { BaseCloudService } from './services/base-cloud.service';
source: 'assets/adf-process-services-cloud' source: 'assets/adf-process-services-cloud'
} }
}, },
BaseCloudService BaseCloudService,
UserPreferenceCloudService
], ],
exports: [ exports: [
AppListCloudModule, AppListCloudModule,

View File

@@ -1,60 +1,69 @@
<mat-accordion *ngIf="processFilter"> <mat-accordion [hideToggle]="isLoading">
<mat-expansion-panel (afterExpand)="onExpand($event)" (closed)="onClose($event)"> <mat-expansion-panel (afterExpand)="onExpand($event)" (closed)="onClose($event)">
<mat-expansion-panel-header id="adf-edit-process-filter-expansion-header"> <mat-expansion-panel-header *ngIf="processFilter" id="adf-edit-process-filter-expansion-header">
<mat-panel-title fxLayoutAlign="space-between center" id="adf-edit-process-filter-title-id">{{processFilter.name | translate}}</mat-panel-title> <ng-container *ngIf="!isLoading; else loadingTemplate">
<mat-panel-description fxLayoutAlign="space-between center" id="adf-edit-process-filter-sub-title-id"> <mat-panel-title fxLayoutAlign="space-between center" id="adf-edit-process-filter-title-id">{{processFilter.name | translate}}</mat-panel-title>
<span *ngIf="showTitle"> {{ 'ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE' | translate}}</span> <mat-panel-description fxLayoutAlign="space-between center" id="adf-edit-process-filter-sub-title-id">
<div *ngIf="showActions()" class="adf-cloud-edit-process-filter-actions"> <span *ngIf="showTitle"> {{ 'ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE' | translate}}</span>
<ng-container *ngIf="toggleFilterActions"> <div *ngIf="showActions()" class="adf-cloud-edit-process-filter-actions">
<button *ngFor="let filterAction of processFilterActions" mat-icon-button matTooltip="{{ filterAction.tooltip | translate}}" [attr.data-automation-id]="'adf-filter-action-' + filterAction.actionType" [disabled]="hasFormChanged(filterAction)" (click)="executeFilterActions(filterAction)"> <ng-container *ngIf="toggleFilterActions">
<mat-icon>{{filterAction.icon}}</mat-icon> <button *ngFor="let filterAction of processFilterActions" mat-icon-button matTooltip="{{ filterAction.tooltip | translate}}" [attr.data-automation-id]="'adf-filter-action-' + filterAction.actionType" [disabled]="hasFormChanged(filterAction)" (click)="executeFilterActions(filterAction)">
</button> <mat-icon>{{filterAction.icon}}</mat-icon>
</button>
</ng-container>
</div>
</mat-panel-description>
</ng-container>
<ng-template #loadingTemplate>
<div class="adf-cloud-edit-process-filter-loading-margin">
<mat-spinner [diameter]="30"></mat-spinner>
</div>
</ng-template>
</mat-expansion-panel-header>
<ng-container *ngIf="!isLoading">
<form [formGroup]="editProcessFilterForm" *ngIf="editProcessFilterForm">
<div fxLayout="row wrap" fxLayout.xs="column" fxLayoutGap="10px" fxLayoutAlign="start center">
<ng-container *ngFor="let processFilterProperty of processFilterProperties">
<mat-form-field fxFlex="23%" *ngIf="isSelectType(processFilterProperty)" [attr.data-automation-id]="processFilterProperty.key">
<mat-select
placeholder="{{processFilterProperty.label | translate}}"
[formControlName]="processFilterProperty.key"
[attr.data-automation-id]="'adf-cloud-edit-process-property-' + processFilterProperty.key">
<mat-option *ngFor="let propertyOption of processFilterProperty.options" [value]="propertyOption.value" [attr.data-automation-id]="'adf-cloud-edit-process-property-options-' + processFilterProperty.key">
{{ propertyOption.label }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="23%" *ngIf="isTextType(processFilterProperty)" [attr.data-automation-id]="processFilterProperty.key">
<input matInput
[formControlName]="processFilterProperty.key"
type="text"
placeholder="{{processFilterProperty.label | translate}}"
[attr.data-automation-id]="'adf-cloud-edit-process-property-' + processFilterProperty.key"/>
</mat-form-field>
<mat-form-field fxFlex="23%" *ngIf="isDateType(processFilterProperty)" [attr.data-automation-id]="processFilterProperty.key">
<mat-label>{{processFilterProperty.label | translate}}</mat-label>
<input
matInput
(keyup)="onDateChanged($event.srcElement.value, processFilterProperty)"
(dateChange)="onDateChanged($event.value, processFilterProperty)"
[matDatepicker]="dateController"
placeholder="{{processFilterProperty.label | translate}}"
[(ngModel)]="dateFilter[processFilterProperty.key]"
[ngModelOptions]="{standalone: true}"
[attr.data-automation-id]="'adf-cloud-edit-process-property-' + processFilterProperty.key">
<mat-datepicker-toggle matSuffix [for]="dateController" [attr.data-automation-id]="'adf-cloud-edit-process-property-date-toggle-' + processFilterProperty.key"></mat-datepicker-toggle>
<mat-datepicker #dateController [attr.data-automation-id]="'adf-cloud-edit-process-property-date-picker-' + processFilterProperty.key"></mat-datepicker>
<div class="adf-edit-process-filter-date-error-container">
<div *ngIf="hasError(processFilterProperty)">
<div class="adf-error-text">{{'ADF_CLOUD_EDIT_PROCESS_FILTER.ERROR.DATE' | translate}}</div>
<mat-icon class="adf-error-icon">warning</mat-icon>
</div>
</div>
</mat-form-field>
</ng-container> </ng-container>
</div> </div>
</mat-panel-description> </form>
</mat-expansion-panel-header> </ng-container>
<form [formGroup]="editProcessFilterForm">
<div fxLayout="row wrap" fxLayout.xs="column" fxLayoutGap="10px" fxLayoutAlign="start center">
<ng-container *ngFor="let processFilterProperty of processFilterProperties">
<mat-form-field fxFlex="23%" *ngIf="isSelectType(processFilterProperty)" [attr.data-automation-id]="processFilterProperty.key">
<mat-select
placeholder="{{processFilterProperty.label | translate}}"
[formControlName]="processFilterProperty.key"
[attr.data-automation-id]="'adf-cloud-edit-process-property-' + processFilterProperty.key">
<mat-option *ngFor="let propertyOption of processFilterProperty.options" [value]="propertyOption.value" [attr.data-automation-id]="'adf-cloud-edit-process-property-options-' + processFilterProperty.key">
{{ propertyOption.label }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="23%" *ngIf="isTextType(processFilterProperty)" [attr.data-automation-id]="processFilterProperty.key">
<input matInput
[formControlName]="processFilterProperty.key"
type="text"
placeholder="{{processFilterProperty.label | translate}}"
[attr.data-automation-id]="'adf-cloud-edit-process-property-' + processFilterProperty.key"/>
</mat-form-field>
<mat-form-field fxFlex="23%" *ngIf="isDateType(processFilterProperty)" [attr.data-automation-id]="processFilterProperty.key">
<mat-label>{{processFilterProperty.label | translate}}</mat-label>
<input
matInput
(keyup)="onDateChanged($event.srcElement.value, processFilterProperty)"
(dateChange)="onDateChanged($event.value, processFilterProperty)"
[matDatepicker]="dateController"
placeholder="{{processFilterProperty.label | translate}}"
[(ngModel)]="dateFilter[processFilterProperty.key]"
[ngModelOptions]="{standalone: true}"
[attr.data-automation-id]="'adf-cloud-edit-process-property-' + processFilterProperty.key">
<mat-datepicker-toggle matSuffix [for]="dateController" [attr.data-automation-id]="'adf-cloud-edit-process-property-date-toggle-' + processFilterProperty.key"></mat-datepicker-toggle>
<mat-datepicker #dateController [attr.data-automation-id]="'adf-cloud-edit-process-property-date-picker-' + processFilterProperty.key"></mat-datepicker>
<div class="adf-edit-process-filter-date-error-container">
<div *ngIf="hasError(processFilterProperty)">
<div class="adf-error-text">{{'ADF_CLOUD_EDIT_PROCESS_FILTER.ERROR.DATE' | translate}}</div>
<mat-icon class="adf-error-icon">warning</mat-icon>
</div>
</div>
</mat-form-field>
</ng-container>
</div>
</form>
</mat-expansion-panel> </mat-expansion-panel>
</mat-accordion> </mat-accordion>

View File

@@ -28,4 +28,12 @@
color: mat-color($warn); color: mat-color($warn);
} }
} }
.adf {
&-cloud-edit-process-filter-loading-margin {
margin-left: calc((100% - 100px) / 2);
margin-right: calc((100% - 100px) / 2);
}
}
} }

View File

@@ -32,6 +32,7 @@ import { AppsProcessCloudService } from '../../../app/services/apps-process-clou
import { fakeApplicationInstance } from './../../../app/mock/app-model.mock'; import { fakeApplicationInstance } from './../../../app/mock/app-model.mock';
import moment from 'moment-es6'; import moment from 'moment-es6';
import { AbstractControl } from '@angular/forms'; import { AbstractControl } from '@angular/forms';
import { UserPreferenceCloudService } from '../../../services/user-preference.cloud.service';
describe('EditProcessFilterCloudComponent', () => { describe('EditProcessFilterCloudComponent', () => {
let component: EditProcessFilterCloudComponent; let component: EditProcessFilterCloudComponent;
@@ -55,7 +56,7 @@ describe('EditProcessFilterCloudComponent', () => {
setupTestBed({ setupTestBed({
imports: [ProcessServiceCloudTestingModule, ProcessFiltersCloudModule], imports: [ProcessServiceCloudTestingModule, ProcessFiltersCloudModule],
providers: [MatDialog] providers: [MatDialog, UserPreferenceCloudService]
}); });
beforeEach(() => { 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)); getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance));
fixture.detectChanges(); 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); const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true);
component.ngOnChanges({ 'id': processFilterIDchange }); component.ngOnChanges({ 'id': processFilterIDchange });
fixture.detectChanges(); fixture.detectChanges();
@@ -114,7 +115,37 @@ describe('EditProcessFilterCloudComponent', () => {
expect(title.innerText).toEqual('FakeRunningProcess'); expect(title.innerText).toEqual('FakeRunningProcess');
expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE'); 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', () => { describe('EditProcessFilter form', () => {
@@ -333,13 +364,13 @@ describe('EditProcessFilterCloudComponent', () => {
})); }));
it('should display sort properties when sort properties are specified', async(() => { it('should display sort properties when sort properties are specified', async(() => {
getProcessFilterByIdSpy.and.returnValue({ getProcessFilterByIdSpy.and.returnValue(of({
id: 'filter-id', id: 'filter-id',
processName: 'process-name', processName: 'process-name',
sort: 'my-custom-sort', sort: 'my-custom-sort',
processDefinitionId: 'process-definition-id', processDefinitionId: 'process-definition-id',
priority: '12' priority: '12'
}); }));
component.sortProperties = ['id', 'processName', 'processDefinitionId']; component.sortProperties = ['id', 'processName', 'processDefinitionId'];
fixture.detectChanges(); fixture.detectChanges();
const processFilterIdchange = new SimpleChange(null, 'mock-process-filter-id', true); const processFilterIdchange = new SimpleChange(null, 'mock-process-filter-id', true);
@@ -367,12 +398,13 @@ describe('EditProcessFilterCloudComponent', () => {
beforeEach(() => { beforeEach(() => {
const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true); const processFilterIDchange = new SimpleChange(null, 'mock-process-filter-id', true);
component.ngOnChanges({ 'id': processFilterIDchange }); component.ngOnChanges({ 'id': processFilterIDchange });
getProcessFilterByIdSpy.and.returnValue(of(fakeFilter));
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should emit save event and save the filter on click save button', async(() => { it('should emit save event and save the filter on click save button', async(() => {
component.toggleFilterActions = true; 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'); const saveSpy: jasmine.Spy = spyOn(component.action, 'emit');
fixture.detectChanges(); fixture.detectChanges();
@@ -395,7 +427,7 @@ describe('EditProcessFilterCloudComponent', () => {
it('should emit delete event and delete the filter on click of delete button', async(() => { it('should emit delete event and delete the filter on click of delete button', async(() => {
component.toggleFilterActions = true; 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'); const deleteSpy: jasmine.Spy = spyOn(component.action, 'emit');
fixture.detectChanges(); fixture.detectChanges();

View File

@@ -15,10 +15,11 @@
* limitations under the License. * 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 { FormGroup, FormBuilder, AbstractControl } from '@angular/forms';
import { MatDialog, DateAdapter } from '@angular/material'; 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-es6';
import { Moment } from 'moment'; import { Moment } from 'moment';
@@ -34,7 +35,7 @@ import { ProcessFilterDialogCloudComponent } from './process-filter-dialog-cloud
templateUrl: './edit-process-filter-cloud.component.html', templateUrl: './edit-process-filter-cloud.component.html',
styleUrls: ['./edit-process-filter-cloud.component.scss'] 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 = 'save';
public static ACTION_SAVE_AS = 'saveAs'; public static ACTION_SAVE_AS = 'saveAs';
@@ -106,6 +107,9 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges {
processFilterActions: ProcessFilterAction[] = []; processFilterActions: ProcessFilterAction[] = [];
toggleFilterActions: boolean = false; toggleFilterActions: boolean = false;
private onDestroy$ = new Subject<boolean>();
isLoading: boolean = false;
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
public dialog: MatDialog, public dialog: MatDialog,
@@ -124,9 +128,7 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges {
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
const id = changes['id']; const id = changes['id'];
if (id && id.currentValue !== id.previousValue) { if (id && id.currentValue !== id.previousValue) {
this.processFilterProperties = this.createAndFilterProperties(); this.retrieveProcessFilterAndBuildForm();
this.processFilterActions = this.createAndFilterActions();
this.buildForm(this.processFilterProperties);
} }
} }
@@ -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 { retrieveProcessFilterAndBuildForm() {
return new ProcessFilterCloudModel(this.processFilterCloudService.getProcessFilterById(this.appName, this.id)); 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.applicationNames = [];
this.getRunningApplications(); this.getRunningApplications();
} }
this.processFilter = this.retrieveProcessFilter();
const defaultProperties = this.createProcessFilterProperties(this.processFilter); const defaultProperties = this.createProcessFilterProperties(this.processFilter);
let filteredProperties = defaultProperties.filter((filterProperty: ProcessFilterProperties) => this.isValidProperty(this.filterProperties, filterProperty)); let filteredProperties = defaultProperties.filter((filterProperty: ProcessFilterProperties) => this.isValidProperty(this.filterProperties, filterProperty));
if (!this.hasSortProperty()) { if (!this.hasSortProperty()) {
@@ -182,7 +193,6 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges {
if (this.hasLastModifiedProperty()) { if (this.hasLastModifiedProperty()) {
filteredProperties = [...filteredProperties, ...this.createLastModifiedProperty()]; filteredProperties = [...filteredProperties, ...this.createLastModifiedProperty()];
} }
return filteredProperties; return filteredProperties;
} }
@@ -283,7 +293,7 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges {
getRunningApplications() { getRunningApplications() {
this.appsProcessCloudService.getDeployedApplicationsByStatus(EditProcessFilterCloudComponent.APP_RUNNING_STATUS) this.appsProcessCloudService.getDeployedApplicationsByStatus(EditProcessFilterCloudComponent.APP_RUNNING_STATUS)
.subscribe((applications: ApplicationInstanceModel[]) => { .pipe(takeUntil(this.onDestroy$)).subscribe((applications: ApplicationInstanceModel[]) => {
if (applications && applications.length > 0) { if (applications && applications.length > 0) {
applications.map((application) => { applications.map((application) => {
this.applicationNames.push({ label: application.name, value: application.name }); 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 a process instance filter
*/ */
save(saveAction: ProcessFilterAction) { save(saveAction: ProcessFilterAction) {
this.processFilterCloudService.updateFilter(this.changedProcessFilter); this.processFilterCloudService.updateFilter(this.changedProcessFilter)
saveAction.filter = this.changedProcessFilter; .pipe(takeUntil(this.onDestroy$)).subscribe((res) => {
this.action.emit(saveAction); saveAction.filter = this.changedProcessFilter;
this.formHasBeenChanged = this.compareFilters(this.changedProcessFilter, this.processFilter); this.action.emit(saveAction);
this.formHasBeenChanged = this.compareFilters(this.changedProcessFilter, this.processFilter);
});
} }
/** /**
* Delete a process instance filter * Delete a process instance filter
*/ */
delete(deleteAction: ProcessFilterAction) { delete(deleteAction: ProcessFilterAction) {
this.processFilterCloudService.deleteFilter(this.processFilter); this.processFilterCloudService.deleteFilter(this.processFilter)
deleteAction.filter = this.processFilter; .pipe(takeUntil(this.onDestroy$)).subscribe((res) => {
this.action.emit(deleteAction); deleteAction.filter = this.processFilter;
this.action.emit(deleteAction);
});
} }
/** /**
@@ -343,9 +357,11 @@ export class EditProcessFilterCloudComponent implements OnInit, OnChanges {
key: 'custom-' + filterKey key: 'custom-' + filterKey
}; };
const resultFilter: ProcessFilterCloudModel = Object.assign({}, this.changedProcessFilter, newFilter); const resultFilter: ProcessFilterCloudModel = Object.assign({}, this.changedProcessFilter, newFilter);
this.processFilterCloudService.addFilter(resultFilter); this.processFilterCloudService.addFilter(resultFilter)
saveAsAction.filter = resultFilter; .pipe(takeUntil(this.onDestroy$)).subscribe((res) => {
this.action.emit(saveAsAction); 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();
}
} }

View File

@@ -26,6 +26,7 @@ import { By } from '@angular/platform-browser';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { ProcessFiltersCloudModule } from '../process-filters-cloud.module'; import { ProcessFiltersCloudModule } from '../process-filters-cloud.module';
import { FilterParamsModel } from '../../../task/task-filters/models/filter-cloud.model'; import { FilterParamsModel } from '../../../task/task-filters/models/filter-cloud.model';
import { UserPreferenceCloudService } from '../../../services/user-preference.cloud.service';
describe('ProcessFiltersCloudComponent', () => { describe('ProcessFiltersCloudComponent', () => {
@@ -75,7 +76,7 @@ describe('ProcessFiltersCloudComponent', () => {
setupTestBed({ setupTestBed({
imports: [ProcessServiceCloudTestingModule, ProcessFiltersCloudModule], imports: [ProcessServiceCloudTestingModule, ProcessFiltersCloudModule],
providers: [ProcessFilterCloudService] providers: [ProcessFilterCloudService, UserPreferenceCloudService]
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -15,18 +15,20 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { ProcessFilterCloudService } from '../services/process-filter-cloud.service'; import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model'; import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
import { TranslationService } from '@alfresco/adf-core'; import { TranslationService } from '@alfresco/adf-core';
import { FilterParamsModel } from '../../../task/task-filters/models/filter-cloud.model'; import { FilterParamsModel } from '../../../task/task-filters/models/filter-cloud.model';
import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'adf-cloud-process-filters', selector: 'adf-cloud-process-filters',
templateUrl: './process-filters-cloud.component.html', templateUrl: './process-filters-cloud.component.html',
styleUrls: ['process-filters-cloud.component.scss'] styleUrls: ['process-filters-cloud.component.scss']
}) })
export class ProcessFiltersCloudComponent implements OnChanges { export class ProcessFiltersCloudComponent implements OnChanges, OnDestroy {
/** (required) The application name */ /** (required) The application name */
@Input() @Input()
@@ -58,6 +60,8 @@ export class ProcessFiltersCloudComponent implements OnChanges {
filters: ProcessFilterCloudModel [] = []; filters: ProcessFilterCloudModel [] = [];
private onDestroy$ = new Subject<boolean>();
constructor( constructor(
private processFilterCloudService: ProcessFilterCloudService, private processFilterCloudService: ProcessFilterCloudService,
private translationService: TranslationService ) { } private translationService: TranslationService ) { }
@@ -78,7 +82,7 @@ export class ProcessFiltersCloudComponent implements OnChanges {
getFilters(appName: string) { getFilters(appName: string) {
this.filters$ = this.processFilterCloudService.getProcessFilters(appName); this.filters$ = this.processFilterCloudService.getProcessFilters(appName);
this.filters$.subscribe( this.filters$.pipe(takeUntil(this.onDestroy$)).subscribe(
(res: ProcessFilterCloudModel[]) => { (res: ProcessFilterCloudModel[]) => {
this.resetFilter(); this.resetFilter();
this.filters = Object.assign([], res); this.filters = Object.assign([], res);
@@ -163,4 +167,9 @@ export class ProcessFiltersCloudComponent implements OnChanges {
this.filters = []; this.filters = [];
this.currentFilter = undefined; this.currentFilter = undefined;
} }
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
} }

View File

@@ -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'
}
];

View File

@@ -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();
});
});

View File

@@ -15,10 +15,12 @@
* limitations under the License. * 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 { 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 { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
import { UserPreferenceCloudService } from '../../../services/public-api';
import { switchMap, map, catchError } from 'rxjs/operators';
@Injectable() @Injectable()
export class ProcessFilterCloudService { export class ProcessFilterCloudService {
@@ -27,41 +29,43 @@ export class ProcessFilterCloudService {
filters$: Observable<ProcessFilterCloudModel[]>; filters$: Observable<ProcessFilterCloudModel[]>;
constructor( constructor(
private storage: StorageService, private preferenceService: UserPreferenceCloudService,
private identityUserService: IdentityUserService) { private identityUserService: IdentityUserService) {
this.filtersSubject = new BehaviorSubject([]); this.filtersSubject = new BehaviorSubject([]);
this.filters$ = this.filtersSubject.asObservable(); 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 * @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) { private createDefaultFilters(appName: string) {
const allProcessesFilter = this.getAllProcessesFilter(appName); const key: string = this.prepareKey(appName);
this.addFilter(allProcessesFilter); this.preferenceService.getPreferences(appName).pipe(
const runningProcessesFilter = this.getRunningProcessesFilter(appName); switchMap((response: any) => {
this.addFilter(runningProcessesFilter); const preferences = (response && response.list && response.list.entries) ? response.list.entries : [];
const completedProcessesFilter = this.getCompletedProcessesFilter(appName); if (!this.hasPreferences(preferences)) {
this.addFilter(completedProcessesFilter); 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. * Gets all process instance filters for a process app.
* @param appName Name of the target app * @param appName Name of the target app
* @returns Observable of process filter details * @returns Observable of process filters details
*/ */
getProcessFilters(appName: string): Observable<ProcessFilterCloudModel[]> { getProcessFilters(appName: string): Observable<ProcessFilterCloudModel[]> {
const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); this.createDefaultFilters(appName);
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);
}
return this.filters$; return this.filters$;
} }
@@ -69,119 +73,213 @@ export class ProcessFilterCloudService {
* Get process instance filter for given filter id * Get process instance filter for given filter id
* @param appName Name of the target app * @param appName Name of the target app
* @param id Id of the target process instance filter * @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 { getFilterById(appName: string, id: string): Observable<ProcessFilterCloudModel> {
const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); const key: string = this.prepareKey(appName);
const key = `process-filters-${appName}-${user.username}`; return this.getProcessFiltersByKey(appName, key).pipe(
let filters = []; switchMap((filters: ProcessFilterCloudModel[]) => {
filters = JSON.parse(this.storage.getItem(key)) || []; if (filters && filters.length === 0) {
return filters.filter((filterTmp: ProcessFilterCloudModel) => id === filterTmp.id)[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 * Adds a new process instance filter
* @param filter The new filter to add * @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) { addFilter(newFilter: ProcessFilterCloudModel): Observable<ProcessFilterCloudModel[]> {
const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); const key: string = this.prepareKey(newFilter.appName);
const key = `process-filters-${filter.appName}-${user.username}`; return this.getProcessFiltersByKey(newFilter.appName, key).pipe(
const storedFilters = JSON.parse(this.storage.getItem(key) || '[]'); switchMap((filters: ProcessFilterCloudModel[]) => {
if (filters && filters.length === 0) {
storedFilters.push(filter); return this.createProcessFilters(newFilter.appName, key, [newFilter]);
this.storage.setItem(key, JSON.stringify(storedFilters)); } else {
filters.push(newFilter);
this.addFiltersToStream(storedFilters); 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 * Update process instance filter
* @param filter The new filter to update * @param filter The new filter to update
* @returns Observable of process instance filters with updated filter
*/ */
updateFilter(filter: ProcessFilterCloudModel) { updateFilter(updatedFilter: ProcessFilterCloudModel): Observable<ProcessFilterCloudModel[]> {
const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); const key: string = this.prepareKey(updatedFilter.appName);
const key = `process-filters-${filter.appName}-${user.username}`; return this.getProcessFiltersByKey(updatedFilter.appName, key).pipe(
if (key) { switchMap((filters: any) => {
const filters = JSON.parse(this.storage.getItem(key) || '[]'); if (filters && filters.length === 0) {
const itemIndex = filters.findIndex((flt: ProcessFilterCloudModel) => flt.id === filter.id); return this.createProcessFilters(updatedFilter.appName, key, [updatedFilter]);
filters[itemIndex] = filter; } else {
this.storage.setItem(key, JSON.stringify(filters)); const itemIndex = filters.findIndex((filter: ProcessFilterCloudModel) => filter.id === updatedFilter.id);
this.addFiltersToStream(filters); 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 * Delete process instance filter
* @param filter The new filter to delete * @param filter The new filter to delete
* @returns Observable of process instance filters without deleted filter
*/ */
deleteFilter(filter: ProcessFilterCloudModel) { deleteFilter(deletedFilter: ProcessFilterCloudModel): Observable<ProcessFilterCloudModel[]> {
const user: IdentityUserModel = this.identityUserService.getCurrentUserInfo(); const key = this.prepareKey(deletedFilter.appName);
const key = `process-filters-${filter.appName}-${user.username}`; return this.getProcessFiltersByKey(deletedFilter.appName, key).pipe(
if (key) { switchMap((filters: any) => {
let filters = JSON.parse(this.storage.getItem(key) || '[]'); if (filters && filters.length > 0) {
filters = filters.filter((item) => item.id !== filter.id); filters = filters.filter((filter: ProcessFilterCloudModel) => filter.id !== deletedFilter.id);
this.storage.setItem(key, JSON.stringify(filters)); return this.updateProcessFilters(deletedFilter.appName, key, filters);
if (filters.length === 0) { }
this.createDefaultFilters(filter.appName); }),
} else { map((filters: ProcessFilterCloudModel[]) => {
this.addFiltersToStream(filters); this.addFiltersToStream(filters);
} return filters;
} }),
catchError((err) => this.handleProcessError(err))
);
} }
/** /**
* Creates and returns a filter for "All" Process instances. * Checks user preference are empty or not
* @param appName Name of the target app * @param preferences User preferences of the target app
* @returns The newly created filter * @returns Boolean value if the preferences are not empty
*/ */
getAllProcessesFilter(appName: string): ProcessFilterCloudModel { private hasPreferences(preferences: any): boolean {
return new ProcessFilterCloudModel({ return preferences && preferences.length > 0;
name: 'ADF_CLOUD_PROCESS_FILTERS.ALL_PROCESSES',
key: 'all-processes',
icon: 'adjust',
appName: appName,
sort: 'startDate',
status: '',
order: 'DESC'
});
} }
/** /**
* Creates and returns a filter for "Running" Process instances. * Checks for process instance filters in given user preferences
* @param appName Name of the target app * @param preferences User preferences of the target app
* @returns The newly created filter * @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 { private hasProcessFilters(preferences: any, key: string): boolean {
return new ProcessFilterCloudModel({ const filters = preferences.find((filter: any) => { return filter.entry.key === key; });
name: 'ADF_CLOUD_PROCESS_FILTERS.RUNNING_PROCESSES', return (filters && filters.entry) ? JSON.parse(filters.entry.value).length > 0 : false;
icon: 'inbox',
key: 'running-processes',
appName: appName,
sort: 'startDate',
status: 'RUNNING',
order: 'DESC'
});
} }
/** /**
* 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 * @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 { private createProcessFilters(appName: string, key: string, filters: ProcessFilterCloudModel[]): Observable<ProcessFilterCloudModel[]> {
return new ProcessFilterCloudModel({ return this.preferenceService.createPreference(appName, key, filters);
name: 'ADF_CLOUD_PROCESS_FILTERS.COMPLETED_PROCESSES',
icon: 'done',
key: 'completed-processes',
appName: appName,
sort: 'startDate',
status: 'COMPLETED',
order: 'DESC'
});
} }
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<ProcessFilterCloudModel[]> {
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<ProcessFilterCloudModel[]> {
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); 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'
})
];
}
} }

View File

@@ -16,3 +16,4 @@
*/ */
export * from './identity-user.service'; export * from './identity-user.service';
export * from './user-preference.cloud.service';

View File

@@ -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');
}
);
});
});

View File

@@ -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<any> {
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<any> {
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<any> {
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<any> {
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<any> {
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');
}
}