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

* [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

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

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

@ -41,6 +41,7 @@ export class AppLayoutComponent implements OnInit {
{ href: '/task-list', icon: 'assignment', title: 'APP_LAYOUT.TASK_LIST' },
{ href: '/process-list', icon: 'assignment', title: 'APP_LAYOUT.PROCESS_LIST' },
{ 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: '/login', icon: 'vpn_key', title: 'APP_LAYOUT.LOGIN' },
{ href: '/trashcan', icon: 'delete', title: 'APP_LAYOUT.TRASHCAN' },

@ -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"
(appClick)="onAppClick($event)"></adf-cloud-app-list>
<div *ngIf="currentAppName">
@ -6,13 +13,13 @@
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Process Example Filters
{{filterName | translate}}
</mat-panel-title>
<mat-panel-description>
Apply one of the filters to the process list
Customise your filter
</mat-panel-description>
</mat-expansion-panel-header>
<div class="app-process-cloud-spacing">
<div>
<mat-form-field>
<mat-select placeholder="Status" [(ngModel)]="status">
<mat-option value="">
@ -21,60 +28,32 @@
<mat-option value="RUNNING">
RUNNING
</mat-option>
<mat-option value="SUSPENDED">
SUSPENDED
<mat-option value="COMPLETED">
COMPLETED
</mat-option>
<mat-option value="CANCELLED">
CANCELLED
</mat-select>
</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-select>
</mat-form-field>
</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-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>
<adf-cloud-process-list
[applicationName]="currentAppName"

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

@ -15,30 +15,70 @@
* limitations under the License.
*/
import { Component, ViewChild } from '@angular/core';
import { Component, ViewChild, OnInit } from '@angular/core';
import { UserPreferencesService } from '@alfresco/adf-core';
import { ProcessListCloudComponent } from '@alfresco/adf-process-services-cloud';
import { FormControl } from '@angular/forms';
@Component({
selector: 'app-process-list-example',
templateUrl: './process-list-cloud-example.component.html',
styleUrls: ['./process-list-cloud-example.component.scss']
})
export class ProcessListCloudExampleComponent {
export class ProcessListCloudExampleComponent implements OnInit {
@ViewChild('processCloud')
processCloud: ProcessListCloudComponent;
sortFormControl: FormControl;
sortDirectionFormControl: FormControl;
currentAppName: string = '';
filterName: string = '';
status: string = '';
filterId: string = '';
sortArray: any = [];
sort: string = '';
sortArray: any[];
sortField: 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) {
}
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) {
this.currentAppName = appClicked.name;
}
@ -51,16 +91,16 @@ export class ProcessListCloudExampleComponent {
this.userPreference.paginationSize = event.maxItems;
}
onFilterButtonClick($event) {
let newSortParam: any = {
orderBy: this.sortField,
direction: this.sortDirection };
this.sortArray.push(newSortParam);
onClearFilters() {
this.processCloud.reload();
}
onClearFilters() {
this.sortArray = [];
this.processCloud.reload();
onFilterSelected(filter) {
this.status = filter.query.state || '';
this.sort = filter.query.sort;
this.sortDirection = filter.query.order;
this.filterName = filter.name;
this.sortDirectionFormControl.setValue(this.sortDirection);
this.sortFormControl.setValue(this.sort);
}
}

@ -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 |

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

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

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

@ -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 { }

@ -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>

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

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

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

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

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

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

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