From 1387aff0f436345c5dcd70e303dd1817182ecbba Mon Sep 17 00:00:00 2001 From: Deepak Paul Date: Wed, 28 Nov 2018 02:44:48 +0530 Subject: [PATCH] [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 --- demo-shell/resources/i18n/en.json | 4 + demo-shell/src/app/app.routes.ts | 2 +- .../app-layout/app-layout.component.ts | 1 + .../process-list-cloud-example.component.html | 83 ++-- .../process-list-cloud-example.component.scss | 4 + .../process-list-cloud-example.component.ts | 62 ++- .../process-filter-cloud.component.md | 63 +++ .../src/lib/i18n/en.json | 5 + .../src/lib/i18n/it.json | 2 +- .../models/process-filter-cloud.model.ts | 71 ++++ .../lib/process-cloud/process-cloud.module.ts | 43 ++ .../process-filters-cloud.component.html | 17 + .../process-filters-cloud.component.scss | 34 ++ .../process-filters-cloud.component.spec.ts | 375 ++++++++++++++++++ .../process-filters-cloud.component.ts | 186 +++++++++ .../src/lib/process-cloud/public-api.ts | 20 + .../services/process-filter-cloud.service.ts | 133 +++++++ .../process-list-cloud.component.html | 4 +- .../src/lib/process-services-cloud.module.ts | 28 +- 19 files changed, 1068 insertions(+), 69 deletions(-) create mode 100644 docs/process-services-cloud/process-filter-cloud.component.md create mode 100644 lib/process-services-cloud/src/lib/process-cloud/models/process-filter-cloud.model.ts create mode 100644 lib/process-services-cloud/src/lib/process-cloud/process-cloud.module.ts create mode 100644 lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.html create mode 100644 lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.scss create mode 100644 lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.spec.ts create mode 100644 lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.ts create mode 100644 lib/process-services-cloud/src/lib/process-cloud/public-api.ts create mode 100644 lib/process-services-cloud/src/lib/process-cloud/services/process-filter-cloud.service.ts diff --git a/demo-shell/resources/i18n/en.json b/demo-shell/resources/i18n/en.json index db0fe6fffa..cc4c439d2d 100644 --- a/demo-shell/resources/i18n/en.json +++ b/demo-shell/resources/i18n/en.json @@ -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" } } diff --git a/demo-shell/src/app/app.routes.ts b/demo-shell/src/app/app.routes.ts index fa10bb05d1..e751d0bcbe 100644 --- a/demo-shell/src/app/app.routes.ts +++ b/demo-shell/src/app/app.routes.ts @@ -150,7 +150,7 @@ export const appRoutes: Routes = [ ] }, { - path: 'process-cloud', + path: 'process-list-cloud', component: ProcessListCloudExampleComponent }, { diff --git a/demo-shell/src/app/components/app-layout/app-layout.component.ts b/demo-shell/src/app/components/app-layout/app-layout.component.ts index 950a0e67e7..704fc1fc80 100644 --- a/demo-shell/src/app/components/app-layout/app-layout.component.ts +++ b/demo-shell/src/app/components/app-layout/app-layout.component.ts @@ -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' }, diff --git a/demo-shell/src/app/components/cloud/process-list-cloud-example.component.html b/demo-shell/src/app/components/cloud/process-list-cloud-example.component.html index 36a6ad350f..9d562c10bc 100644 --- a/demo-shell/src/app/components/cloud/process-list-cloud-example.component.html +++ b/demo-shell/src/app/components/cloud/process-list-cloud-example.component.html @@ -1,4 +1,11 @@ -
PROCESS LIST CLOUD
+

{{'PROCESS_LIST_CLOUD.TITLE' | translate}}

+ + +
@@ -6,13 +13,13 @@ - Process Example Filters + {{filterName | translate}} - Apply one of the filters to the process list + Customise your filter -
+
@@ -21,60 +28,32 @@ RUNNING - - SUSPENDED + + COMPLETED - - CANCELLED + + + + + Select a column + + {{column.label}} + + + + + + Select a direction + + ASC + + + DESC
-
- - - -
- - - - Sorting Panel - - - Choose how to sort your tasks - - -
- - - - ID - - - NAME - - - STATUS - - - -
-
- - - - ASC - - - DESC - - - -
- - -
{ + 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); } } diff --git a/docs/process-services-cloud/process-filter-cloud.component.md b/docs/process-services-cloud/process-filter-cloud.component.md new file mode 100644 index 0000000000..d03f85a324 --- /dev/null +++ b/docs/process-services-cloud/process-filter-cloud.component.md @@ -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 + + +``` + +## 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)`` | Emitted when filters are loaded successfully. | +| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when any error occurs while loading the filters. | + +### Details + +The `filterParam` input can be used to select a filter as mentioned below. + +```html + + +``` + +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 | \ No newline at end of file diff --git a/lib/process-services-cloud/src/lib/i18n/en.json b/lib/process-services-cloud/src/lib/i18n/en.json index 89212ea91e..56b60c3e9e 100644 --- a/lib/process-services-cloud/src/lib/i18n/en.json +++ b/lib/process-services-cloud/src/lib/i18n/en.json @@ -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" } } diff --git a/lib/process-services-cloud/src/lib/i18n/it.json b/lib/process-services-cloud/src/lib/i18n/it.json index 6d56150df4..45edab80f4 100644 --- a/lib/process-services-cloud/src/lib/i18n/it.json +++ b/lib/process-services-cloud/src/lib/i18n/it.json @@ -1,5 +1,5 @@ { - "ADF_CLOUD_TASK_PROCESS_LIST": { + "ADF_CLOUD_PROCESS_LIST": { "MESSAGES": { "TITLE": "Nessun processo trovato", "SUBTITLE":"Crea un nuovo processo", diff --git a/lib/process-services-cloud/src/lib/process-cloud/models/process-filter-cloud.model.ts b/lib/process-services-cloud/src/lib/process-cloud/models/process-filter-cloud.model.ts new file mode 100644 index 0000000000..ebc37e1b73 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-cloud/models/process-filter-cloud.model.ts @@ -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; + } + } +} diff --git a/lib/process-services-cloud/src/lib/process-cloud/process-cloud.module.ts b/lib/process-services-cloud/src/lib/process-cloud/process-cloud.module.ts new file mode 100644 index 0000000000..f5290f353f --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-cloud/process-cloud.module.ts @@ -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 { } diff --git a/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.html b/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.html new file mode 100644 index 0000000000..1d0aec6d7e --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.html @@ -0,0 +1,17 @@ + diff --git a/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.scss b/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.scss new file mode 100644 index 0000000000..c225a1dc27 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.scss @@ -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; + } + } +} diff --git a/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.spec.ts new file mode 100644 index 0000000000..8b554c86f3 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.spec.ts @@ -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; + + 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( {id: filter.id}); + expect(component.getCurrentFilter()).toBe(filter); + }); +}); diff --git a/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.ts new file mode 100644 index 0000000000..79ce89fbd1 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-cloud/process-filters-cloud/process-filters-cloud.component.ts @@ -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 = new EventEmitter(); + + /** Emitted when filters are loaded successfully */ + @Output() + success: EventEmitter = new EventEmitter(); + + /** Emitted when any error occurs while loading the filters */ + @Output() + error: EventEmitter = new EventEmitter(); + + filters$: Observable; + + 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( {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; + } +} diff --git a/lib/process-services-cloud/src/lib/process-cloud/public-api.ts b/lib/process-services-cloud/src/lib/process-cloud/public-api.ts new file mode 100644 index 0000000000..2438eb04b6 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-cloud/public-api.ts @@ -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'; diff --git a/lib/process-services-cloud/src/lib/process-cloud/services/process-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/process-cloud/services/process-filter-cloud.service.ts new file mode 100644 index 0000000000..d56f916ea2 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-cloud/services/process-filter-cloud.service.ts @@ -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 { + 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 { + 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' + } + ) + }); + } +} diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.html b/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.html index e2e8f3ca93..81b905e4d1 100644 --- a/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.html +++ b/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.html @@ -19,8 +19,8 @@ + [title]="'ADF_CLOUD_PROCESS_LIST.MESSAGES.TITLE' | translate" + [subtitle]="'ADF_CLOUD_PROCESS_LIST.MESSAGES.SUBTITLE'| translate"> diff --git a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts index 8e8c894323..a6cac373ee 100644 --- a/lib/process-services-cloud/src/lib/process-services-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/process-services-cloud.module.ts @@ -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 { }