mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-1117] Activiti Start Process - Migrate to MD (#2096)
* [ADF-1117] Activiti Start Process - Migrate to MD
This commit is contained in:
committed by
Eugenio Romano
parent
82e8dfa3b0
commit
3268fdf815
@@ -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>
|
||||
```
|
||||

|
||||
|
||||
### 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 |
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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>
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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",
|
||||
|
Reference in New Issue
Block a user