From 63399f68306ffea9aaed4c80d7ccb6689b932709 Mon Sep 17 00:00:00 2001 From: Vito Date: Sat, 3 Nov 2018 17:18:03 +0000 Subject: [PATCH] [ADF-3539] created first version for process list cloud (#3925) * [ADF-3539] created first version for process list cloud * [ADF-3539] fixed process list and added demo page * [ADF-3539] fixed sorting and start working on tests * [ADF-3539] start adding tests for process list cloud * [ADF-3539] fixed empty templates unit tests * [ADF-3539] added documentation * [ADF-3539] missed import * [ADF-3539] fixed wrong export * [ADF-3539] removed model * [ADF-3539] fixed style problem and removed wrong comment --- demo-shell/src/app/app.module.ts | 4 +- demo-shell/src/app/app.routes.ts | 5 + .../process-list-cloud-example.component.html | 94 ++++++ .../process-list-cloud-example.component.scss | 7 + .../process-list-cloud-example.component.ts | 66 +++++ .../process-list-cloud.component.md | 193 ++++++++++++ .../src/lib/i18n/en.json | 9 +- .../process-list-cloud.component.html | 28 ++ .../process-list-cloud.component.scss | 19 ++ .../process-list-cloud.component.spec.ts | 279 ++++++++++++++++++ .../process-list-cloud.component.ts | 247 ++++++++++++++++ .../mock/process-list-service.mock.ts | 114 +++++++ .../models/process-cloud-preset.model.ts | 34 +++ .../process-cloud-query-request.model.ts | 67 +++++ .../models/process-list-sorting.model.ts | 27 ++ .../process-list-cloud.module.spec.ts | 13 + .../process-list-cloud.module.ts | 26 ++ .../src/lib/process-list-cloud/public_api.ts | 22 ++ .../process-list-cloud.service.spec.ts | 134 +++++++++ .../services/process-list-cloud.service.ts | 89 ++++++ .../testing/process-list.testing.module.ts | 49 +++ .../src/lib/process-services-cloud.module.ts | 6 +- .../src/lib/styles/_index.scss | 2 + lib/process-services-cloud/src/public-api.ts | 1 + .../process/process-instances-list.mock.ts | 7 + .../mock/task/task-list.mock.ts | 7 + .../components/process-list.component.spec.ts | 27 +- .../components/task-list.component.spec.ts | 38 +-- 28 files changed, 1581 insertions(+), 33 deletions(-) create mode 100644 demo-shell/src/app/components/cloud/process-list-cloud-example.component.html create mode 100644 demo-shell/src/app/components/cloud/process-list-cloud-example.component.scss create mode 100644 demo-shell/src/app/components/cloud/process-list-cloud-example.component.ts create mode 100644 docs/process-services-cloud/process-list-cloud.component.md create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.html create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.scss create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.spec.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/mock/process-list-service.mock.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/models/process-cloud-preset.model.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/models/process-cloud-query-request.model.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/models/process-list-sorting.model.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/process-list-cloud.module.spec.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/process-list-cloud.module.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/public_api.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/services/process-list-cloud.service.spec.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/services/process-list-cloud.service.ts create mode 100644 lib/process-services-cloud/src/lib/process-list-cloud/testing/process-list.testing.module.ts diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts index ac4e5fcb82..4e79298873 100644 --- a/demo-shell/src/app/app.module.ts +++ b/demo-shell/src/app/app.module.ts @@ -66,6 +66,7 @@ import { AuthBearerInterceptor } from './services'; import { ProcessServicesCloudModule } from '@alfresco/adf-process-services-cloud'; import { CloudComponent } from './components/cloud/cloud.component'; import { TaskListCloudDemoComponent } from './components/task-list-cloud-demo/task-list-cloud-demo.component'; +import { ProcessListCloudExampleComponent } from './components/cloud/process-list-cloud-example.component'; @NgModule({ imports: [ @@ -115,7 +116,8 @@ import { TaskListCloudDemoComponent } from './components/task-list-cloud-demo/ta DemoPermissionComponent, FormLoadingComponent, ReportIssueComponent, - TaskListCloudDemoComponent + TaskListCloudDemoComponent, + ProcessListCloudExampleComponent ], providers: [ { diff --git a/demo-shell/src/app/app.routes.ts b/demo-shell/src/app/app.routes.ts index b6e4f5fa2c..08a12bf7d7 100644 --- a/demo-shell/src/app/app.routes.ts +++ b/demo-shell/src/app/app.routes.ts @@ -42,6 +42,7 @@ import { ReportIssueComponent } from './components/report-issue/report-issue.com import { AppComponent } from './app.component'; import { CloudComponent } from './components/cloud/cloud.component'; import { TaskListCloudDemoComponent } from './components/task-list-cloud-demo/task-list-cloud-demo.component'; +import { ProcessListCloudExampleComponent } from './components/cloud/process-list-cloud-example.component'; export const appRoutes: Routes = [ { path: 'login', component: LoginComponent }, @@ -147,6 +148,10 @@ export const appRoutes: Routes = [ } ] }, + { + path: 'process-cloud', + component: ProcessListCloudExampleComponent + }, { path: 'node-selector', loadChildren: 'app/components/content-node-selector/content-node-selector.module#AppContentNodeSelectorModule' 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 new file mode 100644 index 0000000000..36a6ad350f --- /dev/null +++ b/demo-shell/src/app/components/cloud/process-list-cloud-example.component.html @@ -0,0 +1,94 @@ +
PROCESS LIST CLOUD
+ +
+ + + + + Process Example Filters + + + Apply one of the filters to the process list + + +
+ + + + ALL + + + RUNNING + + + SUSPENDED + + + CANCELLED + + + +
+
+ + + +
+
+ + + + Sorting Panel + + + Choose how to sort your tasks + + +
+ + + + ID + + + NAME + + + STATUS + + + +
+
+ + + + ASC + + + DESC + + + +
+ + +
+
+ + + + + + + + + + +
diff --git a/demo-shell/src/app/components/cloud/process-list-cloud-example.component.scss b/demo-shell/src/app/components/cloud/process-list-cloud-example.component.scss new file mode 100644 index 0000000000..41a9e4b90c --- /dev/null +++ b/demo-shell/src/app/components/cloud/process-list-cloud-example.component.scss @@ -0,0 +1,7 @@ +.adf-process-list-cloud-button { + margin: 15px; +} + +.app-process-cloud-spacing { + margin: 10px; +} diff --git a/demo-shell/src/app/components/cloud/process-list-cloud-example.component.ts b/demo-shell/src/app/components/cloud/process-list-cloud-example.component.ts new file mode 100644 index 0000000000..d202ba8aab --- /dev/null +++ b/demo-shell/src/app/components/cloud/process-list-cloud-example.component.ts @@ -0,0 +1,66 @@ +/*! + * @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, ViewChild } from '@angular/core'; +import { UserPreferencesService } from '@alfresco/adf-core'; +import { ProcessListCloudComponent } from '@alfresco/adf-process-services-cloud'; + +@Component({ + selector: 'app-process-list-example', + templateUrl: './process-list-cloud-example.component.html', + styleUrls: ['./process-list-cloud-example.component.scss'] +}) +export class ProcessListCloudExampleComponent { + + @ViewChild('processCloud') + processCloud: ProcessListCloudComponent; + + currentAppName: string = ''; + status: string = ''; + filterId: string = ''; + sortArray: any = []; + sortField: string; + sortDirection: string; + + constructor(private userPreference: UserPreferencesService) { + } + + onAppClick(appClicked: any) { + this.currentAppName = appClicked.name; + } + + onClick() { + this.currentAppName = ''; + } + + onChangePageSize(event) { + this.userPreference.paginationSize = event.maxItems; + } + + onFilterButtonClick($event) { + let newSortParam: any = { + orderBy: this.sortField, + direction: this.sortDirection }; + this.sortArray.push(newSortParam); + this.processCloud.reload(); + } + + onClearFilters() { + this.sortArray = []; + this.processCloud.reload(); + } +} diff --git a/docs/process-services-cloud/process-list-cloud.component.md b/docs/process-services-cloud/process-list-cloud.component.md new file mode 100644 index 0000000000..9754f3567f --- /dev/null +++ b/docs/process-services-cloud/process-list-cloud.component.md @@ -0,0 +1,193 @@ +--- +Added: v2.0.0 +Status: Active +Last reviewed: 2018-05-24 +--- + +# Process Instance List + +Renders a list containing all the process instances matched by the parameters specified. + +## Contents + +- [Basic Usage](#basic-usage) + - [Transclusions](#transclusions) +- [Class members](#class-members) + - [Properties](#properties) + - [Events](#events) +- [Details](#details) + - [Setting Sorting Order for the list](#setting-sorting-order-for-the-list) + - [Pagination strategy](#pagination-strategy) +- [See also](#see-also) + +## Basic Usage + +**[app.component](../../demo-shell/src/app/app.component.ts).html** + +```html + + +``` + +### [Transclusions](../user-guide/transclusion.md) + +Any content inside an `` sub-component will be shown +when the process list is empty: + +```html + + + Your Content + + +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| applicationName | `string` | | The name of the application. | +| appVersion | `string` | | The application related version | +| initiator | `string` | | the name of the initiator of the process | +| id | `string` | | Filter the processes. Display only processes with id equal to the one insterted. | +| name | `string` | | Filter the processes. Display only processes with name equal to the one insterted. | +| processDefinitionId | `string` | | Filter the processes. Display only processes with processDefinitionId equal to the one insterted. | +| processDefinitionKey | `string` | | Filter the processes. Display only processes with processDefinitionKey equal to the one insterted. | +| serviceFullName | `string` | | Filter the processes. Display only processes with serviceFullName equal to the one insterted. | +| serviceName | `string` | | Filter the processes. Display only processes with serviceName equal to the one insterted. | +| serviceType | `string` | | Filter the processes. Display only processes with serviceType equal to the one insterted. | +| serviceVersion | `string` | | Filter the processes. Display only processes with serviceVersion equal to the one insterted. | +| status | `string` | | Filter the tasks. Display only processes with status equal to the one insterted. | +| businessKey | `string` | | Filter the tasks. Display only processes with businessKey equal to the one insterted. | +| selectFirstRow | `boolean` | true | Toggles default selection of the first row | +| landingTaskId | `string` | | Define which task id should be selected after reloading. If the task id doesn't exist or nothing is passed then the first task will be selected. | +| selectionMode | `string` | "single" | Row selection mode. Can be none, `single` or `multiple`. For `multiple` mode, you can use Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for multiple rows. | +| multiselect | `boolean` | false | Toggles multiple row selection, renders checkboxes at the beginning of each row | +| sorting | `[ProcessListCloudSortingModel]` | | This array of `ProcessListCloudSortingModel` specify how the sorting on our table should be provided. This parameters are for BE sorting. | + +### Events + +| Name | Type | Description | +| ---- | ---- | ----------- | +| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when an error occurs while loading the list of process instances from the server. | +| rowClick | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when a row in the process list is clicked. | +| rowsSelected | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when rows are selected/unselected. | +success | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when the list of process instances has been loaded successfully from the server. | + +## Details + +You can define a custom schema for the list in the `app.config.json` file and access it with the +`presetColumn` property as shown below: + +```json +"adf-cloud-process-list": { + "presets": { + "customSchema": [ + { + "key": "name", + "type": "text", + "title": "name", + "sortable": true + }], + "default": [ + { + "key": "name", + "type": "text", + "title": "name", + "sortable": true + }], + } +} +``` + +```html + + +``` + +You can also define the schema in the HTML using the +[Data column component](../core/data-column.component.md). You can combine this with schema +information defined in `app.config.json` as in the example below: + +```json +"adf-cloud-process-list": { + "presets": { + "customSchema": [ + { + "key": "id", + "type": "text", + "title": "Id", + "sortable": true + }], + "default": [ + { + "key": "name", + "type": "text", + "title": "name", + "sortable": true + }], + } +} +``` + + + +```html + + + + +
{{getFullName(entry.row.obj.assignee)}}
+
+
+
+
+``` + +### Setting Sorting Order for the list + +you can pass sorting order as shown in the example below: + +```ts + +let sorting = [{ orderBy: 'status', direction: 'desc' }]; +``` + +```html + + +``` + + + +### Pagination strategy + +The Process Instance List also supports pagination: + +```html + + + + +``` + +## See also + +- [Data column component](../core/data-column.component.md) +- [Data Table Adapter interface](../core/datatable-adapter.interface.md) +- [Pagination component](../core/pagination.component.md) diff --git a/lib/process-services-cloud/src/lib/i18n/en.json b/lib/process-services-cloud/src/lib/i18n/en.json index 4fc955a659..1c9901cb40 100644 --- a/lib/process-services-cloud/src/lib/i18n/en.json +++ b/lib/process-services-cloud/src/lib/i18n/en.json @@ -1,3 +1,10 @@ { - "TEST_KEY": "MY TEST" + "TEST_KEY": "MY TEST", + "ADF-PROCESS-LIST-CLOUD": { + "MESSAGES": { + "TITLE": "No Processes Found", + "SUBTITLE":"Create a new process that you want to easily find later", + "NONE": "No process instance filter selected." + } + } } 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 new file mode 100644 index 0000000000..79522b8d1c --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.html @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.scss b/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.scss new file mode 100644 index 0000000000..4ce7d0ede1 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.scss @@ -0,0 +1,19 @@ +@mixin adf-process-filters-cloud-theme($theme) { + + .adf { + + &-cloud-process-list-loading-margin { + margin-left: calc((100% - 100px) / 2); + margin-right: calc((100% - 100px) / 2); + } + } + + + .no-content-message { + font-size: 16px; + font-weight: bold; + text-align: center; + opacity: 0.54; + } +} + diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.spec.ts new file mode 100644 index 0000000000..9614c3f7cc --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.spec.ts @@ -0,0 +1,279 @@ +/*! + * @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, SimpleChange, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { AppConfigService, setupTestBed, CoreModule, DataTableModule } from '@alfresco/adf-core'; +import { DataRowEvent, ObjectDataRow } from '@alfresco/adf-core'; +import { ProcessListCloudService } from '../services/process-list-cloud.service'; +import { ProcessListCloudComponent } from './process-list-cloud.component'; +import { fakeProcessCloudList, fakeCustomSchema } from '../mock/process-list-service.mock'; +import { of } from 'rxjs'; +import { ProcessListCloudTestingModule } from '../testing/process-list.testing.module'; +import { ProcessListCloudModule } from '../process-list-cloud.module'; + +@Component({ + template: ` + + + + + + +
{{getFullName(entry.row.obj.startedBy)}}
+
+
+
+
` +}) +class CustomTaskListComponent { + @ViewChild(ProcessListCloudComponent) + processListCloud: ProcessListCloudComponent; +} + +@Component({ + template: ` + + +

TEST

+
+
+ ` +}) + +class EmptyTemplateComponent { +} +/*tslint:disable*/ +fdescribe('ProcessListCloudComponent', () => { + let component: ProcessListCloudComponent; + let fixture: ComponentFixture; + let appConfig: AppConfigService; + let processListCloudService: ProcessListCloudService; + + setupTestBed({ + imports: [ + ProcessListCloudTestingModule, ProcessListCloudModule + ], + providers: [ProcessListCloudService] + }); + + beforeEach(() => { + appConfig = TestBed.get(AppConfigService); + processListCloudService = TestBed.get(ProcessListCloudService); + fixture = TestBed.createComponent(ProcessListCloudComponent); + component = fixture.componentInstance; + appConfig.config = Object.assign(appConfig.config, { + 'adf-cloud-process-list': { + 'presets': { + 'fakeCustomSchema': [ + { + 'key': 'fakeName', + 'type': 'text', + 'title': 'ADF_TASK_LIST.PROPERTIES.FAKE', + 'sortable': true + }, + { + 'key': 'fakeTaskName', + 'type': 'text', + 'title': 'ADF_TASK_LIST.PROPERTIES.TASK_FAKE', + 'sortable': true + } + ] + } + } + }); + }); + + afterEach(() => { + fixture.destroy(); + }); + + it('should use the default schemaColumn as default', () => { + component.ngAfterContentInit(); + expect(component.columns).toBeDefined(); + expect(component.columns.length).toEqual(2); + }); + + it('should use the custom schemaColumn from app.config.json', () => { + component.presetColumn = 'fakeCustomSchema'; + component.ngAfterContentInit(); + fixture.detectChanges(); + expect(component.columns).toEqual(fakeCustomSchema); + }); + + it('should fetch custom schemaColumn when the input presetColumn is defined', () => { + component.presetColumn = 'fakeCustomSchema'; + fixture.detectChanges(); + expect(component.columns).toBeDefined(); + expect(component.columns.length).toEqual(2); + }); + + it('should return the results if an application name is given', (done) => { + spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); + let appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.rows).toBeDefined(); + expect(component.rows.length).toEqual(3); + expect(component.rows[0].entry['serviceName']).toEqual('simple-app-rb'); + expect(component.rows[0].entry['serviceFullName']).toEqual('simple-app-rb'); + expect(component.rows[0].entry['serviceVersion']).toBe(''); + expect(component.rows[0].entry['appName']).toBe('easy-peasy-japanesey'); + expect(component.rows[0].entry['appVersion']).toBe(''); + expect(component.rows[0].entry['serviceType']).toBeNull(); + expect(component.rows[0].entry['id']).toBe('69eddfa7-d781-11e8-ae24-0a58646001fa'); + expect(component.rows[0].entry['name']).toEqual('starring'); + expect(component.rows[0].entry['description']).toBeNull(); + expect(component.rows[0].entry['processDefinitionId']).toBe('BasicProcess:1:d05062f1-c6fb-11e8-ae24-0a58646001fa'); + expect(component.rows[0].entry['processDefinitionKey']).toBe('BasicProcess'); + expect(component.rows[0].entry['initiator']).toBe('devopsuser'); + expect(component.rows[0].entry['startDate']).toBe(1540381146275); + expect(component.rows[0].entry['businessKey']).toBe('MyBusinessKey'); + expect(component.rows[0].entry['status']).toBe('RUNNING'); + expect(component.rows[0].entry['lastModified']).toBe(1540381146276); + expect(component.rows[0].entry['lastModifiedTo']).toBeNull(); + expect(component.rows[0].entry['lastModifiedFrom']).toBeNull(); + + done(); + }); + component.applicationName = appName.currentValue; + component.ngOnChanges({ 'appName': appName }); + fixture.detectChanges(); + }); + + it('should reload tasks when reload() is called', (done) => { + component.applicationName = 'fake'; + spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.rows).toBeDefined(); + done(); + }); + fixture.detectChanges(); + component.reload(); + }); + + it('should emit row click event', (done) => { + let row = new ObjectDataRow({ + entry: { + id: '999' + } + }); + let rowEvent = new DataRowEvent(row, null); + component.rowClick.subscribe(taskId => { + expect(taskId).toEqual('999'); + expect(component.getCurrentId()).toEqual('999'); + done(); + }); + component.onRowClick(rowEvent); + }); + + describe('component changes', () => { + + beforeEach(() => { + component.rows = fakeProcessCloudList.list.entries; + fixture.detectChanges(); + }); + + it('should NOT reload the tasks if the landingTaskId is the same of the current task', () => { + spyOn(component, 'reload').and.stub(); + component.currentInstanceId = '999'; + component.rows = [{ entry: { id: '999', name: 'Fake-name' } }]; + const landingTaskId = '999'; + let change = new SimpleChange('999', landingTaskId, true); + component.ngOnChanges({ 'landingTaskId': change }); + expect(component.reload).not.toHaveBeenCalled(); + expect(component.rows.length).toEqual(1); + }); + + it('should reload the tasks if the loadingTaskId is different from the current task', (done) => { + component.currentInstanceId = '999'; + component.rows = [{ id: '999', name: 'Fake-name' }]; + const landingTaskId = '888'; + let change = new SimpleChange(null, landingTaskId, true); + component.applicationName = 'fake'; + spyOn(processListCloudService, 'getProcessByRequest').and.returnValue(of(fakeProcessCloudList)); + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.rows).toBeDefined(); + expect(component.rows.length).toEqual(3); + done(); + }); + component.ngOnChanges({ 'landingTaskId': change }); + }); + }); + describe('Injecting custom colums for tasklist - CustomTaskListComponent', () => { + + let fixtureCustom: ComponentFixture; + let componentCustom: CustomTaskListComponent; + + setupTestBed({ + imports: [CoreModule.forRoot()], + declarations: [ProcessListCloudComponent, CustomTaskListComponent], + providers: [ProcessListCloudService] + }); + + beforeEach(() => { + fixtureCustom = TestBed.createComponent(CustomTaskListComponent); + fixtureCustom.detectChanges(); + componentCustom = fixtureCustom.componentInstance; + }); + + afterEach(() => { + fixtureCustom.destroy(); + }); + + it('should create instance of CustomTaskListComponent', () => { + expect(componentCustom instanceof CustomTaskListComponent).toBe(true, 'should create CustomTaskListComponent'); + }); + + it('should fetch custom schemaColumn from html', () => { + fixture.detectChanges(); + expect(componentCustom.processListCloud.columnList).toBeDefined(); + expect(componentCustom.processListCloud.columns[0]['title']).toEqual('ADF_TASK_LIST.PROPERTIES.NAME'); + expect(componentCustom.processListCloud.columns[1]['title']).toEqual('ADF_TASK_LIST.PROPERTIES.CREATED'); + expect(componentCustom.processListCloud.columns.length).toEqual(3); + }); + }); + + describe('Creating an empty custom template - EmptyTemplateComponent', () => { + + let fixtureEmpty: ComponentFixture; + + setupTestBed({ + imports: [ProcessListCloudModule, ProcessListCloudTestingModule, DataTableModule], + declarations: [EmptyTemplateComponent] + }); + + beforeEach(() => { + fixtureEmpty = TestBed.createComponent(EmptyTemplateComponent); + fixtureEmpty.detectChanges(); + }); + + afterEach(() => { + fixtureEmpty.destroy(); + }); + + it('should render the custom template', async(() => { + fixtureEmpty.whenStable().then(() => { + fixtureEmpty.detectChanges(); + expect(fixtureEmpty.debugElement.query(By.css('#custom-id'))).not.toBeNull(); + expect(fixtureEmpty.debugElement.query(By.css('.adf-empty-content'))).toBeNull(); + }); + })); + }); +}); diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.ts b/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.ts new file mode 100644 index 0000000000..52cd4fd82c --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/components/process-list-cloud.component.ts @@ -0,0 +1,247 @@ +import { Component, ViewEncapsulation, OnChanges, AfterContentInit, ContentChild, Output, EventEmitter, SimpleChanges, SimpleChange, Input } from '@angular/core'; +import { DataTableSchema, PaginatedComponent, + EmptyCustomContentDirective, AppConfigService, + UserPreferencesService, PaginationModel, + UserPreferenceValues, DataRowEvent } from '@alfresco/adf-core'; +import { ProcessListCloudService } from '../services/process-list-cloud.service'; +import { BehaviorSubject } from 'rxjs'; +import { processCloudPresetsDefaultModel } from '../models/process-cloud-preset.model'; +import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model'; +import { ProcessListCloudSortingModel } from '../models/process-list-sorting.model'; +import { MinimalNodeEntity } from 'alfresco-js-api'; +@Component({ + selector: 'adf-cloud-process-list', + templateUrl: './process-list-cloud.component.html', + styleUrls: ['./process-list-cloud.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ProcessListCloudComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent { + + static PRESET_KEY = 'adf-cloud-process-list.presets'; + + @ContentChild(EmptyCustomContentDirective) + emptyCustomContent: EmptyCustomContentDirective; + + @Input() + applicationName: string = ''; + + @Input() + appVersion: string = ''; + + @Input() + initiator: string = ''; + + @Input() + id: string = ''; + + @Input() + name: string = ''; + + @Input() + processDefinitionId: string = ''; + + @Input() + processDefinitionKey: string = ''; + + @Input() + serviceFullName: string = ''; + + @Input() + serviceName: string = ''; + + @Input() + serviceType: string = ''; + + @Input() + serviceVersion: string = ''; + + @Input() + status: string = ''; + + @Input() + businessKey: string = ''; + + @Input() + selectFirstRow: boolean = true; + + @Input() + landingTaskId: string; + + @Input() + selectionMode: string = 'single'; // none|single|multiple + + /** Toggles multiple row selection, renders checkboxes at the beginning of each row */ + @Input() + multiselect: boolean = false; + + @Input() + sorting: ProcessListCloudSortingModel[]; + + @Output() + rowClick: EventEmitter = new EventEmitter(); + + @Output() + rowsSelected: EventEmitter = new EventEmitter(); + + @Output() + error: EventEmitter = new EventEmitter(); + + @Output() + success: EventEmitter = new EventEmitter(); + + pagination: BehaviorSubject; + size: number; + skipCount: number = 0; + currentInstanceId: string; + selectedInstances: any[]; + isLoading = false; + rows: any[] = []; + requestNode: ProcessQueryCloudRequestModel; + + constructor(private processListCloudService: ProcessListCloudService, + appConfigService: AppConfigService, + private userPreferences: UserPreferencesService) { + super(appConfigService, ProcessListCloudComponent.PRESET_KEY, processCloudPresetsDefaultModel); + this.size = userPreferences.paginationSize; + this.userPreferences.select(UserPreferenceValues.PaginationSize).subscribe((pageSize) => { + this.size = pageSize; + }); + this.pagination = new BehaviorSubject( { + maxItems: this.size, + skipCount: 0, + totalItems: 0 + }); + } + + ngAfterContentInit() { + this.createDatatableSchema(); + } + + ngOnChanges(changes: SimpleChanges) { + if (this.isPropertyChanged(changes) && + !this.isEqualToCurrentId(changes['landingTaskId'])) { + this.reload(); + } + } + + getCurrentId(): string { + return this.currentInstanceId; + } + + reload() { + this.requestNode = this.createRequestNode(); + if (this.requestNode.appName) { + this.load(this.requestNode); + } else { + this.rows = []; + } + } + + private load(requestNode: ProcessQueryCloudRequestModel) { + this.isLoading = true; + this.processListCloudService.getProcessByRequest(requestNode).subscribe( + (processes) => { + this.rows = processes.list.entries; + this.selectTask(this.landingTaskId); + this.success.emit(processes); + this.isLoading = false; + this.pagination.next(processes.list.pagination); + }, (error) => { + this.error.emit(error); + this.isLoading = false; + }); + } + + private isEqualToCurrentId(landingTaskChanged: SimpleChange): boolean { + return landingTaskChanged && this.currentInstanceId === landingTaskChanged.currentValue; + } + + private isPropertyChanged(changes: SimpleChanges): boolean { + for (let property in changes) { + if (changes.hasOwnProperty(property)) { + if (changes[property] && + (changes[property].currentValue !== changes[property].previousValue)) { + return true; + } + } + } + return false; + } + + selectTask(taskIdSelected: string) { + if (!this.isListEmpty()) { + let dataRow: any = null; + if (taskIdSelected) { + dataRow = this.rows.find((currentRow: MinimalNodeEntity) => { + return currentRow.entry.id === taskIdSelected; + }); + } + if (!dataRow && this.selectFirstRow) { + dataRow = this.rows[0]; + } + if (dataRow) { + dataRow.isSelected = true; + this.currentInstanceId = dataRow.entry.id; + } + } else { + this.currentInstanceId = null; + } + } + + isListEmpty(): boolean { + return !this.rows || this.rows.length === 0; + } + + updatePagination(pagination: PaginationModel) { + this.size = pagination.maxItems; + this.skipCount = pagination.skipCount; + this.pagination.next(pagination); + this.reload(); + } + + onRowClick(item: DataRowEvent) { + this.currentInstanceId = item.value.getValue('entry.id'); + this.rowClick.emit(this.currentInstanceId); + } + + onRowSelect(event: CustomEvent) { + this.selectedInstances = [...event.detail.selection]; + this.rowsSelected.emit(this.selectedInstances); + } + + onRowUnselect(event: CustomEvent) { + this.selectedInstances = [...event.detail.selection]; + this.rowsSelected.emit(this.selectedInstances); + } + + onRowKeyUp(event: CustomEvent) { + if (event.detail.keyboardEvent.key === 'Enter') { + event.preventDefault(); + this.currentInstanceId = event.detail.row.getValue('entry.id'); + this.rowClick.emit(this.currentInstanceId); + } + } + + private createRequestNode(): ProcessQueryCloudRequestModel { + let requestNode = { + appName: this.applicationName, + appVersion: this.appVersion, + maxItems: this.size, + skipCount: this.skipCount, + initiator: this.initiator, + id: this.id, + name: this.name, + processDefinitionId: this.processDefinitionId, + processDefinitionKey: this.processDefinitionKey, + serviceFullName: this.serviceFullName, + serviceName: this.serviceName, + serviceType: this.serviceType, + serviceVersion: this.serviceVersion, + status: this.status, + businessKey: this.businessKey, + sorting: this.sorting + }; + return new ProcessQueryCloudRequestModel(requestNode); + } + +} diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/mock/process-list-service.mock.ts b/lib/process-services-cloud/src/lib/process-list-cloud/mock/process-list-service.mock.ts new file mode 100644 index 0000000000..aed6fe8489 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/mock/process-list-service.mock.ts @@ -0,0 +1,114 @@ +/*! + * @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 { ObjectDataColumn } from '@alfresco/adf-core'; + +export const fakeProcessCloudList = { + list: { + entries: [ + { + entry: { + serviceName: 'simple-app-rb', + serviceFullName: 'simple-app-rb', + serviceVersion: '', + appName: 'easy-peasy-japanesey', + appVersion: '', + serviceType: null, + id: '69eddfa7-d781-11e8-ae24-0a58646001fa', + name: 'starring', + description: null, + processDefinitionId: 'BasicProcess:1:d05062f1-c6fb-11e8-ae24-0a58646001fa', + processDefinitionKey: 'BasicProcess', + initiator: 'devopsuser', + startDate: 1540381146275, + businessKey: 'MyBusinessKey', + status: 'RUNNING', + lastModified: 1540381146276, + lastModifiedTo: null, + lastModifiedFrom: null + } + }, + { + entry: { + serviceName: 'simple-app-rb', + serviceFullName: 'simple-app-rb', + serviceVersion: '', + appName: 'easy-peasy-japanesey', + appVersion: '', + serviceType: null, + id: '8b3f625f-d781-11e8-ae24-0a58646001fa', + name: null, + description: null, + processDefinitionId: 'BasicProcess:1:d05062f1-c6fb-11e8-ae24-0a58646001fa', + processDefinitionKey: 'BasicProcess', + initiator: 'devopsuser', + startDate: 1540381202174, + businessKey: 'MyBusinessKey', + status: 'RUNNING', + lastModified: 1540381202174, + lastModifiedTo: null, + lastModifiedFrom: null + } + }, + { + entry: { + serviceName: 'simple-app-rb', + serviceFullName: 'simple-app-rb', + serviceVersion: '', + appName: 'easy-peasy-japanesey', + appVersion: '', + serviceType: null, + id: '87c12637-d783-11e8-ae24-0a58646001fa', + name: null, + description: null, + processDefinitionId: 'BasicProcess:1:d05062f1-c6fb-11e8-ae24-0a58646001fa', + processDefinitionKey: 'BasicProcess', + initiator: 'superadminuser', + startDate: 1540382055307, + businessKey: 'MyBusinessKey', + status: 'RUNNING', + lastModified: 1540382055308, + lastModifiedTo: null, + lastModifiedFrom: null + } + } + ], + pagination: { + skipCount: 0, + maxItems: 100, + count: 3, + hasMoreItems: false, + totalItems: 3 + } + } +}; + +export let fakeCustomSchema = + [ + new ObjectDataColumn({ + 'key': 'fakeName', + 'type': 'text', + 'title': 'ADF_TASK_LIST.PROPERTIES.FAKE', + 'sortable': true + }), + new ObjectDataColumn({ + 'key': 'fakeTaskName', + 'type': 'text', + 'title': 'ADF_TASK_LIST.PROPERTIES.TASK_FAKE', + 'sortable': true + }) + ]; diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/models/process-cloud-preset.model.ts b/lib/process-services-cloud/src/lib/process-list-cloud/models/process-cloud-preset.model.ts new file mode 100644 index 0000000000..a67923bfb9 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/models/process-cloud-preset.model.ts @@ -0,0 +1,34 @@ +/*! + * @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 let processCloudPresetsDefaultModel = { + 'default': [ + { + 'key': 'name', + 'type': 'text', + 'title': 'ADF_PROCESS_LIST.PROPERTIES.NAME', + 'sortable': true + }, + { + 'key': 'created', + 'type': 'text', + 'title': 'ADF_PROCESS_LIST.PROPERTIES.CREATED', + 'cssClass': 'hidden', + 'sortable': true + } + ] +}; diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/models/process-cloud-query-request.model.ts b/lib/process-services-cloud/src/lib/process-list-cloud/models/process-cloud-query-request.model.ts new file mode 100644 index 0000000000..96c63096be --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/models/process-cloud-query-request.model.ts @@ -0,0 +1,67 @@ +/*! + * @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 { ProcessListCloudSortingModel } from './process-list-sorting.model'; + +export class ProcessQueryCloudRequestModel { + appName: string; + appVersion?: string; + description?: string; + initiator?: null; + id?: string; + name?: string; + processDefinitionId?: string; + processDefinitionKey?: string; + serviceFullName?: string; + serviceName?: string; + serviceType?: string; + serviceVersion?: string; + status?: string; + startDate?: string; + businessKey?: string; + lastModified?: string; + lastModifiedTo?: string; + lastModifiedFrom?: string; + maxItems: number; + skipCount: number; + sorting?: ProcessListCloudSortingModel[]; + constructor(obj?: any) { + if (obj) { + this.appName = obj.appName; + this.appVersion = obj.appVersion; + this.description = obj.description; + this.initiator = obj.initiator; + this.id = obj.id; + this.name = obj.name; + this.processDefinitionId = obj.processDefinitionId; + this.processDefinitionKey = obj.processDefinitionKey; + this.serviceFullName = obj.serviceFullName; + this.serviceName = obj.serviceName; + this.serviceType = obj.serviceType; + this.serviceVersion = obj.serviceVersion; + this.status = obj.status; + this.startDate = obj.startDate; + this.businessKey = obj.businessKey; + this.lastModified = obj.lastModified; + this.lastModifiedTo = obj.lastModifiedTo; + this.lastModifiedFrom = obj.lastModifiedFrom; + this.maxItems = obj.maxItems; + this.skipCount = obj.skipCount; + this.sorting = obj.sorting; + } + } +} diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/models/process-list-sorting.model.ts b/lib/process-services-cloud/src/lib/process-list-cloud/models/process-list-sorting.model.ts new file mode 100644 index 0000000000..0e087948ba --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/models/process-list-sorting.model.ts @@ -0,0 +1,27 @@ +/*! + * @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 ProcessListCloudSortingModel { + orderBy: string; + direction: string; + constructor(obj: any) { + if (obj) { + this.orderBy = obj.orderBy; + this.direction = obj.direction; + } + } +} diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/process-list-cloud.module.spec.ts b/lib/process-services-cloud/src/lib/process-list-cloud/process-list-cloud.module.spec.ts new file mode 100644 index 0000000000..1a890886f5 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/process-list-cloud.module.spec.ts @@ -0,0 +1,13 @@ +import { ProcessListCloudModule } from './process-list-cloud.module'; + +describe('ProcessListCloudModule', () => { + let processListCloudModule: ProcessListCloudModule; + + beforeEach(() => { + processListCloudModule = new ProcessListCloudModule(); + }); + + it('should create an instance', () => { + expect(processListCloudModule).toBeTruthy(); + }); +}); diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/process-list-cloud.module.ts b/lib/process-services-cloud/src/lib/process-list-cloud/process-list-cloud.module.ts new file mode 100644 index 0000000000..4d65c566ac --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/process-list-cloud.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ProcessListCloudComponent } from './components/process-list-cloud.component'; +import { MaterialModule } from '../material.module'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateLoaderService, DataTableModule, TemplateModule } from '@alfresco/adf-core'; +import { ProcessListCloudService } from './services/process-list-cloud.service'; + +@NgModule({ + imports: [ + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderService + } + }), + MaterialModule, + DataTableModule, + TemplateModule + ], + declarations: [ProcessListCloudComponent], + exports: [ProcessListCloudComponent], + providers: [ProcessListCloudService] +}) +export class ProcessListCloudModule { } diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/public_api.ts b/lib/process-services-cloud/src/lib/process-list-cloud/public_api.ts new file mode 100644 index 0000000000..a6e2fea333 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/public_api.ts @@ -0,0 +1,22 @@ +/*! + * @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 './components/process-list-cloud.component'; +export * from './models/process-cloud-query-request.model'; +export * from './models/process-cloud-preset.model'; +export * from './models/process-list-sorting.model'; +export * from './process-list-cloud.module'; diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/services/process-list-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/process-list-cloud/services/process-list-cloud.service.spec.ts new file mode 100644 index 0000000000..04faf1f040 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/services/process-list-cloud.service.spec.ts @@ -0,0 +1,134 @@ +/*! + * @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 { async } from '@angular/core/testing'; +import { setupTestBed } from '@alfresco/adf-core'; +import { fakeProcessCloudList } from '../mock/process-list-service.mock'; +import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, CoreModule } from '@alfresco/adf-core'; +import { ProcessListCloudService } from './process-list-cloud.service'; +import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model'; + +describe('Activiti ProcessList Cloud Service', () => { + let service: ProcessListCloudService; + let alfrescoApiMock: AlfrescoApiServiceMock; + + function returFakeProcessListResults() { + return { + oauth2Auth: { + callCustomApi: () => { + return Promise.resolve(fakeProcessCloudList); + } + } + }; + } + + function returnCallQueryParameters() { + return { + oauth2Auth: { + callCustomApi: (queryUrl, operation, context, queryParams) => { + return Promise.resolve(queryParams); + } + } + }; + } + + function returnCallUrl() { + return { + oauth2Auth: { + callCustomApi: (queryUrl, operation, context, queryParams) => { + return Promise.resolve(queryUrl); + } + } + }; + } + + setupTestBed({ + imports: [ + CoreModule.forRoot() + ] + }); + + beforeEach(async(() => { + alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()); + service = new ProcessListCloudService(alfrescoApiMock, + new AppConfigService(null), + new LogService(new AppConfigService(null))); + })); + + it('should return the processes', (done) => { + let processRequest: ProcessQueryCloudRequestModel = { appName: 'fakeName' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returFakeProcessListResults); + service.getProcessByRequest(processRequest).subscribe((res) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.list.entries.length).toBe(3); + expect(res.list.entries[0].entry.appName).toBe('easy-peasy-japanesey'); + expect(res.list.entries[1].entry.appName).toBe('easy-peasy-japanesey'); + expect(res.list.entries[1].entry.appName).toBe('easy-peasy-japanesey'); + done(); + }); + }); + + it('should append to the call all the parameters', (done) => { + let processRequest: ProcessQueryCloudRequestModel = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnCallQueryParameters); + service.getProcessByRequest(processRequest).subscribe((res) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.skipCount).toBe(0); + expect(res.maxItems).toBe(20); + expect(res.service).toBe('fake-service'); + done(); + }); + }); + + it('should concat the app name to the request url', (done) => { + let processRequest: ProcessQueryCloudRequestModel = { appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service' }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnCallUrl); + service.getProcessByRequest(processRequest).subscribe((requestUrl) => { + expect(requestUrl).toBeDefined(); + expect(requestUrl).not.toBeNull(); + expect(requestUrl).toContain('/fakeName-query/v1/process-instances'); + done(); + }); + }); + + it('should concat the sorting to append as parameters', (done) => { + let processRequest: ProcessQueryCloudRequestModel = { + appName: 'fakeName', skipCount: 0, maxItems: 20, service: 'fake-service', + sorting: [{ orderBy: 'NAME', direction: 'DESC' }, { orderBy: 'TITLE', direction: 'ASC' }] + }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnCallQueryParameters); + service.getProcessByRequest(processRequest).subscribe((res) => { + expect(res).toBeDefined(); + expect(res).not.toBeNull(); + expect(res.sort).toBe('NAME,DESC&TITLE,ASC'); + done(); + }); + }); + + it('should return an error when app name is not specified', (done) => { + let processRequest: ProcessQueryCloudRequestModel = { appName: null }; + spyOn(alfrescoApiMock, 'getInstance').and.callFake(returnCallUrl); + service.getProcessByRequest(processRequest).subscribe( + () => { }, + (error) => { + expect(error).toBe('Appname not configured'); + done(); + } + ); + }); +}); diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/services/process-list-cloud.service.ts b/lib/process-services-cloud/src/lib/process-list-cloud/services/process-list-cloud.service.ts new file mode 100644 index 0000000000..e489cdeea1 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/services/process-list-cloud.service.ts @@ -0,0 +1,89 @@ +/*! + * @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 { Injectable } from '@angular/core'; +import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core'; +import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model'; +import { Observable, from, throwError } from 'rxjs'; +import { ProcessListCloudSortingModel } from '../models/process-list-sorting.model'; +@Injectable() +export class ProcessListCloudService { + + contentTypes = ['application/json']; + accepts = ['application/json']; + + constructor(private apiService: AlfrescoApiService, + private appConfigService: AppConfigService, + private logService: LogService) { + } + + getProcessByRequest(requestNode: ProcessQueryCloudRequestModel): Observable { + if (requestNode.appName) { + let queryUrl = this.buildQueryUrl(requestNode); + let queryParams = this.buildQueryParams(requestNode); + let sortingParams = this.buildSortingParam(requestNode.sorting); + if (sortingParams) { + queryParams['sort'] = sortingParams; + } + return from(this.apiService.getInstance() + .oauth2Auth.callCustomApi(queryUrl, 'GET', + null, queryParams, null, + null, null, null, this.contentTypes, + this.accepts, Object, null, null) + ); + } else { + this.logService.error('Appname is mandatory for querying task'); + return throwError('Appname not configured'); + } + } + private buildQueryUrl(requestNode: ProcessQueryCloudRequestModel) { + return `${this.appConfigService.get('bpmHost', '')}/${requestNode.appName}-query/v1/process-instances`; + } + + private isPropertyValueValid(requestNode, property) { + return requestNode[property] !== '' && requestNode[property] !== null && requestNode[property] !== undefined; + } + + private buildQueryParams(requestNode: ProcessQueryCloudRequestModel) { + let queryParam = {}; + for (let property in requestNode) { + if (requestNode.hasOwnProperty(property) && + !this.isExcludedField(property) && + this.isPropertyValueValid(requestNode, property)) { + queryParam[property] = requestNode[property]; + } + } + return queryParam; + } + + private isExcludedField(property) { + return property === 'appName' || property === 'sorting'; + } + + private buildSortingParam(sortings: ProcessListCloudSortingModel[]): string { + let finalSorting: string = ''; + if (sortings) { + for (let sort of sortings) { + if (!finalSorting) { + finalSorting = `${sort.orderBy},${sort.direction}`; + } else { + finalSorting = `${finalSorting}&${sort.orderBy},${sort.direction}`; + } + } + } + return encodeURI(finalSorting); + } +} diff --git a/lib/process-services-cloud/src/lib/process-list-cloud/testing/process-list.testing.module.ts b/lib/process-services-cloud/src/lib/process-list-cloud/testing/process-list.testing.module.ts new file mode 100644 index 0000000000..feafdaf0b3 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process-list-cloud/testing/process-list.testing.module.ts @@ -0,0 +1,49 @@ +/*! + * @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 { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { HttpClientModule } from '@angular/common/http'; +import { ProcessListCloudModule } from '../process-list-cloud.module'; +import { + AlfrescoApiService, + AlfrescoApiServiceMock, + AppConfigService, + AppConfigServiceMock, + StorageService, + LogService, + TranslationService, + TranslationMock, + UserPreferencesService, + ContextMenuModule +} from '@alfresco/adf-core'; +@NgModule({ + imports: [ + HttpClientModule, + NoopAnimationsModule, + ProcessListCloudModule, + ContextMenuModule + ], + providers: [ + { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }, + { provide: AppConfigService, useClass: AppConfigServiceMock }, + { provide: TranslationService, useClass: TranslationMock }, + StorageService, + LogService, + UserPreferencesService + ] +}) +export class ProcessListCloudTestingModule { } 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 f6f61b741b..8e8c894323 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 @@ -3,12 +3,14 @@ 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'; @NgModule({ imports: [ AppListCloudModule, TaskListCloudModule, - TaskCloudModule + TaskCloudModule, + ProcessListCloudModule ], providers: [ { @@ -21,6 +23,6 @@ import { TaskCloudModule } from './task-cloud/task-cloud.module'; } ], declarations: [], - exports: [AppListCloudModule, TaskListCloudModule, TaskCloudModule] + exports: [AppListCloudModule, TaskListCloudModule, TaskCloudModule, ProcessListCloudModule] }) export class ProcessServicesCloudModule { } diff --git a/lib/process-services-cloud/src/lib/styles/_index.scss b/lib/process-services-cloud/src/lib/styles/_index.scss index 4da0dabfd5..387e4d8920 100644 --- a/lib/process-services-cloud/src/lib/styles/_index.scss +++ b/lib/process-services-cloud/src/lib/styles/_index.scss @@ -1,9 +1,11 @@ @import './../app-list-cloud/components/app-details-cloud.component'; @import './../app-list-cloud/components/app-list-cloud.component'; @import './../task-cloud/task-filters-cloud/task-filters-cloud.component.scss'; +@import './../process-list-cloud/components/process-list-cloud.component.scss'; @mixin adf-process-services-cloud-theme($theme) { @include adf-cloud-app-list-theme($theme); @include adf-cloud-app-details-theme($theme); @include adf-cloud-task-filters-theme($theme); + @include adf-process-filters-cloud-theme($theme); } diff --git a/lib/process-services-cloud/src/public-api.ts b/lib/process-services-cloud/src/public-api.ts index de7e278281..f2f374b5e0 100644 --- a/lib/process-services-cloud/src/public-api.ts +++ b/lib/process-services-cloud/src/public-api.ts @@ -19,3 +19,4 @@ export * from './lib/process-services-cloud.module'; export * from './lib/app-list-cloud/public-api'; export * from './lib/task-list-cloud/public-api'; export * from './lib/task-cloud/public-api'; +export * from './lib/process-list-cloud/public_api'; diff --git a/lib/process-services/mock/process/process-instances-list.mock.ts b/lib/process-services/mock/process/process-instances-list.mock.ts index c86731e127..3d33e3271b 100644 --- a/lib/process-services/mock/process/process-instances-list.mock.ts +++ b/lib/process-services/mock/process/process-instances-list.mock.ts @@ -115,6 +115,13 @@ export let fakeProcessInstancesWithNoName = { ] }; +export let fakeProcessInstancesEmpty = { + size: 0, + total: 0, + start: 0, + data: [] +}; + export let fakeProcessCustomSchema = [ new ObjectDataColumn({ key: 'fakeName', diff --git a/lib/process-services/mock/task/task-list.mock.ts b/lib/process-services/mock/task/task-list.mock.ts index 799f94927b..5e8605428b 100644 --- a/lib/process-services/mock/task/task-list.mock.ts +++ b/lib/process-services/mock/task/task-list.mock.ts @@ -104,3 +104,10 @@ export let fakeColumnSchema = { ], fakeCustomSchema }; + +export let fakeEmptyTask = { + size: 0, + start: 0, + total: 0, + data: [] +}; diff --git a/lib/process-services/process-list/components/process-list.component.spec.ts b/lib/process-services/process-list/components/process-list.component.spec.ts index e7f8460cca..db27e934df 100644 --- a/lib/process-services/process-list/components/process-list.component.spec.ts +++ b/lib/process-services/process-list/components/process-list.component.spec.ts @@ -15,20 +15,21 @@ * limitations under the License. */ -import { Component, SimpleChange, ViewChild, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { Component, SimpleChange, ViewChild } from '@angular/core'; import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { of, throwError } from 'rxjs'; import { By } from '@angular/platform-browser'; import { ProcessInstanceListComponent } from './process-list.component'; -import { AppConfigService, setupTestBed, CoreModule } from '@alfresco/adf-core'; +import { AppConfigService, setupTestBed, CoreModule, DataTableModule } from '@alfresco/adf-core'; import { DataRowEvent, ObjectDataRow, ObjectDataTableAdapter } from '@alfresco/adf-core'; -import { fakeProcessInstance, fakeProcessInstancesWithNoName } from '../../mock'; +import { fakeProcessInstance, fakeProcessInstancesWithNoName, fakeProcessInstancesEmpty } from '../../mock'; import { ProcessService } from '../services/process.service'; import { ProcessTestingModule } from '../../testing/process.testing.module'; import { fakeProcessCustomSchema } from '../../mock'; +import { ProcessListModule } from 'process-list/process-list.module'; describe('ProcessInstanceListComponent', () => { @@ -506,27 +507,28 @@ describe('CustomProcessListComponent', () => { @Component({ template: ` - - + +

No Process Instance

-
+
` }) class EmptyTemplateComponent { } - describe('Process List: Custom EmptyTemplateComponent', () => { let fixture: ComponentFixture; + let processService: ProcessService; setupTestBed({ - imports: [ProcessTestingModule], - declarations: [EmptyTemplateComponent], - schemas: [ CUSTOM_ELEMENTS_SCHEMA ] + imports: [ProcessTestingModule, ProcessListModule, DataTableModule], + declarations: [EmptyTemplateComponent] }); beforeEach(() => { fixture = TestBed.createComponent(EmptyTemplateComponent); + processService = TestBed.get(ProcessService); + spyOn(processService, 'getProcessInstances').and.returnValue(of(fakeProcessInstancesEmpty)); fixture.detectChanges(); }); @@ -534,13 +536,14 @@ describe('Process List: Custom EmptyTemplateComponent', () => { fixture.destroy(); }); - it('should render the custom template', async(() => { + it('should render the custom template', (done) => { fixture.whenStable().then(() => { fixture.detectChanges(); let title = fixture.debugElement.query(By.css('#custom-id')); expect(title).not.toBeNull(); expect(title.nativeElement.innerText).toBe('No Process Instance'); expect(fixture.debugElement.query(By.css('.adf-empty-content'))).toBeNull(); + done(); }); - })); + }); }); diff --git a/lib/process-services/task-list/components/task-list.component.spec.ts b/lib/process-services/task-list/components/task-list.component.spec.ts index dfdfacacde..af8834aec1 100644 --- a/lib/process-services/task-list/components/task-list.component.spec.ts +++ b/lib/process-services/task-list/components/task-list.component.spec.ts @@ -15,17 +15,18 @@ * limitations under the License. */ -import { Component, SimpleChange, ViewChild, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { Component, SimpleChange, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; -import { AppConfigService, setupTestBed, CoreModule } from '@alfresco/adf-core'; +import { AppConfigService, setupTestBed, CoreModule, DataTableModule } from '@alfresco/adf-core'; import { DataRowEvent, ObjectDataRow } from '@alfresco/adf-core'; import { TaskListService } from '../services/tasklist.service'; import { TaskListComponent } from './task-list.component'; import { ProcessTestingModule } from '../../testing/process.testing.module'; -import { fakeGlobalTask, fakeCustomSchema } from '../../mock'; +import { fakeGlobalTask, fakeCustomSchema, fakeEmptyTask } from '../../mock'; import { TranslateService } from '@ngx-translate/core'; import { of } from 'rxjs'; +import { TaskListModule } from 'task-list/task-list.module'; declare let jasmine: any; @@ -567,10 +568,10 @@ describe('CustomTaskListComponent', () => { @Component({ template: ` - - -

-
+ + +

CUSTOM EMPTY

+
` }) @@ -580,19 +581,20 @@ class EmptyTemplateComponent { describe('Task List: Custom EmptyTemplateComponent', () => { let fixture: ComponentFixture; let translateService: TranslateService; + let taskListService: TaskListService; setupTestBed({ - imports: [ProcessTestingModule], - declarations: [EmptyTemplateComponent], - schemas: [ CUSTOM_ELEMENTS_SCHEMA ] + imports: [ProcessTestingModule, TaskListModule, DataTableModule], + declarations: [EmptyTemplateComponent] }); beforeEach(() => { translateService = TestBed.get(TranslateService); + taskListService = TestBed.get(TaskListService); spyOn(translateService, 'get').and.callFake((key) => { return of(key); }); - + spyOn(taskListService, 'findTasksByState').and.returnValue(of(fakeEmptyTask)); fixture = TestBed.createComponent(EmptyTemplateComponent); fixture.detectChanges(); }); @@ -601,10 +603,12 @@ describe('Task List: Custom EmptyTemplateComponent', () => { fixture.destroy(); }); - it('should render the custom template', fakeAsync(() => { + it('should render the custom template', (done) => { fixture.detectChanges(); - tick(100); - expect(fixture.debugElement.query(By.css('#custom-id'))).not.toBeNull(); - expect(fixture.debugElement.query(By.css('.adf-empty-content'))).toBeNull(); - })); + fixture.whenStable().then(() => { + expect(fixture.debugElement.query(By.css('#custom-id'))).not.toBeNull(); + expect(fixture.debugElement.query(By.css('.adf-empty-content'))).toBeNull(); + done(); + }); + }); });