[ADF-3543] Start Process Component for APS 2 (#4105)

* [ADF-3543] Start Process Component for APS 2

* [ADF-3543] Fix e2e tests

* [ADF-3543] Process filter data automation id with process key

* [ADF-3543] Fix Process Services e2e tests

* [ADF-3543] Fix Search e2e tests

* [ADF-3543] Fix Search e2e tests
This commit is contained in:
davidcanonieto 2019-01-04 13:22:03 +01:00 committed by Maurizio Vitale
parent dd25467a98
commit 2acd1b4e26
30 changed files with 1423 additions and 30 deletions

View File

@ -74,6 +74,7 @@ import { StartTaskCloudDemoComponent } from './components/app-layout/cloud/start
import { CloudBreadcrumbsComponent } from './components/app-layout/cloud/cloud-breadcrumb-component'; import { CloudBreadcrumbsComponent } from './components/app-layout/cloud/cloud-breadcrumb-component';
import { TasksCloudDemoComponent } from './components/app-layout/cloud/tasks-cloud-demo.component'; import { TasksCloudDemoComponent } from './components/app-layout/cloud/tasks-cloud-demo.component';
import { CloudFiltersDemoComponent } from './components/app-layout/cloud/cloud-filters-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({ @NgModule({
imports: [ imports: [
@ -130,6 +131,7 @@ import { CloudFiltersDemoComponent } from './components/app-layout/cloud/cloud-f
ProcessesCloudDemoComponent, ProcessesCloudDemoComponent,
TaskDetailsCloudDemoComponent, TaskDetailsCloudDemoComponent,
StartTaskCloudDemoComponent, StartTaskCloudDemoComponent,
StartProcessCloudDemoComponent,
CloudBreadcrumbsComponent, CloudBreadcrumbsComponent,
CloudFiltersDemoComponent CloudFiltersDemoComponent
], ],

View File

@ -29,10 +29,8 @@ import { FormNodeViewerComponent } from './components/process-service/form-node-
import { AppsViewComponent } from './components/process-service/apps-view.component'; import { AppsViewComponent } from './components/process-service/apps-view.component';
import { SearchResultComponent } from './components/search/search-result.component'; import { SearchResultComponent } from './components/search/search-result.component';
import { SearchExtendedComponent } from './components/search/search-extended.component'; import { SearchExtendedComponent } from './components/search/search-extended.component';
import { FilesComponent } from './components/files/files.component'; import { FilesComponent } from './components/files/files.component';
import { FormComponent } from './components/form/form.component'; import { FormComponent } from './components/form/form.component';
import { FormListComponent } from './components/form/form-list.component'; import { FormListComponent } from './components/form/form-list.component';
import { OverlayViewerComponent } from './components/overlay-viewer/overlay-viewer.component'; import { OverlayViewerComponent } from './components/overlay-viewer/overlay-viewer.component';
import { SharedLinkViewComponent } from './components/shared-link-view/shared-link-view.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 { AppsCloudDemoComponent } from './components/app-layout/cloud/apps-cloud-demo.component';
import { TasksCloudDemoComponent } from './components/app-layout/cloud/tasks-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 { 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 = [ export const appRoutes: Routes = [
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },
@ -167,6 +166,10 @@ export const appRoutes: Routes = [
path: 'start-task', path: 'start-task',
component: StartTaskCloudDemoComponent component: StartTaskCloudDemoComponent
}, },
{
path: 'start-process',
component: StartProcessCloudDemoComponent
},
{ {
path: 'task-details/:taskId', path: 'task-details/:taskId',
component: TaskDetailsCloudDemoComponent component: TaskDetailsCloudDemoComponent

View File

@ -9,13 +9,19 @@
<ng-template> <ng-template>
<adf-sidebar-action-menu [expanded]="true" [width]="205" <adf-sidebar-action-menu [expanded]="true" [width]="205"
title="{{'ADF_SIDEBAR_ACTION_MENU.BUTTON.CREATE' | translate}}"> title="{{'ADF_SIDEBAR_ACTION_MENU.BUTTON.CREATE' | translate}}">
<mat-icon sidebar-menu-title-icon>arrow_drop_down</mat-icon> <mat-icon sidebar-menu-title-icon>arrow_drop_down</mat-icon>
<div sidebar-menu-options> <div sidebar-menu-options>
<button mat-menu-item data-automation-id="btn-start-task" (click)="onStartTask()"> <button mat-menu-item data-automation-id="btn-start-task" (click)="onStartTask()">
<mat-icon>assessment</mat-icon> <mat-icon>assessment</mat-icon>
<span>{{'ADF_SIDEBAR_ACTION_MENU.BUTTON.NEW_TASK' | translate}}</span> <span>{{'ADF_SIDEBAR_ACTION_MENU.BUTTON.NEW_TASK' | translate}}</span>
</button> </button>
</div> </div>
<div sidebar-menu-options>
<button mat-menu-item data-automation-id="btn-start-process" (click)="onStartProcess()">
<mat-icon>assessment</mat-icon>
<span>{{'ADF_SIDEBAR_ACTION_MENU.BUTTON.NEW_PROCESS' | translate}}</span>
</button>
</div>
</adf-sidebar-action-menu> </adf-sidebar-action-menu>
<app-cloud-filters-demo [appName]="applicationName"></app-cloud-filters-demo> <app-cloud-filters-demo [appName]="applicationName"></app-cloud-filters-demo>
</ng-template> </ng-template>

View File

@ -39,4 +39,8 @@ export class CloudLayoutComponent implements OnInit {
onStartTask() { onStartTask() {
this.router.navigate([`/cloud/${this.applicationName}/start-task/`]); this.router.navigate([`/cloud/${this.applicationName}/start-task/`]);
} }
onStartProcess() {
this.router.navigate([`/cloud/${this.applicationName}/start-process/`]);
}
} }

View File

@ -0,0 +1,7 @@
<adf-cloud-start-process
[appName]="applicationName"
[name]="processName"
(error)="openSnackMessage($event)"
(success)="onStartProcessSuccess()"
(cancel)="onCancelStartProcess()">
</adf-cloud-start-process>

View File

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

View File

@ -0,0 +1,49 @@
# Start Process Cloud Component
Starts a process.
![adf-start-process ](../docassets/images/startProcess.png)
## Basic Usage
```html
<adf-cloud-start-process
[appName]="YOUR_APP_NAME">
</adf-cloud-start-process>
```
## 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<string, any>[]` | | (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
<adf-cloud-start-process
[appId]="YOUR_APP_ID"
[name]="PROCESS_NAME"
[processDefinitionName]="PROCESS_DEFINITION_NAME">
</adf-cloud-start-process>
```
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

View File

@ -24,9 +24,9 @@ import { element, by } from 'protractor';
export class ProcessCloudDemoPage { export class ProcessCloudDemoPage {
allProcesses = element(by.css('span[data-automation-id="ADF_CLOUD_PROCESS_FILTERS.ALL_PROCESSES_filter"]')); allProcesses = element(by.css('span[data-automation-id="all-processes_filter"]'));
runningProcesses = element(by.css('span[data-automation-id="ADF_CLOUD_PROCESS_FILTERS.RUNNING_PROCESSES_filter"]')); runningProcesses = element(by.css('span[data-automation-id="running-processes_filter"]'));
completedProcesses = element(by.css('span[data-automation-id="ADF_CLOUD_PROCESS_FILTERS.COMPLETED_PROCESSES_filter"]')); completedProcesses = element(by.css('span[data-automation-id="completed-processes_filter"]'));
activeFilter = element(by.css("mat-list-item[class*='active'] span")); activeFilter = element(by.css("mat-list-item[class*='active'] span"));
processFilters = element(by.css("mat-expansion-panel[data-automation-id='Process Filters']")); processFilters = element(by.css("mat-expansion-panel[data-automation-id='Process Filters']"));

View File

@ -62,10 +62,6 @@ export class DatePickerPage {
return `${('0' + date.getDate()).slice(-2)}-${this.months[date.getMonth()]}-${date.getFullYear().toString().substr(-2)}`; 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() { selectTodayDate() {
this.checkDatePickerIsDisplayed(); this.checkDatePickerIsDisplayed();
let todayDate = element(by.css('.mat-calendar-body-today')); let todayDate = element(by.css('.mat-calendar-body-today'));

View File

@ -29,6 +29,7 @@ import TestConfig = require('../../test.config');
import AlfrescoApi = require('alfresco-js-api-node'); import AlfrescoApi = require('alfresco-js-api-node');
import { browser } from 'protractor'; import { browser } from 'protractor';
import moment from 'moment-es6';
describe('Search Date Range Filter', () => { describe('Search Date Range Filter', () => {
@ -199,6 +200,7 @@ describe('Search Date Range Filter', () => {
describe('configuration change', () => { describe('configuration change', () => {
let jsonFile; let jsonFile;
let dateFormat = 'MM-DD-YY';
beforeAll(() => { beforeAll(() => {
let searchConfiguration = new SearchConfiguration(); let searchConfiguration = new SearchConfiguration();
@ -206,7 +208,7 @@ describe('Search Date Range Filter', () => {
}); });
it('[C277117] Should be able to change date format', () => { 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(); navigationBar.clickConfigEditorButton();
configEditor.clickSearchConfiguration(); configEditor.clickSearchConfiguration();
@ -221,7 +223,7 @@ describe('Search Date Range Filter', () => {
dateRangeFilter.checkFromFieldIsDisplayed() dateRangeFilter.checkFromFieldIsDisplayed()
.openFromDatePicker(); .openFromDatePicker();
let todayDate = datePicker.convertDateToNewFormat(new Date()); let todayDate = moment().format(dateFormat);
datePicker.selectTodayDate(); datePicker.selectTodayDate();
browser.controlFlow().execute(async () => { browser.controlFlow().execute(async () => {

View File

@ -2,18 +2,40 @@
"ADF_CLOUD_PROCESS_LIST": { "ADF_CLOUD_PROCESS_LIST": {
"MESSAGES": { "MESSAGES": {
"TITLE": "No Processes Found", "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." "NONE": "No process instance filter selected."
}, },
"PROPERTIES": { "PROPERTIES": {
"NAME": "Name", "NAME": "Name",
"CREATED": "Created" "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": { "ADF_CLOUD_TASK_LIST": {
"APPS": { "APPS": {
"TITLE": "No Applications Found", "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": { "START_TASK": {
"FORM": { "FORM": {
@ -40,7 +62,7 @@
"LIST": { "LIST": {
"MESSAGES": { "MESSAGES": {
"TITLE": "No Tasks Found", "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" "NONE": "No task lists found"
} }
} }

View File

@ -18,15 +18,13 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { TRANSLATION_PROVIDER } from '@alfresco/adf-core'; import { TRANSLATION_PROVIDER } from '@alfresco/adf-core';
import { AppListCloudModule } from './app/app-list-cloud.module'; 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 { TaskCloudModule } from './task/task-cloud.module';
import { ProcessCloudModule } from './process/process-cloud.module';
@NgModule({ @NgModule({
imports: [ imports: [
AppListCloudModule, AppListCloudModule,
ProcessListCloudModule, ProcessCloudModule,
ProcessFiltersCloudModule,
TaskCloudModule TaskCloudModule
], ],
providers: [ providers: [
@ -41,8 +39,7 @@ import { TaskCloudModule } from './task/task-cloud.module';
], ],
exports: [ exports: [
AppListCloudModule, AppListCloudModule,
ProcessListCloudModule, ProcessCloudModule,
ProcessFiltersCloudModule,
TaskCloudModule TaskCloudModule
] ]
}) })

View File

@ -18,15 +18,18 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { ProcessFiltersCloudModule } from './process-filters/process-filters-cloud.module'; import { ProcessFiltersCloudModule } from './process-filters/process-filters-cloud.module';
import { ProcessListCloudModule } from './process-list/process-list-cloud.module'; import { ProcessListCloudModule } from './process-list/process-list-cloud.module';
import { StartProcessCloudModule } from './start-process/start-process-cloud.module';
@NgModule({ @NgModule({
imports: [ imports: [
ProcessFiltersCloudModule, ProcessFiltersCloudModule,
ProcessListCloudModule ProcessListCloudModule,
StartProcessCloudModule
], ],
exports: [ exports: [
ProcessFiltersCloudModule, ProcessFiltersCloudModule,
ProcessListCloudModule ProcessListCloudModule,
StartProcessCloudModule
] ]
}) })
export class ProcessCloudModule { } export class ProcessCloudModule { }

View File

@ -4,7 +4,7 @@
class="adf-filters__entry" [class.adf-active]="currentFilter === filter"> class="adf-filters__entry" [class.adf-active]="currentFilter === filter">
<mat-icon *ngIf="showIcons && filter.icon" matListIcon class="adf-filters__entry-icon">{{filter.icon}} <mat-icon *ngIf="showIcons && filter.icon" matListIcon class="adf-filters__entry-icon">{{filter.icon}}
</mat-icon> </mat-icon>
<span matLine [attr.data-automation-id]="filter.name + '_filter'">{{filter.name | translate}}</span> <span matLine [attr.data-automation-id]="filter.key + '_filter'">{{filter.name | translate}}</span>
</mat-list-item> </mat-list-item>
</mat-list> </mat-list>
<ng-template #loading> <ng-template #loading>

View File

@ -40,6 +40,7 @@ describe('ProcessFiltersCloudComponent', () => {
}), }),
new ProcessFilterCloudModel({ new ProcessFilterCloudModel({
name: 'FakeRunningProcesses', name: 'FakeRunningProcesses',
key: 'FakeRunningProcesses',
icon: 'inbox', icon: 'inbox',
id: '11', id: '11',
state: 'RUNNING' state: 'RUNNING'

View File

@ -17,4 +17,6 @@
export * from './process-list/public-api'; export * from './process-list/public-api';
export * from './process-filters/public-api'; export * from './process-filters/public-api';
export * from './start-process/public-api';
export * from './process-cloud.module'; export * from './process-cloud.module';

View File

@ -0,0 +1,70 @@
<mat-card class="adf-start-process">
<mat-card-title>{{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.TITLE' | translate}}
</mat-card-title>
<mat-card-content *ngIf="!isProcessDefinitionsEmpty(); else emptyProcessDefinitionsList">
<mat-card-subtitle id="error-message" *ngIf="errorMessageId">
{{ errorMessageId | translate }}
</mat-card-subtitle>
<form [formGroup]="processForm">
<mat-form-field class="adf-process-input-container">
<mat-label>{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.NAME' | translate }}</mat-label>
<input
matInput
formControlName="processInstanceName"
id="processName">
<mat-error *ngIf="processInstanceName.hasError('required')">
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.PROCESS_NAME_REQUIRED' | translate }}
</mat-error>
</mat-form-field>
<mat-form-field class="adf-process-input-container">
<mat-label>{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.LABEL.TYPE' | translate }}</mat-label>
<input
#inputAutocomplete
matInput
formControlName="processDefinition"
[matAutocomplete]="auto"
id="processDefinitionName">
<div class="adf-process-input-autocomplete">
<mat-autocomplete #auto="matAutocomplete" id="processDefinitionOptions" [displayWith]="displayProcessNameOnDropdown">
<mat-option *ngFor="let processDef of filteredProcesses" [value]="processDef.name">
{{ processDef.name }}
</mat-option>
</mat-autocomplete>
<button id="adf-select-process-dropdown" *ngIf="showSelectProcessDropdown" mat-icon-button (click)="displayDropdown($event)">
<mat-icon>arrow_drop_down</mat-icon>
</button>
</div>
<mat-error *ngIf="processDefinition.hasError('required')">
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.ERROR.PROCESS_DEFINITION_REQUIRED' | translate }}
</mat-error>
</mat-form-field>
</form>
</mat-card-content>
<ng-template #emptyProcessDefinitionsList>
<mat-card-content>
<mat-card-subtitle class="error-message" id="no-process-message">
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.NO_PROCESS_DEFINITIONS' | translate | uppercase}}
</mat-card-subtitle>
</mat-card-content>
</ng-template>
<mat-card-actions>
<button mat-button (click)="cancelStartProcess()" id="cancel_process">
{{ 'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.ACTION.CANCEL' | translate | uppercase}}
</button>
<button
color="primary"
mat-button
[disabled]="!processForm.valid || isLoading"
(click)="startProcess()"
data-automation-id="btn-start"
id="button-start" class="btn-start">
{{'ADF_CLOUD_PROCESS_LIST.ADF_CLOUD_START_PROCESS.FORM.ACTION.START' | translate | uppercase}}
</button>
</mat-card-actions>
</mat-card>

View File

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

View File

@ -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<StartProcessCloudComponent>;
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<string, object>[] = [];
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();
});
});
});

View File

@ -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<string, object>[];
/** This flag displays/hides the process dropdown list */
@Input()
showSelectProcessDropdown: boolean = true;
/** Emitted when the starting process is successfully created. */
@Output()
success: EventEmitter<ProcessInstanceCloud> = new EventEmitter<ProcessInstanceCloud>();
/** Emitted when the starting process is cancelled */
@Output()
cancel: EventEmitter<ProcessInstanceCloud> = new EventEmitter<ProcessInstanceCloud>();
/** Emitted when an error occurs. */
@Output()
error: EventEmitter<ProcessInstanceCloud> = new EventEmitter<ProcessInstanceCloud>();
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');
}
}

View File

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

View File

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

View File

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

View File

@ -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<string, object>[];
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;
}
}

View File

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

View File

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

View File

@ -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<ProcessDefinitionCloud[]> {
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<ProcessInstanceCloud> {
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');
}
}

View File

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

View File

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