[ADF-1117] Activiti Start Process - Migrate to MD (#2096)

* [ADF-1117] Activiti Start Process - Migrate to MD
This commit is contained in:
siva kumar
2017-07-18 21:57:20 +05:30
committed by Eugenio Romano
parent 82e8dfa3b0
commit 3268fdf815
9 changed files with 176 additions and 96 deletions

View File

@@ -184,14 +184,14 @@ The AccordionComponent is exposed by the alfresco-core.
## Start Process component
Displays a button which in turn displays a dialog when clicked, allowing the user
to specify some basic details needed to start a new process instance.
Displays Start Process, allowing the user to specify some basic details needed to start a new process instance.
```html
<adf-start-process
appId="YOUR_APP_ID" >
</adf-start-process>
```
![adf-start-process ](docs/assets/start-process.png)
### Properties
@@ -205,6 +205,7 @@ to specify some basic details needed to start a new process instance.
| Name | Description |
| --- | --- |
| start | Raised when the process start |
| cancel | Raised when the process canceled |
| error | Raised when the start process fail |
## Process Details component

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -16,7 +16,12 @@
*/
import { ModuleWithProviders, NgModule } from '@angular/core';
import { MdProgressSpinnerModule } from '@angular/material';
import {
MdProgressSpinnerModule,
MdButtonModule,
MdCardModule,
MdInputModule,
MdSelectModule } from '@angular/material';
import { ActivitiFormModule } from 'ng2-activiti-form';
import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';
import { CoreModule, CardViewUpdateService } from 'ng2-alfresco-core';
@@ -118,7 +123,11 @@ export const ACTIVITI_PROCESSLIST_PROVIDERS: [any] = [
DataTableModule,
ActivitiFormModule,
ActivitiTaskListModule,
MdProgressSpinnerModule
MdProgressSpinnerModule,
MdButtonModule,
MdCardModule,
MdInputModule,
MdSelectModule
],
declarations: [
...ACTIVITI_PROCESSLIST_DIRECTIVES

View File

@@ -23,7 +23,13 @@ export let newProcess = new ProcessInstance({
name: 'Process'
});
export let fakeProcessDefs = [new ProcessDefinitionRepresentation({
export let testProcessDefRepr = new ProcessDefinitionRepresentation({
id: 'my:process1',
name: 'My Process 1',
hasStartForm: false
});
export let testProcessDefs = [new ProcessDefinitionRepresentation({
id: 'my:process1',
name: 'My Process 1',
hasStartForm: false
@@ -33,7 +39,7 @@ export let fakeProcessDefs = [new ProcessDefinitionRepresentation({
hasStartForm: false
})];
export let fakeProcessDefWithForm = [new ProcessDefinitionRepresentation({
export let testProcessDefWithForm = [new ProcessDefinitionRepresentation({
id: 'my:process1',
name: 'My Process 1',
hasStartForm: true

View File

@@ -1,37 +1,26 @@
:host {
.adf-smoke-bg {
background-color: whitesmoke;
height: 100%;
width: 100%;
position: absolute;
}
md-card {
width: calc(66.6666% - 48px);
margin-left: calc(33.3333333333% / 2);
margin-right: calc(33.3333333333% / 2);
margin-top: 10px;
}
md-input-container {
width: 100%;
}
.activiti-label {
font-weight: bolder;
}
.material-icons:hover {
color: rgb(255, 152, 0);
}
.mdl-textfield.alf-mdl-selectfield label {
color: rgba(0,0,0,.54);
font-size: 12px;
top: 4px;
}
.mdl-card {
md-select {
width: 100%;
min-height: initial;
margin-bottom: 20px;
padding: 16px 0px 0px 0px;
}
.mdl-card .mdl-card__supporting-text {
width: 100%;
padding: 20px;
box-sizing: inherit;
}
.mdl-dialog {
width: -moz-fit-content;
width: -webkit-fit-content;
width: -ms-fit-content;
width: -o-fit-content;
width: fit-content;
md-card-actions {
text-align: right;
}

View File

@@ -1,34 +1,33 @@
<div *ngIf="processDefinitions.length > 0 || errorMessageId" class="mdl-card mdl-shadow--2dp">
<div class="mdl-card__supporting-text">
<div class="mdl-card mdl-shadow--2dp error-message" *ngIf="errorMessageId">
<div class="mdl-card__supporting-text">{{errorMessageId|translate}}</div>
</div>
<div class="mdl-textfield mdl-js-textfield alf-mdl-selectfield">
<select name="processDefinition" [(ngModel)]="currentProcessDef.id" (ngModelChange)="onProcessDefChange($event)" id="processDefinition" required>
<option value="null">{{'START_PROCESS.DIALOG.TYPE_PLACEHOLDER'|translate}}</option>
<option *ngFor="let processDef of processDefinitions" [value]="processDef.id">
{{processDef.name}}
</option>
</select>
<label class="mdl-textfield__label" for="processDefinition">{{'START_PROCESS.DIALOG.LABEL.TYPE'|translate}}</label>
</div>
<br>
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label" alfresco-mdl-textfield>
<input class="mdl-textfield__input" type="text" [(ngModel)]="name" id="processName" required />
<label class="mdl-textfield__label" for="processName">{{'START_PROCESS.DIALOG.LABEL.NAME'|translate}}</label>
</div>
<adf-start-form *ngIf="hasStartForm()" [processDefinitionId]="currentProcessDef.id"
(outcomeClick)="onOutcomeClick($event)">
</adf-start-form>
</div>
<div class="mdl-card__actions mdl-card--border" *ngIf="!hasStartForm()">
<button type="button" [disabled]="!validateForm()" (click)="startProcess()" class="mdl-button" data-automation-id="btn-start">{{'START_PROCESS.DIALOG.ACTION.START'|translate}}</button>
</div>
</div>
<div *ngIf="processDefinitions.length === 0 && !errorMessageId" class="mdl-card mdl-shadow--2dp">
<div class="mdl-card__supporting-text">
<div class="mdl-textfield mdl-js-textfield alf-mdl-selectfield">
<span id="no-process-message">{{'START_PROCESS.NO_PROCESS_DEFINITIONS' | translate}}</span>
</div>
</div>
</div>
<div class="adf-smoke-bg">
<md-card>
<md-card-title>{{'START_PROCESS.FORM.TITLE' | translate}}
</md-card-title>
<md-card-content *ngIf="processDefinitions.length > 0 || errorMessageId">
<md-card-subtitle id="error-message" *ngIf="errorMessageId">
{{errorMessageId|translate}}
</md-card-subtitle>
<md-input-container>
<input mdInput placeholder="{{'START_PROCESS.FORM.LABEL.NAME'|translate}}" [(ngModel)]="name" id="processName" required />
</md-input-container>
<md-select placeholder="{{'START_PROCESS.FORM.LABEL.TYPE'|translate}}" [(ngModel)]="currentProcessDef.id" (ngModelChange)="onProcessDefChange($event)" required>
<md-option>{{'START_PROCESS.FORM.TYPE_PLACEHOLDER' | translate}}</md-option>
<md-option *ngFor="let processDef of processDefinitions" [value]="processDef.id">
{{ processDef.name }}
</md-option>
</md-select>
<activiti-start-form *ngIf="hasStartForm()" [processDefinitionId]="currentProcessDef.id" (outcomeClick)="onOutcomeClick($event)">
</activiti-start-form>
</md-card-content>
<md-card-content *ngIf="processDefinitions.length === 0 && !errorMessageId">
<md-card-subtitle class="error-message" id="no-process-message">
{{'START_PROCESS.NO_PROCESS_DEFINITIONS' | translate}}
</md-card-subtitle>
</md-card-content>
<md-card-actions *ngIf="processDefinitions.length > 0 || errorMessageId">
<button md-button (click)="cancelStartProcess()" id="cancle_process" class="">{{'START_PROCESS.FORM.ACTION.CANCEL'| translate}}
</button>
<button md-button *ngIf="!hasStartForm()" [disabled]="!validateForm()" (click)="startProcess()" data-automation-id="btn-start" id="button-start" class="btn-start"> {{'START_PROCESS.FORM.ACTION.START' | translate}}
</button>
</md-card-actions>
</md-card>
</div>

View File

@@ -17,14 +17,20 @@
import { DebugElement, SimpleChange } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import {
MdButtonModule,
MdCardModule,
MdInputModule,
MdProgressSpinnerModule,
MdSelectModule
} from '@angular/material';
import { ActivitiFormModule, FormService } from 'ng2-activiti-form';
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Rx';
import { RestVariable } from 'alfresco-js-api';
import { ProcessService } from '../services/process.service';
import { fakeProcessDefs, fakeProcessDefWithForm, newProcess, taskFormMock } from './../assets/start-process.component.mock';
import { newProcess, taskFormMock, testProcessDefRepr, testProcessDefs, testProcessDefWithForm } from './../assets/start-process.component.mock';
import { TranslationMock } from './../assets/translation.service.mock';
import { StartProcessInstanceComponent } from './start-process.component';
@@ -44,7 +50,11 @@ describe('StartProcessInstanceComponent', () => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot(),
ActivitiFormModule.forRoot()
ActivitiFormModule.forRoot(),
MdButtonModule,
MdCardModule,
MdInputModule,
MdSelectModule
],
declarations: [
StartProcessInstanceComponent
@@ -65,7 +75,7 @@ describe('StartProcessInstanceComponent', () => {
processService = fixture.debugElement.injector.get(ProcessService);
formService = fixture.debugElement.injector.get(FormService);
getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(Observable.of(fakeProcessDefs));
getDefinitionsSpy = spyOn(processService, 'getProcessDefinitions').and.returnValue(Observable.of(testProcessDefs));
startProcessSpy = spyOn(processService, 'startProcess').and.returnValue(Observable.of(newProcess));
getStartFormDefinitionSpy = spyOn(formService, 'getStartFormDefinition').and.returnValue(Observable.of(taskFormMock));
@@ -76,6 +86,10 @@ describe('StartProcessInstanceComponent', () => {
window['componentHandler'] = componentHandler;
});
it('should create instance of StartProcessInstanceComponent', () => {
expect(fixture.componentInstance instanceof StartProcessInstanceComponent).toBe(true, 'should create StartProcessInstanceComponent');
});
describe('process definitions list', () => {
it('should call service to fetch process definitions with appId', () => {
@@ -107,21 +121,25 @@ describe('StartProcessInstanceComponent', () => {
component.ngOnChanges({'appId': change});
fixture.detectChanges();
let selectElement = debugElement.query(By.css('select'));
expect(selectElement.children.length).toBe(3);
let selectElement = fixture.nativeElement.querySelector('md-select');
expect(selectElement.children.length).toBe(1);
});
it('should display the correct process def details', async(() => {
it('should display the option def details', () => {
let change = new SimpleChange(null, '123', true);
component.ngOnChanges({'appId': change});
component.processDefinitions = testProcessDefs;
fixture.detectChanges();
fixture.whenStable().then(() => {
let optionEl: HTMLOptionElement = debugElement.queryAll(By.css('select option'))[1].nativeElement;
expect(optionEl.value).toBe('my:process1');
expect(optionEl.textContent.trim()).toBe('My Process 1');
let selectElement = fixture.nativeElement.querySelector('md-select > .mat-select-trigger');
let optionElement = fixture.nativeElement.querySelectorAll('md-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(Observable.throw({}));
@@ -130,9 +148,9 @@ describe('StartProcessInstanceComponent', () => {
fixture.detectChanges();
fixture.whenStable().then(() => {
let errorEl: DebugElement = debugElement.query(By.css('.error-message'));
let errorEl = fixture.nativeElement.querySelector('#error-message');
expect(errorEl).not.toBeNull('Expected error message to be present');
expect(errorEl.nativeElement.innerText.trim()).toBe('START_PROCESS.ERROR.LOAD_PROCESS_DEFS');
expect(errorEl.innerText.trim()).toBe('START_PROCESS.ERROR.LOAD_PROCESS_DEFS');
});
}));
@@ -143,9 +161,9 @@ describe('StartProcessInstanceComponent', () => {
fixture.detectChanges();
fixture.whenStable().then(() => {
let noprocessElement: DebugElement = debugElement.query(By.css('#no-process-message'));
let noprocessElement = fixture.nativeElement.querySelector('#no-process-message');
expect(noprocessElement).not.toBeNull('Expected no available process message to be present');
expect(noprocessElement.nativeElement.innerText.trim()).toBe('START_PROCESS.NO_PROCESS_DEFINITIONS');
expect(noprocessElement.innerText.trim()).toBe('START_PROCESS.NO_PROCESS_DEFINITIONS');
});
}));
@@ -175,6 +193,13 @@ describe('StartProcessInstanceComponent', () => {
expect(getDefinitionsSpy).toHaveBeenCalledWith(null);
});
it('should get current processDeff', () => {
component.ngOnChanges({appId: change});
component.onProcessDefChange('my:Process');
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalled();
expect(component.processDefinitions).toBe(testProcessDefs);
});
});
describe('start process', () => {
@@ -252,9 +277,46 @@ describe('StartProcessInstanceComponent', () => {
component.startProcess();
fixture.whenStable().then(() => {
fixture.detectChanges();
let errorEl: DebugElement = debugElement.query(By.css('.error-message'));
let errorEl = fixture.nativeElement.querySelector('#error-message');
expect(errorEl).not.toBeNull();
expect(errorEl.nativeElement.innerText.trim()).toBe('START_PROCESS.ERROR.START');
expect(errorEl.innerText.trim()).toBe('START_PROCESS.ERROR.START');
});
}));
it('should emit start event when start the process with currentProcessDef and name', () => {
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.currentProcessDef.id = '1001';
component.name = 'my:Process';
component.startProcess();
fixture.detectChanges();
expect(startSpy).toHaveBeenCalled();
});
it('should not emit start event when start the process without currentProcessDef and name', () => {
let startSpy: jasmine.Spy = spyOn(component.start, 'emit');
component.startProcess();
fixture.detectChanges();
expect(startSpy).not.toHaveBeenCalled();
});
it('should true if form is valid', async(() => {
component.currentProcessDef = testProcessDefRepr;
component.name = 'my:process1';
component.currentProcessDef.id = '1001';
component.isStartFormMissingOrValid();
component.validateForm();
fixture.whenStable().then(() => {
expect(component.validateForm()).toBe(true);
});
}));
it('should true if startFrom defined', async(() => {
component.currentProcessDef = testProcessDefRepr;
component.name = 'my:process1';
component.currentProcessDef.hasStartForm = true;
component.hasStartForm();
fixture.whenStable().then(() => {
expect(component.hasStartForm()).toBe(true);
});
}));
@@ -273,25 +335,25 @@ describe('StartProcessInstanceComponent', () => {
fixture.detectChanges();
component.onProcessDefChange('my:process1');
fixture.whenStable();
startBtn = debugElement.query(By.css('[data-automation-id="btn-start"]'));
startBtn = fixture.nativeElement.querySelector('#button-start');
}));
it('should have start button disabled when name not filled out', async(() => {
component.name = '';
fixture.detectChanges();
expect(startBtn.properties['disabled']).toBe(true);
expect(startBtn.disabled).toBe(true);
}));
it('should have start button disabled when no process is selected', async(() => {
component.onProcessDefChange('');
fixture.detectChanges();
expect(startBtn.properties['disabled']).toBe(true);
expect(startBtn.disabled).toBe(true);
}));
it('should enable start button when name and process filled out', async(() => {
fixture.detectChanges();
startBtn = debugElement.query(By.css('[data-automation-id="btn-start"]'));
expect(startBtn.properties['disabled']).toBe(false);
let startButton = fixture.nativeElement.querySelector('#button-start');
expect(startButton.enable).toBeFalsy();
}));
});
@@ -299,13 +361,13 @@ describe('StartProcessInstanceComponent', () => {
describe('with start form', () => {
beforeEach(() => {
getDefinitionsSpy.and.returnValue(Observable.of(fakeProcessDefWithForm));
getDefinitionsSpy.and.returnValue(Observable.of(testProcessDefWithForm));
let change = new SimpleChange(null, '123', true);
component.ngOnChanges({'appId': change});
component.onProcessDefChange('my:process1');
fixture.detectChanges();
fixture.whenStable();
startBtn = debugElement.query(By.css('[data-automation-id="btn-start"]'));
startBtn = fixture.nativeElement.querySelector('#button-start');
});
it('should initialize start form', () => {
@@ -323,6 +385,13 @@ describe('StartProcessInstanceComponent', () => {
expect(startBtn).toBeNull();
}));
it('should emit cancel event on cancel Button', () => {
let cancelButton = fixture.nativeElement.querySelector('#cancle_process');
let cancelSpy: jasmine.Spy = spyOn(component.cancel, 'emit');
cancelButton.click();
fixture.detectChanges();
expect(cancelSpy).toHaveBeenCalled();
});
});
});

View File

@@ -42,6 +42,9 @@ export class StartProcessInstanceComponent implements OnChanges {
@Output()
start: EventEmitter<ProcessInstance> = new EventEmitter<ProcessInstance>();
@Output()
cancel: EventEmitter<ProcessInstance> = new EventEmitter<ProcessInstance>();
@Output()
error: EventEmitter<ProcessInstance> = new EventEmitter<ProcessInstance>();
@@ -111,6 +114,10 @@ export class StartProcessInstanceComponent implements OnChanges {
}
}
public cancelStartProcess() {
this.cancel.emit();
}
hasStartForm() {
return this.currentProcessDef && this.currentProcessDef.hasStartForm;
}

View File

@@ -87,7 +87,7 @@
"START_PROCESS": {
"BUTTON": "Start Process",
"NO_PROCESS_DEFINITIONS": "You cannot start a process as there are no process definitions available",
"DIALOG": {
"FORM": {
"TITLE": "Start Process",
"LABEL": {
"TYPE": "Type",