diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts index dba211c55e..a60127f4e9 100644 --- a/demo-shell/src/app/app.module.ts +++ b/demo-shell/src/app/app.module.ts @@ -74,6 +74,7 @@ import { StartTaskCloudDemoComponent } from './components/app-layout/cloud/start import { CloudBreadcrumbsComponent } from './components/app-layout/cloud/cloud-breadcrumb-component'; import { TasksCloudDemoComponent } from './components/app-layout/cloud/tasks-cloud-demo.component'; import { CloudFiltersDemoComponent } from './components/app-layout/cloud/cloud-filters-demo.component'; +import { StartProcessCloudDemoComponent } from './components/app-layout/cloud/start-process-cloud-demo.component'; @NgModule({ imports: [ @@ -130,6 +131,7 @@ import { CloudFiltersDemoComponent } from './components/app-layout/cloud/cloud-f ProcessesCloudDemoComponent, TaskDetailsCloudDemoComponent, StartTaskCloudDemoComponent, + StartProcessCloudDemoComponent, CloudBreadcrumbsComponent, CloudFiltersDemoComponent ], diff --git a/demo-shell/src/app/app.routes.ts b/demo-shell/src/app/app.routes.ts index a078e9dff7..d7e73248ce 100644 --- a/demo-shell/src/app/app.routes.ts +++ b/demo-shell/src/app/app.routes.ts @@ -29,10 +29,8 @@ import { FormNodeViewerComponent } from './components/process-service/form-node- import { AppsViewComponent } from './components/process-service/apps-view.component'; import { SearchResultComponent } from './components/search/search-result.component'; import { SearchExtendedComponent } from './components/search/search-extended.component'; - import { FilesComponent } from './components/files/files.component'; import { FormComponent } from './components/form/form.component'; - import { FormListComponent } from './components/form/form-list.component'; import { OverlayViewerComponent } from './components/overlay-viewer/overlay-viewer.component'; import { SharedLinkViewComponent } from './components/shared-link-view/shared-link-view.component'; @@ -47,6 +45,7 @@ import { TaskDetailsCloudDemoComponent } from './components/app-layout/cloud/tas import { AppsCloudDemoComponent } from './components/app-layout/cloud/apps-cloud-demo.component'; import { TasksCloudDemoComponent } from './components/app-layout/cloud/tasks-cloud-demo.component'; import { StartTaskCloudDemoComponent } from './components/app-layout/cloud/start-task-cloud-demo.component'; +import { StartProcessCloudDemoComponent } from './components/app-layout/cloud/start-process-cloud-demo.component'; export const appRoutes: Routes = [ { path: 'login', component: LoginComponent }, @@ -167,6 +166,10 @@ export const appRoutes: Routes = [ path: 'start-task', component: StartTaskCloudDemoComponent }, + { + path: 'start-process', + component: StartProcessCloudDemoComponent + }, { path: 'task-details/:taskId', component: TaskDetailsCloudDemoComponent diff --git a/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.html b/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.html index 5d86912600..0a4cffef7d 100644 --- a/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.html +++ b/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.html @@ -9,13 +9,19 @@ - arrow_drop_down - - - assessment - {{'ADF_SIDEBAR_ACTION_MENU.BUTTON.NEW_TASK' | translate}} - - + arrow_drop_down + + + assessment + {{'ADF_SIDEBAR_ACTION_MENU.BUTTON.NEW_TASK' | translate}} + + + + + assessment + {{'ADF_SIDEBAR_ACTION_MENU.BUTTON.NEW_PROCESS' | translate}} + + diff --git a/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.ts b/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.ts index fbe539bca0..690992e776 100644 --- a/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.ts +++ b/demo-shell/src/app/components/app-layout/cloud/cloud-layout.component.ts @@ -39,4 +39,8 @@ export class CloudLayoutComponent implements OnInit { onStartTask() { this.router.navigate([`/cloud/${this.applicationName}/start-task/`]); } + + onStartProcess() { + this.router.navigate([`/cloud/${this.applicationName}/start-process/`]); + } } diff --git a/demo-shell/src/app/components/app-layout/cloud/start-process-cloud-demo.component.html b/demo-shell/src/app/components/app-layout/cloud/start-process-cloud-demo.component.html new file mode 100644 index 0000000000..8b8c2e805c --- /dev/null +++ b/demo-shell/src/app/components/app-layout/cloud/start-process-cloud-demo.component.html @@ -0,0 +1,7 @@ + + diff --git a/demo-shell/src/app/components/app-layout/cloud/start-process-cloud-demo.component.scss b/demo-shell/src/app/components/app-layout/cloud/start-process-cloud-demo.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/demo-shell/src/app/components/app-layout/cloud/start-process-cloud-demo.component.ts b/demo-shell/src/app/components/app-layout/cloud/start-process-cloud-demo.component.ts new file mode 100644 index 0000000000..4bd02ea2f9 --- /dev/null +++ b/demo-shell/src/app/components/app-layout/cloud/start-process-cloud-demo.component.ts @@ -0,0 +1,61 @@ +/*! + * @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, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NotificationService, AppConfigService } from '@alfresco/adf-core'; +import { CloudLayoutService } from './services/cloud-layout.service'; +@Component({ + templateUrl: './start-process-cloud-demo.component.html', + styleUrls: ['./start-process-cloud-demo.component.scss'] +}) +export class StartProcessCloudDemoComponent implements OnInit { + + applicationName; + processName: string; + + constructor(private appConfig: AppConfigService, + private cloudLayoutService: CloudLayoutService, + private route: ActivatedRoute, + private notificationService: NotificationService, + private router: Router) { + } + + ngOnInit() { + this.route.parent.params.subscribe((params) => { + this.applicationName = params.applicationName; + }); + + this.processName = this.appConfig.get('adf-start-process.name'); + } + + onStartProcessSuccess() { + this.router.navigate([`/cloud/${this.applicationName}`]); + this.cloudLayoutService.setCurrentProcessFilterParam({ key: 'running-processes' }); + } + + onCancelStartProcess() { + this.router.navigate([`/cloud/${this.applicationName}`]); + } + + openSnackMessage(event: any) { + this.notificationService.openSnackMessage( + event.response.body.message, + 4000 + ); + } +} diff --git a/docs/process-services-cloud/start-process-cloud.component.md b/docs/process-services-cloud/start-process-cloud.component.md new file mode 100755 index 0000000000..7c9a858969 --- /dev/null +++ b/docs/process-services-cloud/start-process-cloud.component.md @@ -0,0 +1,49 @@ +# Start Process Cloud Component + +Starts a process. + + + +## Basic Usage + +```html + + +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| appName | `string` | | Limit the list of processes that can be started to those contained in the specified app. | +| name | `string` | "" | (optional) Name to assign to the current process. | +| processDefinitionName | `string` | | (optional) Definition name of the process to start. | +| showSelectProcessDropdown | `boolean` | true | (optional) Hide or show the process selection dropdown. | +| variables | `Map[]` | | (optional) Variables in the input to the process. | + +### Events + +| Name | Type | Description | +| ---- | ---- | ----------- | +| cancel | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`ProcessInstanceCloud`](../../lib/process-services-cloud/src/lib/process-cloud/models/process-instance-cloud.model.ts)`>` | Emitted when the process is canceled. | +| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`ProcessInstanceCloud`](../../lib/process-services-cloud/src/lib/process-cloud/models/process-instance-cloud.model.ts)`>` | Emitted when an error occurs. | +| success | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`ProcessInstanceCloud`](../../lib/process-services-cloud/src/lib/process-cloud/models/process-instance-cloud.model.ts)`>` | Emitted when the process starts. | + +## Details + +### Starting a process with a default name and a pre-selected process definition name + +```html + + +``` + +You can use the `processDefinitionName` property to select which process will be selected by default on the dropdown (when there is more than one process to choose from). Use the `name` property to set the name shown on the dropdown item. + +If the app contains only one process definition, this process definition will be selected by default diff --git a/e2e/pages/adf/demo-shell/processCloudDemoPage.ts b/e2e/pages/adf/demo-shell/processCloudDemoPage.ts index 3555c36963..9d27e00880 100644 --- a/e2e/pages/adf/demo-shell/processCloudDemoPage.ts +++ b/e2e/pages/adf/demo-shell/processCloudDemoPage.ts @@ -24,9 +24,9 @@ import { element, by } from 'protractor'; export class ProcessCloudDemoPage { - allProcesses = element(by.css('span[data-automation-id="ADF_CLOUD_PROCESS_FILTERS.ALL_PROCESSES_filter"]')); - runningProcesses = element(by.css('span[data-automation-id="ADF_CLOUD_PROCESS_FILTERS.RUNNING_PROCESSES_filter"]')); - completedProcesses = element(by.css('span[data-automation-id="ADF_CLOUD_PROCESS_FILTERS.COMPLETED_PROCESSES_filter"]')); + allProcesses = element(by.css('span[data-automation-id="all-processes_filter"]')); + runningProcesses = element(by.css('span[data-automation-id="running-processes_filter"]')); + completedProcesses = element(by.css('span[data-automation-id="completed-processes_filter"]')); activeFilter = element(by.css("mat-list-item[class*='active'] span")); processFilters = element(by.css("mat-expansion-panel[data-automation-id='Process Filters']")); diff --git a/e2e/pages/adf/material/datePickerPage.ts b/e2e/pages/adf/material/datePickerPage.ts index afab00346b..745de89cf2 100644 --- a/e2e/pages/adf/material/datePickerPage.ts +++ b/e2e/pages/adf/material/datePickerPage.ts @@ -62,10 +62,6 @@ export class DatePickerPage { return `${('0' + date.getDate()).slice(-2)}-${this.months[date.getMonth()]}-${date.getFullYear().toString().substr(-2)}`; } - convertDateToNewFormat(date) { // Format : mm-dd-yy - return `${date.getMonth() + 1}-${('0' + date.getDate()).slice(-2)}-${date.getFullYear().toString().substr(-2)}`; - } - selectTodayDate() { this.checkDatePickerIsDisplayed(); let todayDate = element(by.css('.mat-calendar-body-today')); diff --git a/e2e/search/components/search-date-range.e2e.ts b/e2e/search/components/search-date-range.e2e.ts index b847ff4d71..6f60b2031e 100644 --- a/e2e/search/components/search-date-range.e2e.ts +++ b/e2e/search/components/search-date-range.e2e.ts @@ -29,6 +29,7 @@ import TestConfig = require('../../test.config'); import AlfrescoApi = require('alfresco-js-api-node'); import { browser } from 'protractor'; +import moment from 'moment-es6'; describe('Search Date Range Filter', () => { @@ -199,6 +200,7 @@ describe('Search Date Range Filter', () => { describe('configuration change', () => { let jsonFile; + let dateFormat = 'MM-DD-YY'; beforeAll(() => { let searchConfiguration = new SearchConfiguration(); @@ -206,7 +208,7 @@ describe('Search Date Range Filter', () => { }); it('[C277117] Should be able to change date format', () => { - jsonFile.categories[4].component.settings.dateFormat = 'MM-DD-YY'; + jsonFile.categories[4].component.settings.dateFormat = dateFormat; navigationBar.clickConfigEditorButton(); configEditor.clickSearchConfiguration(); @@ -221,7 +223,7 @@ describe('Search Date Range Filter', () => { dateRangeFilter.checkFromFieldIsDisplayed() .openFromDatePicker(); - let todayDate = datePicker.convertDateToNewFormat(new Date()); + let todayDate = moment().format(dateFormat); datePicker.selectTodayDate(); browser.controlFlow().execute(async () => { diff --git a/lib/process-services-cloud/src/lib/i18n/en.json b/lib/process-services-cloud/src/lib/i18n/en.json index 249e9bfc32..910da9db0c 100644 --- a/lib/process-services-cloud/src/lib/i18n/en.json +++ b/lib/process-services-cloud/src/lib/i18n/en.json @@ -2,18 +2,40 @@ "ADF_CLOUD_PROCESS_LIST": { "MESSAGES": { "TITLE": "No Processes Found", - "SUBTITLE":"Create a new process that you want to easily find later", + "SUBTITLE": "Create a new process that you want to easily find later", "NONE": "No process instance filter selected." }, "PROPERTIES": { "NAME": "Name", "CREATED": "Created" + }, + "ADF_CLOUD_START_PROCESS": { + "BUTTON": "Start Process", + "NO_PROCESS_DEFINITIONS": "You can't start a process as there are no process definitions available", + "FORM": { + "TITLE": "Start Process", + "LABEL": { + "TYPE": "Select Process", + "NAME": "Process Name" + }, + "TYPE_PLACEHOLDER": "Choose one...", + "ACTION": { + "START": "Start Process", + "CANCEL": "Cancel" + } + }, + "ERROR": { + "LOAD_PROCESS_DEFS": "Couldn't load process definitions, check you have access.", + "START": "Couldn't start new process instance, check you have access.", + "PROCESS_NAME_REQUIRED": "Process Name is required", + "PROCESS_DEFINITION_REQUIRED": "Process Definition is required" + } } }, "ADF_CLOUD_TASK_LIST": { "APPS": { "TITLE": "No Applications Found", - "SUBTITLE":"Create a new application that you want to easily find later" + "SUBTITLE": "Create a new application that you want to easily find later" }, "START_TASK": { "FORM": { @@ -40,7 +62,7 @@ "LIST": { "MESSAGES": { "TITLE": "No Tasks Found", - "SUBTITLE":"Create a new task that you want to easily find later", + "SUBTITLE": "Create a new task that you want to easily find later", "NONE": "No task lists found" } } 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 463b59f6f5..2b9eeaec79 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 @@ -18,15 +18,13 @@ import { NgModule } from '@angular/core'; import { TRANSLATION_PROVIDER } from '@alfresco/adf-core'; import { AppListCloudModule } from './app/app-list-cloud.module'; -import { ProcessListCloudModule } from './process/process-list/process-list-cloud.module'; -import { ProcessFiltersCloudModule } from './process/process-filters/process-filters-cloud.module'; import { TaskCloudModule } from './task/task-cloud.module'; +import { ProcessCloudModule } from './process/process-cloud.module'; @NgModule({ imports: [ AppListCloudModule, - ProcessListCloudModule, - ProcessFiltersCloudModule, + ProcessCloudModule, TaskCloudModule ], providers: [ @@ -41,8 +39,7 @@ import { TaskCloudModule } from './task/task-cloud.module'; ], exports: [ AppListCloudModule, - ProcessListCloudModule, - ProcessFiltersCloudModule, + ProcessCloudModule, TaskCloudModule ] }) diff --git a/lib/process-services-cloud/src/lib/process/process-cloud.module.ts b/lib/process-services-cloud/src/lib/process/process-cloud.module.ts index 1bfd7d6e1e..6461f2ae88 100644 --- a/lib/process-services-cloud/src/lib/process/process-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/process/process-cloud.module.ts @@ -18,15 +18,18 @@ import { NgModule } from '@angular/core'; import { ProcessFiltersCloudModule } from './process-filters/process-filters-cloud.module'; import { ProcessListCloudModule } from './process-list/process-list-cloud.module'; +import { StartProcessCloudModule } from './start-process/start-process-cloud.module'; @NgModule({ imports: [ ProcessFiltersCloudModule, - ProcessListCloudModule + ProcessListCloudModule, + StartProcessCloudModule ], exports: [ ProcessFiltersCloudModule, - ProcessListCloudModule + ProcessListCloudModule, + StartProcessCloudModule ] }) export class ProcessCloudModule { } diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.html b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.html index 32eb520db1..c8b81c8b5d 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.html +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.html @@ -4,7 +4,7 @@ class="adf-filters__entry" [class.adf-active]="currentFilter === filter"> {{filter.icon}} - {{filter.name | translate}} + {{filter.name | translate}} diff --git a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts index 8e448bffdd..83170ab682 100644 --- a/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/process/process-filters/components/process-filters-cloud.component.spec.ts @@ -40,6 +40,7 @@ describe('ProcessFiltersCloudComponent', () => { }), new ProcessFilterCloudModel({ name: 'FakeRunningProcesses', + key: 'FakeRunningProcesses', icon: 'inbox', id: '11', state: 'RUNNING' diff --git a/lib/process-services-cloud/src/lib/process/public-api.ts b/lib/process-services-cloud/src/lib/process/public-api.ts index b2eb193955..2448b0f759 100644 --- a/lib/process-services-cloud/src/lib/process/public-api.ts +++ b/lib/process-services-cloud/src/lib/process/public-api.ts @@ -17,4 +17,6 @@ export * from './process-list/public-api'; export * from './process-filters/public-api'; +export * from './start-process/public-api'; + export * from './process-cloud.module'; diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html new file mode 100755 index 0000000000..484b47c11f --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.html @@ -0,0 +1,70 @@ + + + {{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.TITLE' | translate}} + + + + + {{ errorMessageId | translate }} + + + + + {{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.NAME' | translate }} + + + {{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.PROCESS_NAME_REQUIRED' | translate }} + + + + + {{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.TYPE' | translate }} + + + + + {{ processDef.name }} + + + + arrow_drop_down + + + + {{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.PROCESS_DEFINITION_REQUIRED' | translate }} + + + + + + + + + {{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.NO_PROCESS_DEFINITIONS' | translate | uppercase}} + + + + + + + {{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.ACTION.CANCEL' | translate | uppercase}} + + + {{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.ACTION.START' | translate | uppercase}} + + + \ No newline at end of file diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.scss b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.scss new file mode 100755 index 0000000000..beff34dd4e --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.scss @@ -0,0 +1,56 @@ +.adf { + &-start-process { + width: 66%; + margin-left: auto; + margin-right: auto; + margin-top: 10px; + margin-bottom: 5px; + + .mat-select-trigger { + font-size: 14px !important; + } + mat-form-field { + width: 100%; + } + mat-select { + width: 100%; + padding: 16px 0 0; + } + mat-card-actions { + text-align: right; + .mat-button { + text-transform: uppercase !important; + } + } + } + &-process-input-container { + mat-form-field { + width: 100%; + } + } + &-process-input-autocomplete { + display: flex; + button { + position: absolute; + right: -14px; + top: 0; + } + } + &-start-form-container { + .mat-card { + box-shadow: none !important; + padding: 0 !important; + } + } + &-start-form-actions { + text-align: right !important; + } +} + +@media (max-width: 600px) { + .adf-start-process { + width: 90%; + margin-left: auto; + margin-right: auto; + } +} diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts new file mode 100755 index 0000000000..2b1e467b67 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.spec.ts @@ -0,0 +1,380 @@ +/*! + * @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 { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { setupTestBed } from '@alfresco/adf-core'; +import { of, throwError } from 'rxjs'; +import { StartProcessCloudService } from '../services/start-process-cloud.service'; + +import { StartProcessCloudComponent } from './start-process-cloud.component'; +import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; +import { ProcessCloudModule } from '../../process-cloud.module'; +import { fakeProcessDefinitions, fakeProcessInstance, fakeProcessPayload } from '../mock/start-process.component.mock'; +import { By } from '@angular/platform-browser'; + +describe('StartProcessCloudComponent', () => { + + let component: StartProcessCloudComponent; + let fixture: ComponentFixture; + let processService: StartProcessCloudService; + let getDefinitionsSpy: jasmine.Spy; + let startProcessSpy: jasmine.Spy; + + setupTestBed({ + imports: [ + ProcessServiceCloudTestingModule, + ProcessCloudModule + ] + }); + + beforeEach(() => { + processService = TestBed.get(StartProcessCloudService); + fixture = TestBed.createComponent(StartProcessCloudComponent); + component = fixture.componentInstance; + + getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(of(fakeProcessDefinitions)); + startProcessSpy = spyOn(processService, 'startProcess').and.returnValue(of(fakeProcessInstance)); + }); + + afterEach(() => { + fixture.destroy(); + TestBed.resetTestingModule(); + }); + + it('should create instance of StartProcessInstanceComponent', () => { + expect(fixture.componentInstance instanceof StartProcessCloudComponent).toBe(true, 'should create StartProcessInstanceComponent'); + }); + + describe('first step', () => { + + describe('without start form', () => { + + beforeEach(() => { + fixture.detectChanges(); + component.name = 'My new process'; + component.appName = 'myApp'; + fixture.detectChanges(); + }); + + it('should enable start button when name and process filled out', async(() => { + spyOn(component, 'loadProcessDefinitions').and.callThrough(); + component.processDefinitionList = fakeProcessDefinitions; + component.processForm.controls['processInstanceName'].setValue('My Process 1'); + component.processForm.controls['processDefinition'].setValue('NewProcess 1'); + + fixture.detectChanges(); + + fixture.whenStable().then(() => { + let startBtn = fixture.nativeElement.querySelector('#button-start'); + expect(startBtn.disabled).toBe(false); + }); + })); + + it('should have start button disabled when name not filled out', async(() => { + spyOn(component, 'loadProcessDefinitions').and.callThrough(); + component.processForm.controls['processInstanceName'].setValue(''); + component.processForm.controls['processDefinition'].setValue(fakeProcessInstance.name); + fixture.detectChanges(); + fixture.whenStable().then(() => { + let startBtn = fixture.nativeElement.querySelector('#button-start'); + expect(startBtn.disabled).toBe(true); + }); + })); + + it('should have start button disabled when no process is selected', async(() => { + component.processPayloadCloud.processDefinitionKey = null; + fixture.detectChanges(); + fixture.whenStable().then(() => { + let startBtn = fixture.nativeElement.querySelector('#button-start'); + expect(startBtn.disabled).toBe(true); + }); + })); + }); + }); + + describe('process definitions list', () => { + + beforeEach(() => { + component.name = 'My new process'; + component.appName = 'myApp'; + fixture.detectChanges(); + let change = new SimpleChange(null, 'MyApp', true); + component.ngOnChanges({ 'appName': change }); + fixture.detectChanges(); + }); + + it('should call service to fetch process definitions with appId', () => { + fixture.whenStable().then(() => { + expect(getDefinitionsSpy).toHaveBeenCalledWith('myApp'); + }); + }); + + it('should display the correct number of processes in the select list', () => { + fixture.whenStable().then(() => { + let selectElement = fixture.nativeElement.querySelector('mat-select'); + expect(selectElement.children.length).toBe(1); + }); + }); + + it('should display the option def details', () => { + component.processDefinitionList = fakeProcessDefinitions; + fixture.detectChanges(); + fixture.whenStable().then(() => { + let selectElement = fixture.nativeElement.querySelector('mat-select > .mat-select-trigger'); + let optionElement = fixture.nativeElement.querySelectorAll('mat-option'); + selectElement.click(); + expect(selectElement).not.toBeNull(); + expect(selectElement).toBeDefined(); + expect(optionElement).not.toBeNull(); + expect(optionElement).toBeDefined(); + }); + }); + + it('should indicate an error to the user if process defs cannot be loaded', async(() => { + getDefinitionsSpy = getDefinitionsSpy.and.returnValue(throwError({})); + let change = new SimpleChange('myApp', 'myApp1', true); + component.ngOnChanges({ appName: change }); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + let errorEl = fixture.nativeElement.querySelector('#error-message'); + expect(errorEl.innerText.trim()).toBe('ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.LOAD_PROCESS_DEFS'); + }); + })); + + it('should show no process available message when no process definition is loaded', async(() => { + getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of([])); + let change = new SimpleChange('myApp', 'myApp1', true); + component.ngOnChanges({ appName: change }); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const noProcessElement = fixture.nativeElement.querySelector('#no-process-message'); + expect(noProcessElement).not.toBeNull('Expected no available process message to be present'); + expect(noProcessElement.innerText.trim()).toBe('ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.NO_PROCESS_DEFINITIONS'); + }); + })); + + it('should select processDefinition based on processDefinition input', async(() => { + getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions)); + component.processForm.controls['processInstanceName'].setValue('NewProcess 1'); + component.processForm.controls['processDefinition'].setValue('NewProcess 1'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(component.processPayloadCloud.processDefinitionKey).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[0])).name); + }); + })); + + it('should select automatically the processDefinition if the app contain only one', async(() => { + getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of([fakeProcessDefinitions[0]])); + let change = new SimpleChange('myApp', 'myApp1', true); + component.ngOnChanges({ appName: change }); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(component.processForm.controls['processDefinition'].value).toBe(JSON.parse(JSON.stringify(fakeProcessDefinitions[0])).name); + }); + })); + + it('should not select automatically any processDefinition if the app contain multiple process and does not have any processDefinition as input', async(() => { + getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions)); + component.appName = 'myApp'; + component.ngOnChanges({}); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(component.processPayloadCloud.processInstanceName).toBeNull(); + }); + })); + + describe('dropdown', () => { + + it('should hide the process dropdown button if showSelectProcessDropdown is false', async(() => { + fixture.detectChanges(); + getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions)); + component.appName = 'myApp'; + component.showSelectProcessDropdown = false; + component.ngOnChanges({}); + fixture.detectChanges(); + fixture.whenStable().then(() => { + let selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown'); + expect(selectElement).toBeNull(); + }); + })); + + it('should show the process dropdown button if showSelectProcessDropdown is false', async(() => { + fixture.detectChanges(); + getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions)); + component.appName = 'myApp'; + component.processDefinitionName = 'NewProcess 2'; + component.showSelectProcessDropdown = true; + component.ngOnChanges({}); + fixture.detectChanges(); + fixture.whenStable().then(() => { + let selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown'); + expect(selectElement).not.toBeNull(); + }); + })); + + it('should show the process dropdown button by default', async(() => { + fixture.detectChanges(); + getDefinitionsSpy = getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions)); + component.appName = 'myApp'; + component.processDefinitionName = 'NewProcess 2'; + component.ngOnChanges({}); + fixture.detectChanges(); + fixture.whenStable().then(() => { + let selectElement = fixture.nativeElement.querySelector('button#adf-select-process-dropdown'); + expect(selectElement).not.toBeNull(); + }); + })); + }); + }); + + describe('input changes', () => { + + let change = new SimpleChange('myApp', 'myApp1', true); + + beforeEach(async(() => { + component.appName = 'myApp'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + getDefinitionsSpy.calls.reset(); + }); + })); + + it('should reload processes when appId input changed', async(() => { + component.ngOnChanges({ appName: change }); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(getDefinitionsSpy).toHaveBeenCalledWith('myApp1'); + }); + })); + + it('should get current processDef', () => { + component.ngOnChanges({ appName: change }); + fixture.detectChanges(); + expect(getDefinitionsSpy).toHaveBeenCalled(); + expect(component.processDefinitionList).toBe(fakeProcessDefinitions); + }); + }); + + describe('start process', () => { + + beforeEach(() => { + fixture.detectChanges(); + component.name = 'NewProcess 1'; + component.appName = 'myApp'; + component.ngOnChanges({}); + }); + + it('should call service to start process if required fields provided', async(() => { + component.processPayloadCloud = fakeProcessPayload; + component.startProcess(); + fixture.whenStable().then(() => { + expect(startProcessSpy).toHaveBeenCalled(); + }); + })); + + it('should avoid calling service to start process if required fields NOT provided', async(() => { + component.processForm.controls['processInstanceName'].setValue(''); + component.processForm.controls['processDefinition'].setValue(''); + fixture.whenStable().then(() => { + let startProcessButton = fixture.debugElement.query(By.css('[data-automation-id="btn-start"]')); + expect(startProcessButton.nativeElement.disabled).toBeTruthy(); + }); + })); + + it('should call service to start process with the correct parameters', async(() => { + component.processPayloadCloud = fakeProcessPayload; + component.startProcess(); + fixture.whenStable().then(() => { + expect(startProcessSpy).toHaveBeenCalledWith('myApp', fakeProcessPayload); + }); + })); + + it('should call service to start process with the variables setted', async(() => { + let inputProcessVariable: Map[] = []; + inputProcessVariable['name'] = {value: 'Josh'}; + + component.variables = inputProcessVariable; + component.processPayloadCloud = fakeProcessPayload; + + component.startProcess(); + fixture.whenStable().then(() => { + expect(component.processPayloadCloud.variables).toBe(inputProcessVariable); + }); + })); + + it('should output start event when process started successfully', async(() => { + let emitSpy = spyOn(component.success, 'emit'); + component.processPayloadCloud = fakeProcessPayload; + component.startProcess(); + fixture.whenStable().then(() => { + expect(emitSpy).toHaveBeenCalledWith(fakeProcessInstance); + }); + })); + + it('should throw error event when process cannot be started', async(() => { + let errorSpy = spyOn(component.error, 'emit'); + let error = { message: 'My error' }; + startProcessSpy = startProcessSpy.and.returnValue(throwError(error)); + component.processPayloadCloud = fakeProcessPayload; + component.startProcess(); + fixture.whenStable().then(() => { + expect(errorSpy).toHaveBeenCalledWith(error); + }); + })); + + it('should indicate an error to the user if process cannot be started', async(() => { + getDefinitionsSpy.and.returnValue(of(fakeProcessDefinitions)); + let change = new SimpleChange('myApp', 'myApp1', true); + component.ngOnChanges({ appName: change }); + startProcessSpy = startProcessSpy.and.returnValue(throwError({})); + component.startProcess(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + let errorEl = fixture.nativeElement.querySelector('#error-message'); + expect(errorEl.innerText.trim()).toBe('ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.START'); + }); + })); + + it('should emit start event when start select a process and add a name', (done) => { + let disposableStart = component.success.subscribe(() => { + disposableStart.unsubscribe(); + done(); + }); + + component.processPayloadCloud = fakeProcessPayload; + component.name = 'NewProcess 1'; + component.startProcess(); + fixture.detectChanges(); + }); + + it('should able to start the process when the required fields are filled up', (done) => { + component.processForm.controls['processInstanceName'].setValue('My Process 1'); + component.processForm.controls['processDefinition'].setValue('NewProcess 1'); + + let disposableStart = component.success.subscribe(() => { + disposableStart.unsubscribe(); + done(); + }); + + component.startProcess(); + }); + }); +}); diff --git a/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts new file mode 100755 index 0000000000..79f4eed74a --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/components/start-process-cloud.component.ts @@ -0,0 +1,237 @@ +/*! + * @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, OnInit, + Output, SimpleChanges, ViewChild, ViewEncapsulation +} from '@angular/core'; + +import { ProcessInstanceCloud } from '../models/process-instance-cloud.model'; +import { StartProcessCloudService } from '../services/start-process-cloud.service'; +import { FormControl, Validators, FormGroup, AbstractControl, FormBuilder, ValidatorFn } from '@angular/forms'; +import { MatAutocompleteTrigger } from '@angular/material'; +import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; +import { debounceTime } from 'rxjs/operators'; +import { ProcessDefinitionCloud } from '../models/process-definition-cloud.model'; + +@Component({ + selector: 'adf-cloud-start-process', + templateUrl: './start-process-cloud.component.html', + styleUrls: ['./start-process-cloud.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class StartProcessCloudComponent implements OnChanges, OnInit { + + @ViewChild(MatAutocompleteTrigger) + inputAutocomplete: MatAutocompleteTrigger; + + /** (required) Name of the app. */ + @Input() + appName: string; + + /** Name of the process. */ + @Input() + name: string = ''; + + /** Name of the process definition. */ + @Input() + processDefinitionName: string; + + /** Variables to attach to the payload */ + @Input() + variables: Map[]; + + /** This flag displays/hides the process dropdown list */ + @Input() + showSelectProcessDropdown: boolean = true; + + /** Emitted when the starting process is successfully created. */ + @Output() + success: EventEmitter = new EventEmitter(); + + /** Emitted when the starting process is cancelled */ + @Output() + cancel: EventEmitter = new EventEmitter(); + + /** Emitted when an error occurs. */ + @Output() + error: EventEmitter = new EventEmitter(); + + processDefinitionList: ProcessDefinitionCloud[] = []; + errorMessageId: string = ''; + processForm: FormGroup; + processPayloadCloud = new ProcessPayloadCloud(); + filteredProcesses: ProcessDefinitionCloud[] = []; + isLoading = false; + + constructor(private startProcessCloudService: StartProcessCloudService, + private formBuilder: FormBuilder) { + } + + ngOnInit() { + this.processForm = this.formBuilder.group({ + processInstanceName: new FormControl(this.name, Validators.required), + processDefinition: new FormControl('', [Validators.required, this.processDefinitionNameValidator()]) + }); + + this.processDefinition.valueChanges + .pipe(debounceTime(300)) + .subscribe((processDefinitionName) => { + this.processPayloadCloud.processDefinitionKey = null; + if (this.processDefinition.valid) { + this.setProcessDefinitionOnForm(processDefinitionName); + } + }); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes['appName'] && changes['appName'].currentValue !== changes['appName'].previousValue) { + this.appName = changes['appName'].currentValue; + this.loadProcessDefinitions(); + } + } + + setProcessDefinitionOnForm(processDefinitionName: string) { + this.filteredProcesses = this.getProcessDefinitionList(processDefinitionName); + const selectedProcess = this.getProcessIfExists(processDefinitionName); + this.processPayloadCloud.processDefinitionKey = selectedProcess.key; + } + + private getProcessDefinitionList(processDefinitionName: string): ProcessDefinitionCloud[] { + return this.processDefinitionList.filter((option) => option.name.toLowerCase().includes(processDefinitionName.toLowerCase())); + } + + private getProcessIfExists(processDefinitionName: string): ProcessDefinitionCloud { + let matchedProcess = this.processDefinitionList.find((option) => option.name.toLowerCase() === processDefinitionName.toLowerCase()); + if (!matchedProcess) { + matchedProcess = new ProcessDefinitionCloud(); + } + + return matchedProcess; + } + + private getProcessDefinitionByName(processDefinitionName: string): ProcessDefinitionCloud { + const matchedProcess = processDefinitionName ? this.getProcessIfExists(processDefinitionName) : this.processDefinitionList[0]; + return matchedProcess; + } + + private selectDefaultProcessDefinition() { + let selectedProcess = this.getProcessDefinitionByName(this.processDefinitionName); + if (selectedProcess) { + this.processForm.controls['processDefinition'].setValue(selectedProcess.name); + this.processPayloadCloud.processDefinitionKey = selectedProcess.key; + } + } + + public loadProcessDefinitions() { + this.resetErrorMessage(); + + this.startProcessCloudService.getProcessDefinitions(this.appName).subscribe( + (processDefinitionRepresentations: ProcessDefinitionCloud[]) => { + this.processDefinitionList = processDefinitionRepresentations; + if (processDefinitionRepresentations.length > 0) { + this.selectDefaultProcessDefinition(); + } + }, + () => { + this.errorMessageId = 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.LOAD_PROCESS_DEFS'; + }); + } + + isProcessDefinitionsEmpty(): boolean { + return this.processDefinitionList.length === 0; + } + + startProcess() { + this.isLoading = true; + + this.processPayloadCloud.processInstanceName = this.processInstanceName.value; + this.processPayloadCloud.payloadType = 'StartProcessPayload'; + if (this.variables) { + this.processPayloadCloud.variables = this.variables; + } + + this.startProcessCloudService.startProcess(this.appName, this.processPayloadCloud).subscribe( + (res) => { + this.success.emit(res); + this.isLoading = false; + }, + (err) => { + this.errorMessageId = 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.START'; + this.error.emit(err); + this.isLoading = false; + } + ); + } + + cancelStartProcess() { + this.cancel.emit(); + } + + private resetErrorMessage() { + this.errorMessageId = ''; + } + + private resetProcessDefinitionList() { + this.processForm.controls['processDefinition'].setValue(''); + this.filteredProcesses = this.processDefinitionList; + } + + displayProcessNameOnDropdown(process: any) { + if (process) { + let processName = process; + if (typeof process !== 'string') { + processName = process.name; + } + return processName; + } + } + + displayDropdown(event) { + event.stopPropagation(); + if (!this.inputAutocomplete.panelOpen) { + this.resetProcessDefinitionList(); + this.inputAutocomplete.openPanel(); + } else { + this.inputAutocomplete.closePanel(); + } + } + + processDefinitionNameValidator(): ValidatorFn { + return (control: AbstractControl): { [key: string]: any } | null => { + const processDefinitionFieldValue = control.value; + let processDefinitionNameError = false; + + if (processDefinitionFieldValue) { + const processDefinition = this.getProcessIfExists(processDefinitionFieldValue); + if (!processDefinition.key) { + processDefinitionNameError = true; + } + } + + return processDefinitionNameError ? { 'invalid name': true } : null; + }; + } + + get processInstanceName(): AbstractControl { + return this.processForm.get('processInstanceName'); + } + + get processDefinition(): AbstractControl { + return this.processForm.get('processDefinition'); + } +} diff --git a/lib/process-services-cloud/src/lib/process/start-process/mock/start-process.component.mock.ts b/lib/process-services-cloud/src/lib/process/start-process/mock/start-process.component.mock.ts new file mode 100755 index 0000000000..5098dae02f --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/mock/start-process.component.mock.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 { ProcessInstanceCloud } from '../models/process-instance-cloud.model'; +import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; +import { ProcessDefinitionCloud } from '../models/process-definition-cloud.model'; + +export let fakeProcessInstance = new ProcessInstanceCloud({ + appName: 'simple-app', + appVersion: '', + serviceName: 'simple-app-rb', + serviceFullName: 'simple-app-rb', + serviceType: 'runtime-bundle', + serviceVersion: '', + id: 'd0b30377-dc5a-11e8-ae24-0a58646001fa', + name: 'My Process Name', + startDate: '2018-10-30T15:45:24.136+0000', + initiator: 'usermock', + status: 'RUNNING', + processDefinitionId: 'BasicProcess:1:d05062f1-c6fb-11e8-ae24-0a58646001fa', + processDefinitionKey: 'BasicProcess' +}); + +export let fakeProcessDefinitions: ProcessDefinitionCloud[] = [ + new ProcessDefinitionCloud({ + appName: 'myApp', + appVersion: 0, + id: 'NewProcess:1', + key: 'NewProcess 1', + name: 'NewProcess 1', + serviceFullName: 'myApp-rb', + serviceName: 'myApp-rb', + serviceType: 'runtime-bundle', + serviceVersion: null + }), + new ProcessDefinitionCloud({ + appName: 'myApp', + appVersion: 0, + id: 'NewProcess:2', + key: 'NewProcess 2', + name: 'NewProcess 2', + serviceFullName: 'myApp-rb', + serviceName: 'myApp-rb', + serviceType: 'runtime-bundle', + serviceVersion: null + }) +]; + +export let fakeProcessPayload = new ProcessPayloadCloud({ + processDefinitionKey: 'NewProcess:1', + processInstanceName: 'NewProcess 1', + payloadType: 'string' +}); diff --git a/lib/process-services-cloud/src/lib/process/start-process/models/process-definition-cloud.model.ts b/lib/process-services-cloud/src/lib/process/start-process/models/process-definition-cloud.model.ts new file mode 100755 index 0000000000..41d6a19356 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/models/process-definition-cloud.model.ts @@ -0,0 +1,42 @@ +/*! + * @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 ProcessDefinitionCloud { + id: string; + appName: string; + key: string; + appVersion: number; + version: number; + name: string; + serviceFullName: string; + serviceName: string; + serviceType: string; + serviceVersion: string; + + constructor(obj?: any) { + this.id = obj && obj.id || null; + this.name = obj && obj.name || null; + this.appName = obj && obj.appName || null; + this.key = obj && obj.key || null; + this.version = obj && obj.version || 0; + this.appVersion = obj && obj.appVersion || 0; + this.serviceFullName = obj && obj.serviceFullName || null; + this.serviceType = obj && obj.serviceType || null; + this.serviceName = obj && obj.serviceName || null; + this.serviceVersion = obj && obj.serviceVersion || null; + } +} diff --git a/lib/process-services-cloud/src/lib/process/start-process/models/process-instance-cloud.model.ts b/lib/process-services-cloud/src/lib/process/start-process/models/process-instance-cloud.model.ts new file mode 100755 index 0000000000..7bb7d1c6e0 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/models/process-instance-cloud.model.ts @@ -0,0 +1,38 @@ +/*! + * @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 ProcessInstanceCloud { + appName: string; + id: string; + name: string; + startDate: Date; + initiator: string; + status: string; + processDefinitionId: string; + processDefinitionKey: string; + + constructor(obj?: any) { + this.appName = obj && obj.appName || null; + this.id = obj && obj.id || null; + this.name = obj && obj.name || null; + this.startDate = obj && obj.startDate || null; + this.initiator = obj && obj.initiator || null; + this.status = obj && obj.status || null; + this.processDefinitionId = obj && obj.processDefinitionId || null; + this.processDefinitionKey = obj && obj.processDefinitionKey || null; + } +} diff --git a/lib/process-services-cloud/src/lib/process/start-process/models/process-payload-cloud.model.ts b/lib/process-services-cloud/src/lib/process/start-process/models/process-payload-cloud.model.ts new file mode 100755 index 0000000000..15f35c0c1f --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/models/process-payload-cloud.model.ts @@ -0,0 +1,32 @@ +/*! + * @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 ProcessPayloadCloud { + processDefinitionKey: string; + processInstanceName: string; + businessKey: string; + variables: Map[]; + payloadType: string; + + constructor(obj?: any) { + this.processDefinitionKey = obj && obj.processDefinitionKey ? obj.processDefinitionKey : null; + this.processInstanceName = obj && obj.processInstanceName ? obj.processInstanceName : null; + this.businessKey = obj && obj.businessKey ? obj.businessKey : null; + this.variables = obj && obj.variables ? obj.variables : null; + this.payloadType = obj && obj.valueUrl ? obj.payloadType : null; + } +} diff --git a/lib/process-services-cloud/src/lib/process/start-process/public-api.ts b/lib/process-services-cloud/src/lib/process/start-process/public-api.ts new file mode 100644 index 0000000000..2a0a993ffa --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/public-api.ts @@ -0,0 +1,25 @@ +/*! + * @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/start-process-cloud.component'; + +export * from './models/process-definition-cloud.model'; +export * from './models/process-instance-cloud.model'; +export * from './models/process-payload-cloud.model'; + +export * from './services/start-process-cloud.service'; +export * from './start-process-cloud.module'; diff --git a/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.spec.ts new file mode 100755 index 0000000000..1d0c68f915 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.spec.ts @@ -0,0 +1,111 @@ +/*! + * @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 { TestBed } from '@angular/core/testing'; +import { setupTestBed } from '@alfresco/adf-core'; +import { of, throwError } from 'rxjs'; +import { + AlfrescoApiService, + AppConfigService, + LogService, + StorageService +} from '@alfresco/adf-core'; +import { StartProcessCloudService } from './start-process-cloud.service'; +import { fakeProcessPayload } from '../mock/start-process.component.mock'; +import { ProcessInstanceCloud } from '../models/process-instance-cloud.model'; +import { HttpErrorResponse } from '@angular/common/http'; +import { ProcessDefinitionCloud } from '../models/process-definition-cloud.model'; +import { ProcessCloudModule } from '../../process-cloud.module'; + +describe('StartTaskCloudService', () => { + + let service: StartProcessCloudService; + + setupTestBed({ + imports: [ProcessCloudModule], + providers: [StartProcessCloudService, AlfrescoApiService, AppConfigService, LogService, StorageService] + }); + + beforeEach(() => { + service = TestBed.get(StartProcessCloudService); + }); + + it('should be able to create a new process', (done) => { + spyOn(service, 'startProcess').and.returnValue(of({ id: 'fake-id', name: 'fake-name' })); + service.startProcess('appName1', fakeProcessPayload) + .subscribe( + (res: ProcessInstanceCloud) => { + expect(res).toBeDefined(); + expect(res.id).toEqual('fake-id'); + expect(res.name).toEqual('fake-name'); + done(); + } + ); + }); + + it('Should not be able to create a process if error occurred', () => { + const errorResponse = new HttpErrorResponse({ + error: 'Mock Error', + status: 404, statusText: 'Not Found' + }); + + spyOn(service, 'startProcess').and.returnValue(throwError(errorResponse)); + service.startProcess('appName1', fakeProcessPayload) + .subscribe( + () => { + fail('expected an error, not applications'); + }, + (error) => { + expect(error.status).toEqual(404); + expect(error.statusText).toEqual('Not Found'); + expect(error.error).toEqual('Mock Error'); + } + ); + }); + + it('should be able to get all the process definitions', (done) => { + spyOn(service, 'getProcessDefinitions').and.returnValue(of([{ id: 'fake-id', name: 'fake-name' }])); + service.getProcessDefinitions('appName1') + .subscribe( + (res: ProcessDefinitionCloud[]) => { + expect(res).toBeDefined(); + expect(res[0].id).toEqual('fake-id'); + expect(res[0].name).toEqual('fake-name'); + done(); + } + ); + }); + + it('should not be able to get all the process definitions if error occurred', () => { + const errorResponse = new HttpErrorResponse({ + error: 'Mock Error', + status: 404, statusText: 'Not Found' + }); + spyOn(service, 'getProcessDefinitions').and.returnValue(throwError(errorResponse)); + service.getProcessDefinitions('appName1') + .subscribe( + () => { + fail('expected an error, not applications'); + }, + (error) => { + expect(error.status).toEqual(404); + expect(error.statusText).toEqual('Not Found'); + expect(error.error).toEqual('Mock Error'); + } + ); + }); +}); diff --git a/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts new file mode 100755 index 0000000000..35fee6df4f --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/services/start-process-cloud.service.ts @@ -0,0 +1,99 @@ +/*! + * @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 { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core'; +import { Injectable } from '@angular/core'; +import { Observable, from, throwError } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; +import { ProcessInstanceCloud } from '../models/process-instance-cloud.model'; +import { ProcessPayloadCloud } from '../models/process-payload-cloud.model'; +import { ProcessDefinitionCloud } from '../models/process-definition-cloud.model'; + +@Injectable({ + providedIn: 'root' +}) +export class StartProcessCloudService { + + contextRoot: string; + contentTypes = ['application/json']; + accepts = ['application/json']; + returnType = Object; + + constructor(private alfrescoApiService: AlfrescoApiService, + private appConfigService: AppConfigService, + private logService: LogService) { + this.contextRoot = this.appConfigService.get('bpmHost', ''); + } + + /** + * Gets process definitions associated with an app. + * @param appId ID of a target app + * @returns Array of process definitions + */ + getProcessDefinitions(appName: string): Observable { + + if (appName) { + let queryUrl = `${this.contextRoot}/${appName}-rb/v1/process-definitions`; + + return from(this.alfrescoApiService.getInstance() + .oauth2Auth.callCustomApi(queryUrl, 'GET', + null, null, null, + null, null, null, + this.contentTypes, this.accepts, + this.returnType, null, null) + ).pipe( + map((res: any) => { + return res.list.entries.map((processDefs) => new ProcessDefinitionCloud(processDefs.entry)); + }), + catchError((err) => this.handleProcessError(err)) + ); + } else { + this.logService.error('AppName is mandatory for querying task'); + return throwError('AppName not configured'); + } + } + + /** + * Starts a process based on a process definition, name, form values or variables. + * @param appName name of the Application + * @param processDefinitionId Process definition ID + * @param name Process name + * @param outcome Process outcome + * @param startFormValues Values for the start form + * @param variables Array of process instance variables + * @returns Details of the process instance just started + */ + startProcess(appName: string, requestPayload: ProcessPayloadCloud): Observable { + + let queryUrl = `${this.contextRoot}/${appName}-rb/v1/process-instances`; + + return from(this.alfrescoApiService.getInstance() + .oauth2Auth.callCustomApi(queryUrl, 'POST', + null, null, null, + null, requestPayload, null, + this.contentTypes, this.accepts, + this.returnType, null, null) + ).pipe( + map((processInstance) => new ProcessInstanceCloud(processInstance)), + catchError((err) => this.handleProcessError(err)) + ); + } + + private handleProcessError(error: any) { + return throwError(error || 'Server error'); + } +} diff --git a/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.spec.ts b/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.spec.ts new file mode 100755 index 0000000000..d439b1fe69 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.spec.ts @@ -0,0 +1,30 @@ +/*! + * @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 { StartProcessCloudModule } from './start-process-cloud.module'; + +describe('ProcessCloudModule', () => { + let startProcessCloudModule: StartProcessCloudModule; + + beforeEach(() => { + startProcessCloudModule = new StartProcessCloudModule(); + }); + + it('should create an instance', () => { + expect(startProcessCloudModule).toBeTruthy(); + }); +}); diff --git a/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.ts b/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.ts new file mode 100644 index 0000000000..5af51d4006 --- /dev/null +++ b/lib/process-services-cloud/src/lib/process/start-process/start-process-cloud.module.ts @@ -0,0 +1,51 @@ +/*! + * @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 { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { MaterialModule } from '../../material.module'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; +import { TranslateLoaderService } from '@alfresco/adf-core'; +import { StartProcessCloudComponent } from './components/start-process-cloud.component'; +import { StartProcessCloudService } from './services/start-process-cloud.service'; +@NgModule({ + imports: [ + FormsModule, + CommonModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderService + } + }), + MaterialModule, + FlexLayoutModule, + ReactiveFormsModule + ], + declarations: [ + StartProcessCloudComponent + ], + exports: [ + StartProcessCloudComponent + ], + providers: [ + StartProcessCloudService + ] +}) +export class StartProcessCloudModule { }