[ADF-3749] Process Filter Component - APS2 (#3998)

* [ADF-3749] Created new process filter component

* [ADF-3749] Improved process filters

* [ADF-3749] Improved process filters

* [ADF-3749] Added tests

* [ADF-3749] Included filter in process list cloud demo

* [ADF-3749] Added documentation

* [ADF-3749] Improved documentation

* [ADF-3749] Added new query model and improved model names

* [][ADF-3749] Added extra documentation

* [ADF-3749] Added new key in the filter models

* [ADF-3749] Added translation support for filters

* [ADF-3749] Added new tests

* [ADF-3749] Added new translation keys
This commit is contained in:
Deepak Paul 2018-11-28 02:44:48 +05:30 committed by Eugenio Romano
parent 86ae2f5c2b
commit 1387aff0f4
19 changed files with 1068 additions and 69 deletions

View File

@ -62,6 +62,7 @@
"TASK_LIST": "Task List", "TASK_LIST": "Task List",
"PROCESS_LIST": "Process List", "PROCESS_LIST": "Process List",
"PROCESS_CLOUD": "Process Cloud", "PROCESS_CLOUD": "Process Cloud",
"PROCESS_LIST_CLOUD": "Process List Cloud",
"CARD_VIEW": "CardView", "CARD_VIEW": "CardView",
"PROCESS_SERVICES": "Process Services", "PROCESS_SERVICES": "Process Services",
"LOGIN": "Login", "LOGIN": "Login",
@ -269,5 +270,8 @@
"TEXT": "Back to home" "TEXT": "Back to home"
} }
} }
},
"PROCESS_LIST_CLOUD": {
"TITLE": "PROCESS LIST CLOUD DEMO"
} }
} }

View File

