[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) {
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 });
}

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 { 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,

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-header 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>
<mat-panel-description fxLayoutAlign="space-between center" id="adf-edit-process-filter-sub-title-id">
<span *ngIf="showTitle"> {{ 'ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE' | translate}}</span>
<div *ngIf="showActions()" class="adf-cloud-edit-process-filter-actions">
<ng-container *ngIf="toggleFilterActions">
<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)">
<mat-icon>{{filterAction.icon}}</mat-icon>
</button>
<mat-expansion-panel-header *ngIf="processFilter" id="adf-edit-process-filter-expansion-header">
<ng-container *ngIf="!isLoading; else loadingTemplate">
<mat-panel-title fxLayoutAlign="space-between center" id="adf-edit-process-filter-title-id">{{processFilter.name | translate}}</mat-panel-title>
<mat-panel-description fxLayoutAlign="space-between center" id="adf-edit-process-filter-sub-title-id">
<span *ngIf="showTitle"> {{ 'ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE' | translate}}</span>
<div *ngIf="showActions()" class="adf-cloud-edit-process-filter-actions">
<ng-container *ngIf="toggleFilterActions">
<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)">
<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>
</div>
</mat-panel-description>
</mat-expansion-panel-header>
<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>
</form>
</ng-container>
</mat-expansion-panel>
</mat-accordion>

View File

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

View File

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

View File

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

View File

@@ -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(() => {

View File

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

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.
*/
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<ProcessFilterCloudModel[]>;
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<ProcessFilterCloudModel[]> {
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<ProcessFilterCloudModel> {
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<ProcessFilterCloudModel[]> {
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<ProcessFilterCloudModel[]> {
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<ProcessFilterCloudModel[]> {
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<ProcessFilterCloudModel[]> {
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<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);
}
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 './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');
}
}