@ -150,7 +150,7 @@ export const appRoutes: Routes = [
] ]
}, },
{ {
path: 'process-cloud', path: 'process-list-cloud',
component: ProcessListCloudExampleComponent component: ProcessListCloudExampleComponent
}, },
{ {

View File

@ -41,6 +41,7 @@ export class AppLayoutComponent implements OnInit {
{ href: '/task-list', icon: 'assignment', title: 'APP_LAYOUT.TASK_LIST' }, { href: '/task-list', icon: 'assignment', title: 'APP_LAYOUT.TASK_LIST' },
{ href: '/process-list', icon: 'assignment', title: 'APP_LAYOUT.PROCESS_LIST' }, { href: '/process-list', icon: 'assignment', title: 'APP_LAYOUT.PROCESS_LIST' },
{ href: '/cloud', icon: 'cloud', title: 'APP_LAYOUT.PROCESS_CLOUD' }, { href: '/cloud', icon: 'cloud', title: 'APP_LAYOUT.PROCESS_CLOUD' },
{ href: '/process-list-cloud', icon: 'cloud', title: 'APP_LAYOUT.PROCESS_LIST_CLOUD' },
{ href: '/activiti', icon: 'device_hub', title: 'APP_LAYOUT.PROCESS_SERVICES' }, { href: '/activiti', icon: 'device_hub', title: 'APP_LAYOUT.PROCESS_SERVICES' },
{ href: '/login', icon: 'vpn_key', title: 'APP_LAYOUT.LOGIN' }, { href: '/login', icon: 'vpn_key', title: 'APP_LAYOUT.LOGIN' },
{ href: '/trashcan', icon: 'delete', title: 'APP_LAYOUT.TRASHCAN' }, { href: '/trashcan', icon: 'delete', title: 'APP_LAYOUT.TRASHCAN' },

View File

@ -1,4 +1,11 @@
<div>PROCESS LIST CLOUD</div> <h3>{{'PROCESS_LIST_CLOUD.TITLE' | translate}}</h3>
<adf-cloud-process-filters
[appName]="currentAppName"
[showIcons]="true"
(filterClick)="onFilterSelected($event)"
*ngIf="currentAppName">
</adf-cloud-process-filters>
<adf-cloud-app-list *ngIf="!currentAppName" <adf-cloud-app-list *ngIf="!currentAppName"
(appClick)="onAppClick($event)"></adf-cloud-app-list> (appClick)="onAppClick($event)"></adf-cloud-app-list>
<div *ngIf="currentAppName"> <div *ngIf="currentAppName">
@ -6,13 +13,13 @@
<mat-expansion-panel> <mat-expansion-panel>
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-panel-title> <mat-panel-title>
Process Example Filters {{filterName | translate}}
</mat-panel-title> </mat-panel-title>
<mat-panel-description> <mat-panel-description>
Apply one of the filters to the process list Customise your filter
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div class="app-process-cloud-spacing"> <div>
<mat-form-field> <mat-form-field>
<mat-select placeholder="Status" [(ngModel)]="status"> <mat-select placeholder="Status" [(ngModel)]="status">
<mat-option value=""> <mat-option value="">
@ -21,60 +28,32 @@
<mat-option value="RUNNING"> <mat-option value="RUNNING">
RUNNING RUNNING
</mat-option> </mat-option>
<mat-option value="SUSPENDED"> <mat-option value="COMPLETED">
SUSPENDED COMPLETED
</mat-option> </mat-option>
<mat-option value="CANCELLED"> </mat-select>
CANCELLED </mat-form-field>
<mat-form-field>
<mat-select [formControl]="sortFormControl">
<mat-option [value]="''">Select a column</mat-option>
<mat-option *ngFor="let column of columns" [value]="column.key">
{{column.label}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select [formControl]="sortDirectionFormControl">
<mat-option [value]="''">Select a direction</mat-option>
<mat-option value="ASC">
ASC
</mat-option>
<mat-option value="DESC">
DESC
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="app-process-cloud-spacing">
<mat-form-field class="example-full-width">
<input matInput placeholder="Filter by id" [(ngModel)]="filterId">
</mat-form-field>
</div>
</mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Sorting Panel
</mat-panel-title>
<mat-panel-description>
Choose how to sort your tasks
</mat-panel-description>
</mat-expansion-panel-header>
<div class="task-cloud-demo-select">
<mat-form-field>
<mat-select placeholder="Sort Field" [(ngModel)]="sortField">
<mat-option value="id">
ID
</mat-option>
<mat-option value="name">
NAME
</mat-option>
<mat-option value="status">
STATUS
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="task-cloud-demo-select">
<mat-form-field>
<mat-select placeholder="Direction" [(ngModel)]="sortDirection">
<mat-option value="ASC">
ASC
</mat-option>
<mat-option value="DESC">
DESC
</mat-option>
</mat-select>
</mat-form-field>
</div>
<button mat-button (click)="onFilterButtonClick($event)">Apply Filter</button>
<button mat-button (click)="onClearFilters()">Clear Filter</button>
</mat-expansion-panel>
</mat-accordion> </mat-accordion>
<adf-cloud-process-list <adf-cloud-process-list
[applicationName]="currentAppName" [applicationName]="currentAppName"

View File

@ -5,3 +5,7 @@
.app-process-cloud-spacing { .app-process-cloud-spacing {
margin: 10px; margin: 10px;
} }
mat-form-field {
margin-left: 10px;
}

View File

@ -15,30 +15,70 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild, OnInit } from '@angular/core';
import { UserPreferencesService } from '@alfresco/adf-core'; import { UserPreferencesService } from '@alfresco/adf-core';
import { ProcessListCloudComponent } from '@alfresco/adf-process-services-cloud'; import { ProcessListCloudComponent } from '@alfresco/adf-process-services-cloud';
import { FormControl } from '@angular/forms';
@Component({ @Component({
selector: 'app-process-list-example', selector: 'app-process-list-example',
templateUrl: './process-list-cloud-example.component.html', templateUrl: './process-list-cloud-example.component.html',
styleUrls: ['./process-list-cloud-example.component.scss'] styleUrls: ['./process-list-cloud-example.component.scss']
}) })
export class ProcessListCloudExampleComponent { export class ProcessListCloudExampleComponent implements OnInit {
@ViewChild('processCloud') @ViewChild('processCloud')
processCloud: ProcessListCloudComponent; processCloud: ProcessListCloudComponent;
sortFormControl: FormControl;
sortDirectionFormControl: FormControl;
currentAppName: string = ''; currentAppName: string = '';
filterName: string = '';
status: string = ''; status: string = '';
filterId: string = ''; filterId: string = '';
sortArray: any = []; sort: string = '';
sortArray: any[];
sortField: string; sortField: string;
sortDirection: string; sortDirection: string;
columns = [
{key: 'id', label: 'ID'},
{key: 'name', label: 'NAME'},
{key: 'status', label: 'STATUS'},
{key: 'startDate', label: 'START DATE'}
];
constructor(private userPreference: UserPreferencesService) { constructor(private userPreference: UserPreferencesService) {
} }
ngOnInit() {
this.sortFormControl = new FormControl('');
this.sortFormControl.valueChanges.subscribe(
(sortValue) => {
this.sort = sortValue;
this.sortArray = [{
orderBy: this.sort,
direction: this.sortDirection
}];
}
);
this.sortDirectionFormControl = new FormControl('');
this.sortDirectionFormControl.valueChanges.subscribe(
(sortDirectionValue) => {
this.sortDirection = sortDirectionValue;
this.sortArray = [{
orderBy: this.sort,
direction: this.sortDirection
}];
}
);
}
onAppClick(appClicked: any) { onAppClick(appClicked: any) {
this.currentAppName = appClicked.name; this.currentAppName = appClicked.name;
} }
@ -51,16 +91,16 @@ export class ProcessListCloudExampleComponent {
this.userPreference.paginationSize = event.maxItems; this.userPreference.paginationSize = event.maxItems;
} }
onFilterButtonClick($event) { onClearFilters() {
let newSortParam: any = {
orderBy: this.sortField,
direction: this.sortDirection };
this.sortArray.push(newSortParam);
this.processCloud.reload(); this.processCloud.reload();
} }
onClearFilters() { onFilterSelected(filter) {
this.sortArray = []; this.status = filter.query.state || '';
this.processCloud.reload(); this.sort = filter.query.sort;
this.sortDirection = filter.query.order;
this.filterName = filter.name;
this.sortDirectionFormControl.setValue(this.sortDirection);
this.sortFormControl.setValue(this.sort);
} }
} }

View File

@ -0,0 +1,63 @@
---
Added: v3.0.0
Status: Active
Last reviewed: 2018-21-11
---
# Process Filter Cloud Component
Lists all available process filters and allows to select a filter.
## Contents
- [Basic Usage](#basic-usage)
- [Class members](#class-members)
- [Properties](#properties)
- [Events](#events)
## Basic Usage
```html
<adf-cloud-process-filters
[appName]="currentAppName"
[showIcons]="true">
</adf-cloud-process-filters>
```
## Class members
### Properties
| Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- |
| appName | `string` | | (required) The application name |
| filterParam | `ProcessFilterParamModel` | | (optional) The filter to be selected by default |
| showIcons | `boolean` | false | (optional) The flag hides/shows icon against each filter |
### Events
| Name | Type | Description |
| ---- | ---- | ----------- |
| filterClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`ProcessFilterRepresentationModel`](../../lib/process-services-cloud/src/lib/process-cloud/models/process-filter-cloud.model.ts)`>` | Emitted when a filter is selected/clicked. |
| success | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when filters are loaded successfully. |
| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<any>` | Emitted when any error occurs while loading the filters. |
### Details
The `filterParam` input can be used to select a filter as mentioned below.
```html
<adf-cloud-process-filters
[filterParam]="{name:'Running Processes'}">
</adf-cloud-process-filters>
```
A filter can be selected by using any of the `ProcessFilterParamModel` property.
| Name | Type | Description |
| ---- | ---- | ----------- |
| id | string | The id of the filter |
| name | string | The name of the filter |
| key | string | The key of the filter |
| index | string | The zero-based position of the filter in the array |

View File

@ -9,5 +9,10 @@
"ADF_CLOUD_TASK_FILTERS": { "ADF_CLOUD_TASK_FILTERS": {
"MY_TASKS": "My Tasks", "MY_TASKS": "My Tasks",
"COMPLETED_TASKS": "Completed Tasks" "COMPLETED_TASKS": "Completed Tasks"
},
"ADF_CLOUD_PROCESS_FILTERS": {
"ALL_PROCESSES": "All Processes",
"RUNNING_PROCESSES": "Running Processes",
"COMPLETED_PROCESSES": "Completed Processes"
} }
} }

View File

@ -1,5 +1,5 @@
{ {
"ADF_CLOUD_TASK_PROCESS_LIST": { "ADF_CLOUD_PROCESS_LIST": {
"MESSAGES": { "MESSAGES": {
"TITLE": "Nessun processo trovato", "TITLE": "Nessun processo trovato",
"SUBTITLE":"Crea un nuovo processo", "SUBTITLE":"Crea un nuovo processo",

View File

@ -0,0 +1,71 @@
/*!
* @license
* Copyright 2016 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 class ProcessQueryModel {
processDefinitionId: string;
appName: string;
state: string;
sort: string;
assignment: string;
order: string;
constructor(obj?: any) {
if (obj) {
this.appName = obj.appName || null;
this.processDefinitionId = obj.processDefinitionId || null;
this.state = obj.state || null;
this.sort = obj.sort || null;
this.assignment = obj.assignment || null;
this.order = obj.order || null;
}
}
}
export class ProcessFilterRepresentationModel {
id: string;
name: string;
key: string;
icon: string;
query: ProcessQueryModel;
constructor(obj?: any) {
if (obj) {
this.id = obj.id || Math.random().toString(36).substring(2, 9);
this.name = obj.name || null;
this.key = obj.key || null;
this.icon = obj.icon || null;
this.query = new ProcessQueryModel(obj.query);
}
}
hasFilter() {
return !!this.query;
}
}
export class ProcessFilterParamModel {
id: string;
name: string;
key: string;
index: number;
constructor(obj?: any) {
if (obj) {
this.id = obj.id || null;
this.name = obj.name || null;
this.key = obj.key || null;
this.index = obj.index || null;
}
}
}

View File

@ -0,0 +1,43 @@
/*!
* @license
* Copyright 2016 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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProcessFiltersCloudComponent } from './process-filters-cloud/process-filters-cloud.component';
import { MaterialModule } from '../material.module';
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateLoaderService, LogService, StorageService } from '@alfresco/adf-core';
import { ProcessFilterCloudService } from './services/process-filter-cloud.service';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
HttpClientModule,
CommonModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderService
}
}),
MaterialModule
],
declarations: [ProcessFiltersCloudComponent],
exports: [ProcessFiltersCloudComponent],
providers: [ProcessFilterCloudService, LogService, StorageService]
})
export class ProcessCloudModule { }

View File

@ -0,0 +1,17 @@
<div class="menu-container">
<mat-list class="adf-menu-list" *ngIf="filters$ | async as filterList; else loading">
<mat-list-item (click)="selectFilterById(filter.id)" *ngFor="let filter of filterList"
class="adf-filters__entry" [class.active]="currentFilter === filter">
<mat-icon *ngIf="showIcons && filter.icon" matListIcon class="adf-filters__entry-icon">{{filter.icon}}
</mat-icon>
<span matLine [attr.data-automation-id]="filter.name + '_filter'">{{filter.name | translate}}</span>
</mat-list-item>
</mat-list>
<ng-template #loading>
<ng-container>
<div class="adf-app-list-spinner">
<mat-spinner></mat-spinner>
</div>
</ng-container>
</ng-template>
</div>

View File

@ -0,0 +1,34 @@
@mixin adf-cloud-process-filters-theme($theme) {
$primary: map-get($theme, primary);
.adf {
&-filters__entry {
cursor: pointer;
font-size: 14px!important;
font-weight: bold;
opacity: .54;
padding-left: 30px;
.mat-list-item-content {
height: 34px;
}
}
&-filters__entry-icon {
padding-right: 12px !important;
padding-left: 0px !important;
}
&-filters__entry {
&.active, &:hover {
color: mat-color($primary);
opacity: 1;
}
}
&-menu-list {
padding-top: 0px!important;
}
}
}

View File

@ -0,0 +1,375 @@
/*!
* @license
* Copyright 2016 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 { SimpleChange } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { from, Observable } from 'rxjs';
import { ProcessFilterRepresentationModel, ProcessFilterParamModel } from '../models/process-filter-cloud.model';
import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
import { ProcessFiltersCloudComponent } from './process-filters-cloud.component';
import { By } from '@angular/platform-browser';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { ProcessCloudModule } from '../process-cloud.module';
describe('ProcessFiltersCloudComponent', () => {
let processFilterService: ProcessFilterCloudService;
let fakeGlobalFilter = [
new ProcessFilterRepresentationModel({
name: 'FakeAllProcesses',
icon: 'adjust',
id: '10',
query: {state: ''}
}),
new ProcessFilterRepresentationModel({
name: 'FakeRunningProcesses',
icon: 'inbox',
id: '11',
query: {state: 'RUNNING'}
}),
new ProcessFilterRepresentationModel({
name: 'FakeCompletedProcesses',
key: 'completed-processes',
icon: 'done',
id: '12',
query: {state: 'COMPLETED'}
})
];
let fakeGlobalFilterObservable =
new Observable(function(observer) {
observer.next(fakeGlobalFilter);
observer.complete();
});
let fakeGlobalFilterPromise = new Promise(function (resolve, reject) {
resolve(fakeGlobalFilter);
});
let fakeGlobalEmptyFilter = {
message: 'invalid data'
};
let fakeGlobalEmptyFilterPromise = new Promise(function (resolve, reject) {
resolve(fakeGlobalEmptyFilter);
});
let mockErrorFilterList = {
error: 'wrong request'
};
let mockErrorFilterPromise = Promise.reject(mockErrorFilterList);
let component: ProcessFiltersCloudComponent;
let fixture: ComponentFixture<ProcessFiltersCloudComponent>;
setupTestBed({
imports: [ProcessServiceCloudTestingModule, ProcessCloudModule],
providers: [ProcessFilterCloudService]
});
beforeEach(() => {
fixture = TestBed.createComponent(ProcessFiltersCloudComponent);
component = fixture.componentInstance;
processFilterService = TestBed.get(ProcessFilterCloudService);
});
it('should attach specific icon for each filter if hasIcon is true', async(() => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(fakeGlobalFilterObservable);
let change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({'appName': change});
fixture.detectChanges();
component.showIcons = true;
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(component.filters.length).toBe(3);
let filters = fixture.nativeElement.querySelectorAll('.adf-filters__entry-icon');
expect(filters.length).toBe(3);
expect(filters[0].innerText).toContain('adjust');
expect(filters[1].innerText).toContain('inbox');
expect(filters[2].innerText).toContain('done');
});
}));
it('should not attach icons for each filter if hasIcon is false', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(from(fakeGlobalFilterPromise));
component.showIcons = false;
let change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({'appName': change});
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
let filters: any = fixture.debugElement.queryAll(By.css('.adf-filters__entry-icon'));
expect(filters.length).toBe(0);
done();
});
});
it('should display the filters', async(() => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(fakeGlobalFilterObservable);
let change = new SimpleChange(undefined, 'my-app-1', true);
component.ngOnChanges({'appName': change});
fixture.detectChanges();
component.showIcons = true;
fixture.whenStable().then(() => {
fixture.detectChanges();
let filters = fixture.debugElement.queryAll(By.css('mat-list-item[class*="adf-filters__entry"]'));
expect(component.filters.length).toBe(3);
expect(filters.length).toBe(3);
expect(filters[0].nativeElement.innerText).toContain('FakeAllProcesses');
expect(filters[1].nativeElement.innerText).toContain('FakeRunningProcesses');
expect(filters[2].nativeElement.innerText).toContain('FakeCompletedProcesses');
});
}));
it('should emit an error with a bad response', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(from(mockErrorFilterPromise));
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
component.ngOnChanges({'appName': change});
component.error.subscribe((err) => {
expect(err).toBeDefined();
done();
});
});
it('should emit success with the filters when filters are loaded', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(from(fakeGlobalFilterPromise));
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
component.ngOnChanges({ 'appName': change });
component.success.subscribe((res) => {
expect(res).toBeDefined();
expect(component.filters).toBeDefined();
expect(component.filters[0].name).toEqual('FakeAllProcesses');
expect(component.filters[1].name).toEqual('FakeRunningProcesses');
expect(component.filters[2].name).toEqual('FakeCompletedProcesses');
done();
});
});
it('should select the first filter as default', async(() => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(fakeGlobalFilterObservable);
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
fixture.detectChanges();
component.ngOnChanges({ 'appName': change });
component.success.subscribe((res) => {
expect(res).toBeDefined();
expect(component.currentFilter).toBeDefined();
expect(component.currentFilter.name).toEqual('FakeAllProcesses');
});
}));
it('should be able to fetch and select the default filters if the input filter is not valid', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(from(fakeGlobalEmptyFilterPromise));
spyOn(component, 'createFilters').and.callThrough();
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
component.ngOnChanges({ 'appName': change });
component.success.subscribe((res) => {
expect(res).toBeDefined();
expect(component.createFilters).not.toHaveBeenCalled();
done();
});
});
it('should select the filter based on the input by name param', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(fakeGlobalFilterObservable);
component.filterParam = new ProcessFilterParamModel({ name: 'FakeRunningProcesses' });
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
component.filterClick.subscribe((res) => {
expect(res).toBeDefined();
expect(component.currentFilter).toBeDefined();
expect(component.currentFilter.name).toEqual('FakeRunningProcesses');
done();
});
fixture.detectChanges();
component.ngOnChanges({ 'appName': change });
});
it('should select the filter based on the input by key param', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(fakeGlobalFilterObservable);
component.filterParam = new ProcessFilterParamModel({ key: 'completed-processes' });
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
fixture.detectChanges();
component.filterClick.subscribe((res) => {
expect(res).toBeDefined();
expect(component.currentFilter).toBeDefined();
expect(component.currentFilter.name).toEqual('FakeCompletedProcesses');
done();
});
component.ngOnChanges({ 'appName': change });
});
it('should select the default filter if filter input does not exist', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(fakeGlobalFilterObservable);
component.filterParam = new ProcessFilterParamModel({ name: 'UnexistableFilter' });
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
fixture.detectChanges();
component.filterClick.subscribe((res) => {
expect(res).toBeDefined();
expect(component.currentFilter).toBeDefined();
expect(component.currentFilter.name).toEqual('FakeAllProcesses');
done();
});
component.ngOnChanges({ 'appName': change });
});
it('should select the filter based on the input by index param', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(fakeGlobalFilterObservable);
component.filterParam = new ProcessFilterParamModel({ index: 2 });
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
fixture.detectChanges();
component.filterClick.subscribe((res) => {
expect(res).toBeDefined();
expect(component.currentFilter).toBeDefined();
expect(component.currentFilter.name).toEqual('FakeCompletedProcesses');
done();
});
component.ngOnChanges({ 'appName': change });
});
it('should select the filter based on the input by id param', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(fakeGlobalFilterObservable);
component.filterParam = new ProcessFilterParamModel({ id: '12' });
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
fixture.detectChanges();
component.filterClick.subscribe((res) => {
expect(res).toBeDefined();
expect(component.currentFilter).toBeDefined();
expect(component.currentFilter.name).toEqual('FakeCompletedProcesses');
done();
});
component.ngOnChanges({ 'appName': change });
});
it('should emit an event when a filter is selected', (done) => {
spyOn(processFilterService, 'getProcessFilters').and.returnValue(fakeGlobalFilterObservable);
component.filterParam = new ProcessFilterParamModel({ id: '10' });
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
component.ngOnChanges({ 'appName': change });
fixture.detectChanges();
component.filterClick.subscribe((res) => {
expect(res).toBeDefined();
expect(component.currentFilter).toBeDefined();
expect(component.currentFilter.name).toEqual('FakeRunningProcesses');
done();
});
let filterButton = fixture.debugElement.nativeElement.querySelector('span[data-automation-id="FakeRunningProcesses_filter"]');
filterButton.click();
});
it('should reload filters by appName on binding changes', () => {
spyOn(component, 'getFilters').and.stub();
const appName = 'my-app-1';
let change = new SimpleChange(null, appName, true);
component.ngOnChanges({ 'appName': change });
expect(component.getFilters).toHaveBeenCalledWith(appName);
});
it('should not reload filters by appName null on binding changes', () => {
spyOn(component, 'getFilters').and.stub();
const appName = null;
let change = new SimpleChange(undefined, appName, true);
component.ngOnChanges({ 'appName': change });
expect(component.getFilters).not.toHaveBeenCalledWith(appName);
});
it('should change current filter when filterParam (name) changes', () => {
component.filters = fakeGlobalFilter;
component.currentFilter = null;
fixture.whenStable().then(() => {
expect(component.currentFilter.name).toEqual(fakeGlobalFilter[2].name);
});
const change = new SimpleChange(null, { name: fakeGlobalFilter[2].name }, true);
component.ngOnChanges({ 'filterParam': change });
});
it('should reload filters by app name on binding changes', () => {
spyOn(component, 'getFilters').and.stub();
const appName = 'fake-app-name';
let change = new SimpleChange(null, appName, true);
component.ngOnChanges({ 'appName': change });
expect(component.getFilters).toHaveBeenCalledWith(appName);
});
it('should return the current filter after one is selected', () => {
let filter = fakeGlobalFilter[1];
component.filters = fakeGlobalFilter;
expect(component.currentFilter).toBeUndefined();
component.selectFilter(<ProcessFilterParamModel> {id: filter.id});
expect(component.getCurrentFilter()).toBe(filter);
});
});

View File

@ -0,0 +1,186 @@
/*!
* @license
* Copyright 2016 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 { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { Observable } from 'rxjs';
import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
import { ProcessFilterRepresentationModel, ProcessFilterParamModel } from '../models/process-filter-cloud.model';
import { TranslationService } from '@alfresco/adf-core';
@Component({
selector: 'adf-cloud-process-filters',
templateUrl: './process-filters-cloud.component.html',
styleUrls: ['process-filters-cloud.component.scss']
})
export class ProcessFiltersCloudComponent implements OnChanges {
/** (required) The application name */
@Input()
appName: string;
/** (optional) The filter to be selected by default */
@Input()
filterParam: ProcessFilterParamModel;
/** (optional) The flag hides/shows icon against each filter */
@Input()
showIcons: boolean = false;
/** Emitted when a filter is selected/clicked */
@Output()
filterClick: EventEmitter<ProcessFilterRepresentationModel> = new EventEmitter<ProcessFilterRepresentationModel>();
/** Emitted when filters are loaded successfully */
@Output()
success: EventEmitter<any> = new EventEmitter<any>();
/** Emitted when any error occurs while loading the filters */
@Output()
error: EventEmitter<any> = new EventEmitter<any>();
filters$: Observable<ProcessFilterRepresentationModel[]>;
currentFilter: ProcessFilterRepresentationModel;
filters: ProcessFilterRepresentationModel [] = [];
constructor(
private processFilterCloudService: ProcessFilterCloudService,
private translate: TranslationService ) { }
ngOnChanges(changes: SimpleChanges) {
const appName = changes['appName'];
const filter = changes['filterParam'];
if (appName && appName.currentValue) {
this.getFilters(appName.currentValue);
} else if (filter && filter.currentValue !== filter.previousValue) {
this.selectFilterAndEmit(filter.currentValue);
}
}
/**
* Fetch the filter list based on appName
*/
getFilters(appName: string) {
this.filters$ = this.processFilterCloudService.getProcessFilters(appName);
this.filters$.subscribe(
(res: ProcessFilterRepresentationModel[]) => {
if (res.length === 0) {
this.createFilters(appName);
} else {
this.resetFilter();
this.filters = res;
}
this.selectFilterAndEmit(this.filterParam);
this.success.emit(res);
},
(err: any) => {
this.error.emit(err);
}
);
}
/**
* Create default filters by appName
*/
createFilters(appName?: string) {
this.filters$ = this.processFilterCloudService.createDefaultFilters(appName);
this.filters$.subscribe(
(resDefault: ProcessFilterRepresentationModel[]) => {
this.resetFilter();
this.filters = resDefault;
},
(errDefault: any) => {
this.error.emit(errDefault);
}
);
}
/**
* Pass the selected filter as next
*/
public selectFilter(filterParam: ProcessFilterParamModel) {
if (filterParam) {
this.currentFilter = this.filters.find((filter, index) => {
return filterParam.id === filter.id ||
(filterParam.name && this.checkFilterNamesEquality(filterParam.name, filter.name)) ||
(filterParam.key && (filterParam.key === filter.key)) ||
filterParam.index === index;
});
}
if (!this.currentFilter) {
this.selectDefaultProcessFilter();
}
}
/**
* Check equality of the filter names by translating the given name strings
*/
private checkFilterNamesEquality(name1: string, name2: string ): boolean {
const translatedName1 = this.translate.instant(name1);
const translatedName2 = this.translate.instant(name2);
return translatedName1.toLocaleLowerCase() === translatedName2.toLocaleLowerCase();
}
/**
* Select and emit the given filter
*/
public selectFilterAndEmit(newFilter: ProcessFilterParamModel) {
this.selectFilter(newFilter);
this.filterClick.emit(this.currentFilter);
}
/**
* Select filter with the id
*/
public selectFilterById(id: string) {
this.selectFilterAndEmit(<ProcessFilterParamModel> {id: id});
}
/**
* Select as default process filter the first in the list
*/
public selectDefaultProcessFilter() {
if (!this.isFilterListEmpty()) {
this.currentFilter = this.filters[0];
}
}
/**
* Return the current process
*/
getCurrentFilter(): ProcessFilterRepresentationModel {
return this.currentFilter;
}
/**
* Check if the filter list is empty
*/
isFilterListEmpty(): boolean {
return this.filters === undefined || (this.filters && this.filters.length === 0);
}
/**
* Reset the filters
*/
private resetFilter() {
this.filters = [];
this.currentFilter = undefined;
}
}

View File

@ -0,0 +1,20 @@
/*!
* @license
* Copyright 2016 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 * from './process-filters-cloud/process-filters-cloud.component';
export * from './models/process-filter-cloud.model';
export * from './process-cloud.module';

View File

@ -0,0 +1,133 @@
/*!
* @license
* Copyright 2016 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 { StorageService } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { ProcessFilterRepresentationModel, ProcessQueryModel } from '../models/process-filter-cloud.model';
@Injectable()
export class ProcessFilterCloudService {
constructor(private storage: StorageService) {
}
/**
* Creates and returns the default filters for a process app.
* @param appName Name of the target app
* @returns Observable of default filters just created
*/
public createDefaultFilters(appName: string): Observable<ProcessFilterRepresentationModel[]> {
const allProcessesFilter = this.getAllProcessesFilter(appName);
this.addFilter(allProcessesFilter);
const runningProcessesFilter = this.getRunningProcessesFilter(appName);
this.addFilter(runningProcessesFilter);
const completedProcessesFilter = this.getCompletedProcessesFilter(appName);
this.addFilter(completedProcessesFilter);
return this.getProcessFilters(appName);
}
/**
* Gets all process instance filters for a process app.
* @param appName Name of the target app
* @returns Observable of process filter details
*/
getProcessFilters(appName: string): Observable<ProcessFilterRepresentationModel[]> {
let key = 'process-filters-' + appName;
const filters = JSON.parse(this.storage.getItem(key) || '[]');
return new Observable(function(observer) {
observer.next(filters);
observer.complete();
});
}
/**
* Adds a new process instance filter
* @param filter The new filter to add
* @returns Details of process filter just added
*/
addFilter(filter: ProcessFilterRepresentationModel) {
const key = 'process-filters-' + filter.query.appName;
const storedFilters = JSON.parse(this.storage.getItem(key) || '[]');
storedFilters.push(filter);
this.storage.setItem(key, JSON.stringify(storedFilters));
}
/**
* Creates and returns a filter for "All" Process instances.
* @param appName Name of the target app
* @returns The newly created filter
*/
getAllProcessesFilter(appName: string): ProcessFilterRepresentationModel {
return new ProcessFilterRepresentationModel({
name: 'ADF_CLOUD_PROCESS_FILTERS.ALL_PROCESSES',
key: 'all-processes',
icon: 'adjust',
query: new ProcessQueryModel(
{
appName: appName,
sort: 'startDate',
order: 'DESC'
}
)
});
}
/**
* Creates and returns a filter for "Running" Process instances.
* @param appName Name of the target app
* @returns The newly created filter
*/
getRunningProcessesFilter(appName: string): ProcessFilterRepresentationModel {
return new ProcessFilterRepresentationModel({
name: 'ADF_CLOUD_PROCESS_FILTERS.RUNNING_PROCESSES',
icon: 'inbox',
key: 'running-processes',
query: new ProcessQueryModel(
{
appName: appName,
sort: 'startDate',
state: 'RUNNING',
order: 'DESC'
}
)
});
}
/**
* Creates and returns a filter for "Completed" Process instances.
* @param appName Name of the target app
* @returns The newly created filter
*/
getCompletedProcessesFilter(appName: string): ProcessFilterRepresentationModel {
return new ProcessFilterRepresentationModel({
name: 'ADF_CLOUD_PROCESS_FILTERS.COMPLETED_PROCESSES',
icon: 'done',
key: 'completed-processes',
query: new ProcessQueryModel(
{
appName: appName,
sort: 'startDate',
state: 'COMPLETED',
order: 'DESC'
}
)
});
}
}

View File

@ -19,8 +19,8 @@
<ng-template> <ng-template>
<adf-empty-content *ngIf="!emptyCustomContent" <adf-empty-content *ngIf="!emptyCustomContent"
icon="assessment" icon="assessment"
[title]="'ADF_CLOUD_TASK_PROCESS_LIST.MESSAGES.TITLE' | translate" [title]="'ADF_CLOUD_PROCESS_LIST.MESSAGES.TITLE' | translate"
[subtitle]="'ADF_CLOUD_TASK_PROCESS_LIST.MESSAGES.SUBTITLE'| translate"> [subtitle]="'ADF_CLOUD_PROCESS_LIST.MESSAGES.SUBTITLE'| translate">
</adf-empty-content> </adf-empty-content>
<ng-content select="adf-empty-custom-content"></ng-content> <ng-content select="adf-empty-custom-content"></ng-content>
</ng-template> </ng-template>

View File

@ -1,16 +1,35 @@
/*!
* @license
* Copyright 2016 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 { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { TRANSLATION_PROVIDER } from '@alfresco/adf-core'; import { TRANSLATION_PROVIDER } from '@alfresco/adf-core';
import { AppListCloudModule } from './app-list-cloud/app-list-cloud.module'; import { AppListCloudModule } from './app-list-cloud/app-list-cloud.module';
import { TaskListCloudModule } from './task-list-cloud/task-list-cloud.module'; import { TaskListCloudModule } from './task-list-cloud/task-list-cloud.module';
import { TaskCloudModule } from './task-cloud/task-cloud.module'; import { TaskCloudModule } from './task-cloud/task-cloud.module';
import { ProcessListCloudModule } from './process-list-cloud/process-list-cloud.module'; import { ProcessListCloudModule } from './process-list-cloud/process-list-cloud.module';
import { ProcessCloudModule } from './process-cloud/process-cloud.module';
@NgModule({ @NgModule({
imports: [ imports: [
AppListCloudModule, AppListCloudModule,
TaskListCloudModule, TaskListCloudModule,
TaskCloudModule, TaskCloudModule,
ProcessListCloudModule ProcessListCloudModule,
ProcessCloudModule
], ],
providers: [ providers: [
{ {
@ -23,6 +42,11 @@ import { ProcessListCloudModule } from './process-list-cloud/process-list-cloud.
} }
], ],
declarations: [], declarations: [],
exports: [AppListCloudModule, TaskListCloudModule, TaskCloudModule, ProcessListCloudModule] exports: [
AppListCloudModule,
TaskListCloudModule,
TaskCloudModule,
ProcessListCloudModule,
ProcessCloudModule]
}) })
export class ProcessServicesCloudModule { } export class ProcessServicesCloudModule { }