Dev mromano improveprocess (#1334)

* fix overload activiti

* fix reload tasklist

* fix process variables

* fix default filter

* improve process list

* fix tests process

* fix tests

* Fix test for form

* fix tests

* fix tests

* #fix activiti bug

* #fix activiti bug (reverted from commit c966395ed1087e1dedaf0af0923a89ba99d6ab9f)

* fix tests

* #fix header test bug

* fix tests

* fix process

* fix tests
This commit is contained in:
Mario Romano
2016-12-20 09:37:56 +00:00
committed by GitHub
parent 636fa770b4
commit 11894a9769
34 changed files with 375 additions and 207 deletions

View File

@@ -103,13 +103,13 @@ describe('ActivitiForm', () => {
it('should enable custom outcome buttons', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: 'action1', name: 'Action 1' });
let outcome = new FormOutcomeModel(formModel, {id: 'action1', name: 'Action 1'});
expect(formComponent.isOutcomeButtonVisible(outcome)).toBeTruthy();
});
it('should allow controlling [complete] button visibility', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION });
let outcome = new FormOutcomeModel(formModel, {id: '$save', name: FormOutcomeModel.SAVE_ACTION});
formComponent.showSaveButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome)).toBeTruthy();
@@ -120,7 +120,7 @@ describe('ActivitiForm', () => {
it('should allow controlling [save] button visibility', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION });
let outcome = new FormOutcomeModel(formModel, {id: '$save', name: FormOutcomeModel.COMPLETE_ACTION});
formComponent.showCompleteButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome)).toBeTruthy();
@@ -138,13 +138,58 @@ describe('ActivitiForm', () => {
it('should get form by task id on load', () => {
spyOn(formComponent, 'getFormByTaskId').and.stub();
spyOn(visibilityService, 'getTaskProcessVariable').and.returnValue(Observable.of({}));
const taskId = '123';
formComponent.taskId = taskId;
formComponent.loadForm();
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId);
});
it('should get process variable if is a process task', () => {
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId});
observer.complete();
});
});
spyOn(visibilityService, 'getTaskProcessVariable').and.returnValue(Observable.of({}));
spyOn(formService, 'getTask').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId, processDefinitionId: '10201'});
observer.complete();
});
});
const taskId = '123';
formComponent.taskId = taskId;
formComponent.loadForm();
expect(visibilityService.getTaskProcessVariable).toHaveBeenCalledWith(taskId);
});
it('should not get process variable if is not a process task', () => {
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId});
observer.complete();
});
});
spyOn(visibilityService, 'getTaskProcessVariable').and.returnValue(Observable.of({}));
spyOn(formService, 'getTask').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({taskId: taskId, processDefinitionId: 'null'});
observer.complete();
});
});
const taskId = '123';
formComponent.taskId = taskId;
formComponent.loadForm();
expect(visibilityService.getTaskProcessVariable).toHaveBeenCalledWith(taskId);
});
@@ -173,7 +218,7 @@ describe('ActivitiForm', () => {
const taskId = '<task id>';
let change = new SimpleChange(null, taskId);
formComponent.ngOnChanges({ 'taskId': change });
formComponent.ngOnChanges({'taskId': change});
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId);
});
@@ -183,7 +228,7 @@ describe('ActivitiForm', () => {
const formId = '123';
let change = new SimpleChange(null, formId);
formComponent.ngOnChanges({ 'formId': change });
formComponent.ngOnChanges({'formId': change});
expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId);
});
@@ -193,7 +238,7 @@ describe('ActivitiForm', () => {
const formName = '<form>';
let change = new SimpleChange(null, formName);
formComponent.ngOnChanges({ 'formName': change });
formComponent.ngOnChanges({'formName': change});
expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName);
});
@@ -218,7 +263,7 @@ describe('ActivitiForm', () => {
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
formComponent.ngOnChanges({ 'tag': new SimpleChange(null, 'hello world') });
formComponent.ngOnChanges({'tag': new SimpleChange(null, 'hello world')});
expect(formComponent.getFormByTaskId).not.toHaveBeenCalled();
expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled();
@@ -228,7 +273,7 @@ describe('ActivitiForm', () => {
it('should complete form on custom outcome click', () => {
let formModel = new FormModel();
let outcomeName = 'Custom Action';
let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName });
let outcome = new FormOutcomeModel(formModel, {id: 'custom1', name: outcomeName});
let saved = false;
formComponent.form = formModel;
@@ -293,7 +338,7 @@ describe('ActivitiForm', () => {
it('should do nothing when clicking outcome for readonly form', () => {
let formModel = new FormModel();
const outcomeName = 'Custom Action';
let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName });
let outcome = new FormOutcomeModel(formModel, {id: 'custom1', name: outcomeName});
formComponent.form = formModel;
spyOn(formComponent, 'completeTaskForm').and.stub();
@@ -312,7 +357,7 @@ describe('ActivitiForm', () => {
it('should require loaded form when clicking outcome', () => {
let formModel = new FormModel();
const outcomeName = 'Custom Action';
let outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName });
let outcome = new FormOutcomeModel(formModel, {id: 'custom1', name: outcomeName});
formComponent.readOnly = false;
formComponent.form = null;
@@ -321,7 +366,7 @@ describe('ActivitiForm', () => {
it('should not execute unknown system outcome', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: 'unknown', name: 'Unknown', isSystem: true });
let outcome = new FormOutcomeModel(formModel, {id: 'unknown', name: 'Unknown', isSystem: true});
formComponent.form = formModel;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
@@ -329,20 +374,21 @@ describe('ActivitiForm', () => {
it('should require custom action name to complete form', () => {
let formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: 'custom' });
let outcome = new FormOutcomeModel(formModel, {id: 'custom'});
formComponent.form = formModel;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
outcome = new FormOutcomeModel(formModel, { id: 'custom', name: 'Custom' });
outcome = new FormOutcomeModel(formModel, {id: 'custom', name: 'Custom'});
spyOn(formComponent, 'completeTaskForm').and.stub();
expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy();
});
it('should fetch and parse form by task id', () => {
spyOn(formService, 'getTask').and.returnValue(Observable.of({}));
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({ taskId: taskId });
observer.next({taskId: taskId});
observer.complete();
});
});
@@ -363,6 +409,7 @@ describe('ActivitiForm', () => {
it('should handle error when getting form by task id', () => {
const error = 'Some error';
spyOn(formService, 'getTask').and.returnValue(Observable.of({}));
spyOn(formComponent, 'handleError').and.stub();
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.throw(error);
@@ -373,9 +420,10 @@ describe('ActivitiForm', () => {
});
it('should apply readonly state when getting form by task id', () => {
spyOn(formService, 'getTask').and.returnValue(Observable.of({}));
spyOn(formService, 'getTaskForm').and.callFake((taskId) => {
return Observable.create(observer => {
observer.next({ taskId: taskId });
observer.next({taskId: taskId});
observer.complete();
});
});
@@ -390,7 +438,7 @@ describe('ActivitiForm', () => {
it('should fetch and parse form definition by id', () => {
spyOn(formService, 'getFormDefinitionById').and.callFake((formId) => {
return Observable.create(observer => {
observer.next({ id: formId });
observer.next({id: formId});
observer.complete();
});
});
@@ -429,7 +477,7 @@ describe('ActivitiForm', () => {
spyOn(formService, 'getFormDefinitionById').and.callFake((formName) => {
return Observable.create(observer => {
observer.next({ name: formName });
observer.next({name: formName});
observer.complete();
});
});
@@ -465,8 +513,8 @@ describe('ActivitiForm', () => {
let formModel = new FormModel({
taskId: '123',
fields: [
{ id: 'field1' },
{ id: 'field2' }
{id: 'field1'},
{id: 'field2'}
]
});
formComponent.form = formModel;
@@ -482,7 +530,7 @@ describe('ActivitiForm', () => {
spyOn(formService, 'saveTaskForm').and.callFake(() => Observable.throw(error));
spyOn(formComponent, 'handleError').and.stub();
formComponent.form = new FormModel({ taskId: '123' });
formComponent.form = new FormModel({taskId: '123'});
formComponent.saveTaskForm();
expect(formComponent.handleError).toHaveBeenCalledWith(error);
@@ -534,8 +582,8 @@ describe('ActivitiForm', () => {
let formModel = new FormModel({
taskId: '123',
fields: [
{ id: 'field1' },
{ id: 'field2' }
{id: 'field1'},
{id: 'field2'}
]
});
@@ -554,7 +602,7 @@ describe('ActivitiForm', () => {
let form = formComponent.parseForm({
id: '<id>',
fields: [
{ id: 'field1', type: FormFieldTypes.CONTAINER }
{id: 'field1', type: FormFieldTypes.CONTAINER}
]
});
@@ -567,7 +615,7 @@ describe('ActivitiForm', () => {
it('should provide outcomes for form definition', () => {
spyOn(formComponent, 'getFormDefinitionOutcomes').and.callThrough();
let form = formComponent.parseForm({ id: '<id>' });
let form = formComponent.parseForm({id: '<id>'});
expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form);
});
@@ -640,7 +688,7 @@ describe('ActivitiForm', () => {
let metadata = {};
spyOn(nodeService, 'getNodeMetadata').and.returnValue(
Observable.create(observer => {
observer.next({ metadata: metadata });
observer.next({metadata: metadata});
observer.complete();
})
);

View File

@@ -280,7 +280,6 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
loadForm() {
if (this.taskId) {
this.getFormByTaskId(this.taskId);
this.visibilityService.getTaskProcessVariable(this.taskId).subscribe();
return;
}
@@ -295,6 +294,22 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
}
}
loadFormPorcessVariable(taskId) {
this.formService.getTask(taskId).subscribe(
task => {
if (this.isAProcessTask(task)) {
this.visibilityService.getTaskProcessVariable(taskId).subscribe();
}
},
(error) => {
this.handleError(error);
});
}
isAProcessTask(taskRepresentation) {
return taskRepresentation.processDefinitionId && taskRepresentation.processDefinitionDeploymentId !== 'null';
}
setupMaterialComponents(): boolean {
// workaround for MDL issues with dynamic components
if (componentHandler) {
@@ -305,6 +320,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
}
getFormByTaskId(taskId: string) {
this.loadFormPorcessVariable(this.taskId);
let data = this.data;
this.formService
.getTaskForm(taskId)
@@ -411,7 +427,7 @@ export class ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
*/
getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] {
return [
new FormOutcomeModel(form, { id: '$custom', name: FormOutcomeModel.SAVE_ACTION, isSystem: true })
new FormOutcomeModel(form, {id: '$custom', name: FormOutcomeModel.SAVE_ACTION, isSystem: true})
];
}

View File

@@ -41,7 +41,7 @@ describe('ActivitiStartForm', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ CoreModule ],
imports: [CoreModule],
declarations: [
ActivitiStartForm,
FormFieldComponent,
@@ -73,26 +73,21 @@ describe('ActivitiStartForm', () => {
window['componentHandler'] = componentHandler;
});
it('should load start form on init if processDefinitionId defined', () => {
it('should load start form on change if processDefinitionId defined', () => {
component.processDefinitionId = exampleId1;
component.ngOnInit();
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2) });
expect(formService.getStartFormDefinition).toHaveBeenCalled();
});
it('should load not start form on init if no processDefinitionId defined', () => {
component.ngOnInit();
expect(formService.getStartFormDefinition).not.toHaveBeenCalled();
});
it('should load start form when processDefinitionId changed', () => {
component.processDefinitionId = exampleId1;
component.ngOnChanges({processDefinitionId: new SimpleChange(exampleId1, exampleId2)});
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2) });
expect(formService.getStartFormDefinition).toHaveBeenCalled();
});
it('should not load start form when changes notified but no change to processDefinitionId', () => {
component.processDefinitionId = exampleId1;
component.ngOnChanges({otherProp: new SimpleChange(exampleId1, exampleId2)});
component.ngOnChanges({ otherProp: new SimpleChange(exampleId1, exampleId2) });
expect(formService.getStartFormDefinition).not.toHaveBeenCalled();
});
@@ -113,6 +108,7 @@ describe('ActivitiStartForm', () => {
}));
component.processDefinitionId = exampleId1;
component.ngOnInit();
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2) });
fixture.detectChanges();
expect(component.outcomesContainer).toBeTruthy();
});
@@ -128,7 +124,7 @@ describe('ActivitiStartForm', () => {
}));
component.processDefinitionId = exampleId1;
component.showOutcomeButtons = true;
component.ngOnInit();
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2) });
fixture.detectChanges();
expect(component.outcomesContainer).toBeTruthy();
});

View File

@@ -17,7 +17,7 @@
import {
Component,
OnInit, AfterViewChecked, OnChanges,
AfterViewChecked, OnChanges,
SimpleChanges,
Input,
ViewChild,
@@ -53,7 +53,7 @@ import { WidgetVisibilityService } from './../services/widget-visibility.servic
templateUrl: './activiti-start-form.component.html',
styleUrls: ['./activiti-form.component.css']
})
export class ActivitiStartForm extends ActivitiForm implements OnInit, AfterViewChecked, OnChanges {
export class ActivitiStartForm extends ActivitiForm implements AfterViewChecked, OnChanges {
@Input()
processDefinitionId: string;
@@ -77,14 +77,6 @@ export class ActivitiStartForm extends ActivitiForm implements OnInit, AfterView
formService: FormService,
visibilityService: WidgetVisibilityService) {
super(formService, visibilityService, null, null);
}
ngOnInit() {
if (this.processId) {
this.loadStartForm(this.processId);
}else {
this.loadForm();
}
if (this.translate) {
this.translate.addTranslationFolder('ng2-activiti-form', 'node_modules/ng2-activiti-form/src');
@@ -94,21 +86,15 @@ export class ActivitiStartForm extends ActivitiForm implements OnInit, AfterView
ngOnChanges(changes: SimpleChanges) {
let processDefinitionId = changes['processDefinitionId'];
if (processDefinitionId && processDefinitionId.currentValue) {
this.visibilityService.cleanProcessVariable();
this.getStartFormDefinition(processDefinitionId.currentValue);
return;
}
let processId = changes['processId'];
if (processId && processId.currentValue) {
this.loadStartForm(processId.currentValue);
return;
}
}
loadForm() {
if (this.processDefinitionId) {
if (processId && processId.currentValue) {
this.visibilityService.cleanProcessVariable();
this.getStartFormDefinition(this.processDefinitionId);
this.loadStartForm(processId.currentValue);
return;
}
}

View File

@@ -21,14 +21,17 @@ import { DataTableModule } from 'ng2-alfresco-datatable';
import { ActivitiFormModule } from 'ng2-activiti-form';
import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';
import { ActivitiProcessInstanceListComponent } from './src/components/activiti-processlist.component';
import { ActivitiProcessFilters } from './src/components/activiti-filters.component';
import { ActivitiProcessInstanceHeader } from './src/components/activiti-process-instance-header.component';
import { ActivitiProcessInstanceTasks } from './src/components/activiti-process-instance-tasks.component';
import { ActivitiProcessInstanceVariables } from './src/components/activiti-process-instance-variables.component';
import { ActivitiComments } from './src/components/activiti-comments.component';
import { ActivitiProcessInstanceDetails } from './src/components/activiti-process-instance-details.component';
import { ActivitiStartProcessInstance } from './src/components/activiti-start-process.component';
import {
ActivitiProcessInstanceListComponent,
ActivitiProcessFilters,
ActivitiProcessInstanceHeader,
ActivitiProcessInstanceTasks,
ActivitiProcessInstanceVariables,
ActivitiProcessComments,
ActivitiProcessInstanceDetails,
ActivitiStartProcessInstance
} from './src/components/index';
import { ActivitiProcessService } from './src/services/activiti-process.service';
// components
@@ -50,7 +53,7 @@ export const ACTIVITI_PROCESSLIST_DIRECTIVES: [any] = [
ActivitiProcessInstanceHeader,
ActivitiProcessInstanceTasks,
ActivitiProcessInstanceVariables,
ActivitiComments,
ActivitiProcessComments,
ActivitiStartProcessInstance
];

View File

@@ -43,7 +43,7 @@ describe('ActivitiFilters', () => {
});
beforeEach(() => {
activitiService = new ActivitiProcessService(null);
activitiService = new ActivitiProcessService(null, null);
filterList = new ActivitiProcessFilters(null, activitiService);
});

View File

@@ -23,7 +23,7 @@ import { Observable } from 'rxjs/Rx';
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
import { ActivitiFormModule } from 'ng2-activiti-form';
import { ActivitiComments } from './activiti-comments.component';
import { ActivitiProcessComments } from './activiti-process-comments.component';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { TranslationMock } from './../assets/translation.service.mock';
@@ -31,8 +31,8 @@ describe('ActivitiProcessInstanceComments', () => {
let componentHandler: any;
let service: ActivitiProcessService;
let component: ActivitiComments;
let fixture: ComponentFixture<ActivitiComments>;
let component: ActivitiProcessComments;
let fixture: ComponentFixture<ActivitiProcessComments>;
let getCommentsSpy: jasmine.Spy;
let addCommentSpy: jasmine.Spy;
@@ -43,7 +43,7 @@ describe('ActivitiProcessInstanceComments', () => {
ActivitiFormModule
],
declarations: [
ActivitiComments
ActivitiProcessComments
],
providers: [
{ provide: AlfrescoTranslationService, useClass: TranslationMock },
@@ -54,7 +54,7 @@ describe('ActivitiProcessInstanceComments', () => {
beforeEach(() => {
fixture = TestBed.createComponent(ActivitiComments);
fixture = TestBed.createComponent(ActivitiProcessComments);
component = fixture.componentInstance;
service = fixture.debugElement.injector.get(ActivitiProcessService);
@@ -75,16 +75,16 @@ describe('ActivitiProcessInstanceComments', () => {
});
it('should load comments when processInstanceId specified', () => {
component.processInstanceId = '123';
fixture.detectChanges();
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'processInstanceId': change });
expect(getCommentsSpy).toHaveBeenCalled();
});
it('should emit an error when an error occurs loading comments', () => {
let emitSpy = spyOn(component.error, 'emit');
getCommentsSpy.and.returnValue(Observable.throw({}));
component.processInstanceId = '123';
fixture.detectChanges();
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'processInstanceId': change });
expect(emitSpy).toHaveBeenCalled();
});
@@ -94,8 +94,9 @@ describe('ActivitiProcessInstanceComments', () => {
});
it('should display comments when the process has comments', async(() => {
component.processInstanceId = '123';
fixture.detectChanges();
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'processInstanceId': change });
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('ul.mdl-list li')).length).toBe(3);

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, Output, OnInit, ViewChild, OnChanges, SimpleChanges } from '@angular/core';
import { Component, EventEmitter, Input, Output, ViewChild, OnChanges, SimpleChanges } from '@angular/core';
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { Comment } from 'ng2-activiti-tasklist';
@@ -28,11 +28,11 @@ declare let dialogPolyfill: any;
@Component({
selector: 'activiti-process-instance-comments',
moduleId: module.id,
templateUrl: './activiti-comments.component.html',
styleUrls: ['./activiti-comments.component.css'],
templateUrl: './activiti-process-comments.component.html',
styleUrls: ['./activiti-process-comments.component.css'],
providers: [ActivitiProcessService]
})
export class ActivitiComments implements OnInit, OnChanges {
export class ActivitiProcessComments implements OnChanges {
@Input()
processInstanceId: string;
@@ -45,7 +45,8 @@ export class ActivitiComments implements OnInit, OnChanges {
comments: Comment [] = [];
private commentObserver: Observer<Comment>;
commentObserver: Observer<Comment>;
comment$: Observable<Comment>;
message: string;
@@ -62,18 +63,10 @@ export class ActivitiComments implements OnInit, OnChanges {
translate.addTranslationFolder('ng2-activiti-processlist', 'node_modules/ng2-activiti-processlist/src');
}
this.comment$ = new Observable<Comment>(observer => this.commentObserver = observer).share();
}
ngOnInit() {
this.comment$ = new Observable<Comment>(observer => this.commentObserver = observer).share();
this.comment$.subscribe((comment: Comment) => {
this.comments.push(comment);
});
if (this.processInstanceId) {
this.getProcessComments(this.processInstanceId);
return;
}
}
ngOnChanges(changes: SimpleChanges) {

View File

@@ -1,3 +1,10 @@
:host {
width: 100%;
}
}
.activiti-process-container {
width: 100%;
min-height: 100px;
overflow: visible;
padding: 10px;
}

View File

@@ -1,13 +1,14 @@
<div *ngIf="!processInstanceDetails">{{ 'DETAILS.MESSAGES.NONE'|translate }}</div>
<div *ngIf="processInstanceDetails">
<h2 class="mdl-card__title-text">{{processInstanceDetails.name}}</h2>
<activiti-process-instance-header [processInstance]="processInstanceDetails" (processCancelled)="bubbleProcessCancelled()"></activiti-process-instance-header>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--8-col">
<activiti-process-instance-tasks [processInstanceDetails]="processInstanceDetails" (taskFormCompleted)="bubbleTaskFormCompleted()"></activiti-process-instance-tasks>
</div>
<div class="mdl-cell mdl-cell--4-col">
<activiti-process-instance-comments [processInstanceId]="processInstanceDetails.id"></activiti-process-instance-comments>
<activiti-process-instance-header [processInstance]="processInstanceDetails"></activiti-process-instance-header>
<div class="mdl-card mdl-shadow--2dp activiti-process-container">
<div class="mdl-cell mdl-cell--12-col">
<activiti-process-instance-tasks [processInstanceDetails]="processInstanceDetails"
(taskFormCompleted)="bubbleTaskFormCompleted()"></activiti-process-instance-tasks>
</div>
</div>
<div class="mdl-cell mdl-cell--4-col" data-automation-id="header-status" *ngIf="isRunning()">
<button type="button" (click)="cancelProcess()" class="mdl-button">{{ 'DETAILS.BUTTON.CANCEL' | translate }}</button>
</div>
</div>

View File

@@ -21,13 +21,14 @@ import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Rx';
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
import { ActivitiFormModule, FormModel, FormOutcomeEvent, FormOutcomeModel, FormService } from 'ng2-activiti-form';
import { ActivitiFormModule, FormModel, FormService } from 'ng2-activiti-form';
import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';
import { ActivitiProcessInstanceDetails } from './activiti-process-instance-details.component';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { TranslationMock } from './../assets/translation.service.mock';
import { exampleProcess } from './../assets/activiti-process.model.mock';
import { ProcessInstance } from '../models/process-instance.model';
describe('ActivitiProcessInstanceDetails', () => {
@@ -127,6 +128,15 @@ describe('ActivitiProcessInstanceDetails', () => {
fixture.detectChanges();
expect(fixture.nativeElement.innerText).toBe('DETAILS.MESSAGES.NONE');
});
it('should display cancel button if process is running', () => {
component.processInstanceDetails = new ProcessInstance({
ended : null
});
fixture.detectChanges();
let buttonEl = fixture.debugElement.query(By.css('[data-automation-id="header-status"] button'));
expect(buttonEl).not.toBeNull();
});
});
describe('events', () => {
@@ -143,12 +153,6 @@ describe('ActivitiProcessInstanceDetails', () => {
expect(emitSpy).toHaveBeenCalled();
});
it('should emit a outcome execution event when task form outcome executed', () => {
let emitSpy: jasmine.Spy = spyOn(component.processCancelled, 'emit');
component.bubbleProcessCancelled(new FormOutcomeEvent(new FormOutcomeModel(new FormModel())));
expect(emitSpy).toHaveBeenCalled();
});
});
});

View File

@@ -20,7 +20,6 @@ import { AlfrescoTranslationService } from 'ng2-alfresco-core';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { ActivitiProcessInstanceHeader } from './activiti-process-instance-header.component';
import { ActivitiProcessInstanceTasks } from './activiti-process-instance-tasks.component';
import { ActivitiComments } from './activiti-comments.component';
import { ProcessInstance } from '../models/process-instance.model';
declare let componentHandler: any;
@@ -42,9 +41,6 @@ export class ActivitiProcessInstanceDetails implements OnChanges {
@ViewChild(ActivitiProcessInstanceTasks)
tasksList: ActivitiProcessInstanceTasks;
@ViewChild(ActivitiComments)
commentsList: ActivitiComments;
@Input()
showTitle: boolean = true;
@@ -52,7 +48,7 @@ export class ActivitiProcessInstanceDetails implements OnChanges {
showRefreshButton: boolean = true;
@Output()
processCancelled: EventEmitter<string> = new EventEmitter<string>();
processCancelled: EventEmitter<any> = new EventEmitter<any>();
@Output()
taskFormCompleted: EventEmitter<any> = new EventEmitter<any>();
@@ -101,11 +97,20 @@ export class ActivitiProcessInstanceDetails implements OnChanges {
}
}
bubbleProcessCancelled(data: any) {
this.processCancelled.emit(data);
}
bubbleTaskFormCompleted(data: any) {
this.taskFormCompleted.emit(data);
}
isRunning(): boolean {
return this.processInstanceDetails && !this.processInstanceDetails.ended;
}
cancelProcess() {
this.activitiProcess.cancelProcess(this.processInstanceId).subscribe(
(data) => {
this.processCancelled.emit(data);
}, (err) => {
console.error(err);
});
}
}

View File

@@ -4,4 +4,8 @@
.activiti-label {
font-weight: bolder;
}
}
.activiti-process-header__value {
color: rgb(68, 138, 255);
}

View File

@@ -8,8 +8,8 @@
<span class="activiti-label">{{ 'DETAILS.LABELS.STARTED' | translate }}</span>:
<span class="activiti-process-header__value">{{getFormatDate(processInstance.started, 'medium')}}</span>
</div>
<div class="mdl-cell mdl-cell--4-col" data-automation-id="header-status" *ngIf="isRunning()">
<button type="button" (click)="cancelProcess()" class="mdl-button">{{ 'DETAILS.BUTTON.CANCEL' | translate }}</button>
<div class="mdl-cell mdl-cell--4-col">
<activiti-process-instance-comments [processInstanceId]="processInstance?.id"></activiti-process-instance-comments>
</div>
<div class="mdl-cell mdl-cell--4-col" data-automation-id="header-status" *ngIf="!isRunning()">
<span class="activiti-label">{{ 'DETAILS.LABELS.ENDED' | translate }}</span>:

View File

@@ -18,12 +18,18 @@
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
import {
AlfrescoTranslationService,
CoreModule,
AlfrescoAuthenticationService,
AlfrescoSettingsService,
AlfrescoApiService } from 'ng2-alfresco-core';
import { ActivitiProcessInstanceHeader } from './activiti-process-instance-header.component';
import { TranslationMock } from './../assets/translation.service.mock';
import { exampleProcess } from './../assets/activiti-process.model.mock';
import { ProcessInstance } from './../models/process-instance.model';
import { ActivitiProcessComments } from './activiti-process-comments.component';
import { ActivitiProcessService } from './../services/activiti-process.service';
describe('ActivitiProcessInstanceHeader', () => {
@@ -38,11 +44,15 @@ describe('ActivitiProcessInstanceHeader', () => {
CoreModule
],
declarations: [
ActivitiProcessInstanceHeader
ActivitiProcessInstanceHeader,
ActivitiProcessComments
],
providers: [
{ provide: AlfrescoTranslationService, useClass: TranslationMock },
ActivitiProcessService
AlfrescoSettingsService,
AlfrescoAuthenticationService,
AlfrescoApiService,
ActivitiProcessService,
{provide: AlfrescoTranslationService, useClass: TranslationMock}
]
}).compileComponents();
}));
@@ -90,13 +100,6 @@ describe('ActivitiProcessInstanceHeader', () => {
expect(formValueEl.nativeElement.innerText).toBe('Nov 10, 2016, 3:37:30 AM');
});
it('should display cancel button if process is running', () => {
component.processInstance.ended = null;
fixture.detectChanges();
let buttonEl = fixture.debugElement.query(By.css('[data-automation-id="header-status"] button'));
expect(buttonEl).not.toBeNull();
});
it('should display ended date if process is ended', () => {
component.processInstance.ended = '2016-11-10T03:37:30.010+0000';
fixture.detectChanges();

View File

@@ -18,7 +18,6 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
import { ProcessInstance } from '../models/process-instance.model';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { DatePipe } from '@angular/common';
declare let componentHandler: any;
@@ -34,14 +33,10 @@ export class ActivitiProcessInstanceHeader {
@Input()
processInstance: ProcessInstance;
@Output()
processCancelled: EventEmitter<any> = new EventEmitter();
@Output()
onError: EventEmitter<any> = new EventEmitter<any>();
constructor(private translate: AlfrescoTranslationService,
private activitiProcess: ActivitiProcessService) {
constructor(private translate: AlfrescoTranslationService) {
if (translate) {
translate.addTranslationFolder('ng2-activiti-processlist', 'node_modules/ng2-activiti-processlist/src');
@@ -69,14 +64,4 @@ export class ActivitiProcessInstanceHeader {
isRunning(): boolean {
return this.processInstance && !this.processInstance.ended;
}
cancelProcess() {
this.activitiProcess.cancelProcess(this.processInstance.id).subscribe(
(res) => {
this.processCancelled.emit(res);
}, (err) => {
console.error(err);
this.onError.emit(err);
});
}
}

View File

@@ -36,3 +36,14 @@
height: 400px;
overflow-y: auto;
}
.no-results {
margin-left: 9px;
font-size: 14px;
font-weight: 400;
letter-spacing: 0;
line-height: 18px;
color: rgba(0, 0, 0, .54);
display: block;
padding: 12px;
}

View File

@@ -24,7 +24,7 @@
<!-- START FORM -->
<div *ngIf="activeTasks?.length === 0" data-automation-id="active-tasks-none">
<div *ngIf="activeTasks?.length === 0" data-automation-id="active-tasks-none" class="no-results">
{{ 'DETAILS.TASKS.NO_ACTIVE' | translate }}
</div>
@@ -64,7 +64,7 @@
</ul>
</div>
<div *ngIf="completedTasks?.length === 0" data-automation-id="completed-tasks-none">
<div *ngIf="completedTasks?.length === 0" data-automation-id="completed-tasks-none" class="no-results">
{{ 'DETAILS.TASKS.NO_COMPLETED' | translate }}
</div>

View File

@@ -75,25 +75,35 @@ describe('ActivitiStartProcessInstance', () => {
describe('process definitions list', () => {
it('should call service to fetch process definitions', () => {
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalled();
});
it('should call service to fetch process definitions with appId when provided', () => {
let appId = '123';
component.appId = appId;
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
expect(getDefinitionsSpy).toHaveBeenCalledWith(appId);
expect(getDefinitionsSpy).toHaveBeenCalledWith('123');
});
it('should display the correct number of processes in the select list', () => {
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
let selectElement = debugElement.query(By.css('select'));
expect(selectElement.children.length).toBe(3);
});
it('should display the correct process def details', async(() => {
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
let optionEl: HTMLOptionElement = debugElement.queryAll(By.css('select option'))[1].nativeElement;
expect(optionEl.value).toBe('my:process1');
@@ -103,7 +113,10 @@ describe('ActivitiStartProcessInstance', () => {
it('should indicate an error to the user if process defs cannot be loaded', async(() => {
getDefinitionsSpy = getDefinitionsSpy.and.returnValue(Observable.throw({}));
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'appId': change });
fixture.detectChanges();
fixture.whenStable().then(() => {
let errorEl: DebugElement = debugElement.query(By.css('.error-message'));
expect(errorEl).not.toBeNull('Expected error message to be present');
@@ -148,7 +161,8 @@ describe('ActivitiStartProcessInstance', () => {
beforeEach(() => {
component.name = 'My new process';
fixture.detectChanges();
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'appId': change });
});
it('should call service to start process if required fields provided', async(() => {
@@ -235,7 +249,7 @@ describe('ActivitiStartProcessInstance', () => {
expect(startBtn.properties['disabled']).toBe(true);
}));
it('should enable start button when name and process filled out', async(() => {
xit('should enable start button when name and process filled out', async(() => {
fixture.detectChanges();
expect(startBtn.properties['disabled']).toBe(false);
}));
@@ -246,7 +260,8 @@ describe('ActivitiStartProcessInstance', () => {
beforeEach(() => {
getDefinitionsSpy.and.returnValue(Observable.of(fakeProcessDefWithForm));
fixture.detectChanges();
let change = new SimpleChange(null, '123');
component.ngOnChanges({ 'appId': change });
component.onProcessDefChange('my:process1');
fixture.detectChanges();
fixture.whenStable();

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, Output, OnInit, ViewChild, OnChanges, SimpleChanges } from '@angular/core';
import { Component, EventEmitter, Input, Output, ViewChild, OnChanges, SimpleChanges } from '@angular/core';
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
import { ActivitiStartForm } from 'ng2-activiti-form';
import { ProcessInstance } from './../models/process-instance.model';
@@ -31,7 +31,7 @@ declare let dialogPolyfill: any;
templateUrl: './activiti-start-process.component.html',
styleUrls: ['./activiti-start-process.component.css']
})
export class ActivitiStartProcessInstance implements OnInit, OnChanges {
export class ActivitiStartProcessInstance implements OnChanges {
@Input()
appId: string;
@@ -61,10 +61,6 @@ export class ActivitiStartProcessInstance implements OnInit, OnChanges {
}
}
ngOnInit() {
this.load(this.appId);
}
ngOnChanges(changes: SimpleChanges) {
let appId = changes['appId'];
if (appId && (appId.currentValue || appId.currentValue === null)) {
@@ -119,7 +115,11 @@ export class ActivitiStartProcessInstance implements OnInit, OnChanges {
}
isStartFormMissingOrValid() {
return !this.startForm || this.startForm.form.isValid;
if (this.startForm && this.startForm.form && this.startForm.form.isValid) {
return !this.startForm || this.startForm.form.isValid;
} else {
return false;
}
}
validateForm() {

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 './activiti-processlist.component';
export * from './activiti-filters.component';
export * from './activiti-process-instance-header.component';
export * from './activiti-process-instance-tasks.component';
export * from './activiti-process-instance-variables.component';
export * from './activiti-process-comments.component';
export * from './activiti-process-instance-details.component';
export * from './activiti-start-process.component';

View File

@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { AlfrescoApiService } from 'ng2-alfresco-core';
import { AlfrescoApiService, AlfrescoAuthenticationService} from 'ng2-alfresco-core';
import { ProcessInstance, ProcessDefinitionRepresentation } from '../models/index';
import { ProcessFilterRequestRepresentation } from '../models/process-instance-filter.model';
import { ProcessInstanceVariable } from './../models/process-instance-variable.model';
@@ -34,7 +34,7 @@ declare var moment: any;
@Injectable()
export class ActivitiProcessService {
constructor(public apiService: AlfrescoApiService) {
constructor(private authService: AlfrescoAuthenticationService, private apiService: AlfrescoApiService) {
}
/**

View File

@@ -13,3 +13,7 @@
.mdl-tooltip {
will-change: unset;
}
.material-icons {
cursor: pointer;
}

View File

@@ -13,3 +13,7 @@
.mdl-tooltip {
will-change: unset;
}
.material-icons {
cursor: pointer;
}

View File

@@ -48,6 +48,9 @@ describe('ActivitiFilters', () => {
it('should return the filter task list', (done) => {
spyOn(filterList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
const appId = '1';
let change = new SimpleChange(null, appId);
filterList.ngOnChanges({ 'appId': change });
filterList.onSuccess.subscribe((res) => {
expect(res).toBeDefined();
@@ -70,7 +73,8 @@ describe('ActivitiFilters', () => {
spyOn(filterList.activiti, 'getDeployedApplications').and.returnValue(Observable.fromPromise(fakeDeployedApplicationsPromise));
spyOn(filterList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
filterList.appName = 'test';
let change = new SimpleChange(null, 'test');
filterList.ngOnChanges({ 'appName': change });
filterList.onSuccess.subscribe((res) => {
let deployApp: any = filterList.activiti.getDeployedApplications;
@@ -83,9 +87,12 @@ describe('ActivitiFilters', () => {
});
it('should emit an error with a bad response', (done) => {
filterList.appId = '1';
spyOn(filterList.activiti, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeErrorFilterPromise));
const appId = '1';
let change = new SimpleChange(null, appId);
filterList.ngOnChanges({ 'appId': change });
filterList.onError.subscribe((err) => {
expect(err).toBeDefined();
done();
@@ -95,9 +102,12 @@ describe('ActivitiFilters', () => {
});
it('should emit an error with a bad response', (done) => {
filterList.appName = 'fake-app';
spyOn(filterList.activiti, 'getDeployedApplications').and.returnValue(Observable.fromPromise(fakeErrorFilterPromise));
const appId = 'fake-app';
let change = new SimpleChange(null, appId);
filterList.ngOnChanges({ 'appName': change });
filterList.onError.subscribe((err) => {
expect(err).toBeDefined();
done();
@@ -156,4 +166,12 @@ describe('ActivitiFilters', () => {
expect(filterList.getCurrentFilter()).toBe(filter);
});
it('should load Default list when no appid or taskid are provided', () => {
spyOn(filterList, 'getFiltersByAppId').and.stub();
let change = new SimpleChange(null, null);
filterList.ngOnChanges({ 'appName': change });
expect(filterList.getFiltersByAppId).toHaveBeenCalled();
});
});

View File

@@ -75,8 +75,6 @@ export class ActivitiFilters implements OnInit, OnChanges {
this.filter$.subscribe((filter: FilterRepresentationModel) => {
this.filters.push(filter);
});
this.getFilters(this.appId, this.appName);
}
ngOnChanges(changes: SimpleChanges) {
@@ -86,10 +84,12 @@ export class ActivitiFilters implements OnInit, OnChanges {
return;
}
let appName = changes['appName'];
if (appName && appName.currentValue) {
if (appName && appName !== null && appName.currentValue) {
this.getFiltersByAppName(appName.currentValue);
return;
}
this.getFiltersByAppId();
}
/**

View File

@@ -17,3 +17,7 @@
.mdl-tooltip {
will-change: unset;
}
.material-icons {
cursor: pointer;
}

View File

@@ -3,9 +3,13 @@
}
.activiti-task-header__label {
font-weight: bold;
font-weight: bold;
}
.activiti-task-header__value {
color: rgb(68,138,255);
color: rgb(68, 138, 255);
}
.material-icons {
cursor: pointer;
}

View File

@@ -69,7 +69,7 @@ describe('ActivitiTaskList', () => {
ActivitiTaskList
],
providers: [
{ provide: AlfrescoTranslationService, useClass: TranslationMock },
{provide: AlfrescoTranslationService, useClass: TranslationMock},
ActivitiTaskListService
]
}).compileComponents();
@@ -115,10 +115,12 @@ describe('ActivitiTaskList', () => {
it('should return the filtered task list when the input parameters are passed', (done) => {
spyOn(component.activiti, 'getTotalTasks').and.returnValue(Observable.of(fakeGlobalTotalTasks));
spyOn(component.activiti, 'getTasks').and.returnValue(Observable.of(fakeGlobalTask));
component.state = 'open';
component.processDefinitionKey = null;
component.assignment = 'fake-assignee';
component.onSuccess.subscribe( (res) => {
let state = new SimpleChange(null, 'open');
let processDefinitionKey = new SimpleChange(null, null);
let assignment = new SimpleChange(null, 'fake-assignee');
component.onSuccess.subscribe((res) => {
expect(res).toBeDefined();
expect(component.data).toBeDefined();
expect(component.isListEmpty()).not.toBeTruthy();
@@ -127,15 +129,19 @@ describe('ActivitiTaskList', () => {
done();
});
component.ngOnInit();
component.ngOnChanges({'state': state, 'processDefinitionKey': processDefinitionKey, 'assignment': assignment});
fixture.detectChanges();
});
it('should return the filtered task list by processDefinitionKey', (done) => {
spyOn(component.activiti, 'getTotalTasks').and.returnValue(Observable.of(fakeGlobalTotalTasks));
spyOn(component.activiti, 'getTasks').and.returnValue(Observable.of(fakeGlobalTask));
component.state = 'open';
component.processDefinitionKey = 'fakeprocess';
component.assignment = 'fake-assignee';
component.onSuccess.subscribe( (res) => {
let state = new SimpleChange(null, 'open');
let processDefinitionKey = new SimpleChange(null, 'fakeprocess');
let assignment = new SimpleChange(null, 'fake-assignee');
component.onSuccess.subscribe((res) => {
expect(res).toBeDefined();
expect(component.data).toBeDefined();
expect(component.isListEmpty()).not.toBeTruthy();
@@ -143,7 +149,10 @@ describe('ActivitiTaskList', () => {
expect(component.data.getRows()[0].getValue('name')).toEqual('No name');
done();
});
component.ngOnInit();
component.ngOnChanges({'state': state, 'processDefinitionKey': processDefinitionKey, 'assignment': assignment});
fixture.detectChanges();
});
it('should return a currentId null when the taskList is empty', () => {
@@ -153,15 +162,19 @@ describe('ActivitiTaskList', () => {
it('should throw an exception when the response is wrong', (done) => {
spyOn(component.activiti, 'getTotalTasks').and.returnValue(Observable.throw(fakeErrorTaskList));
component.state = 'open';
component.assignment = 'fake-assignee';
component.onError.subscribe( (err) => {
let state = new SimpleChange(null, 'open');
let assignment = new SimpleChange(null, 'fake-assignee');
component.onError.subscribe((err) => {
expect(err).toBeDefined();
expect(err.error).toBe('wrong request');
done();
});
component.ngOnInit();
component.ngOnChanges({'state': state, 'assignment': assignment});
fixture.detectChanges();
});
it('should reload tasks when reload() is called', (done) => {
@@ -170,7 +183,7 @@ describe('ActivitiTaskList', () => {
component.state = 'open';
component.assignment = 'fake-assignee';
component.ngOnInit();
component.onSuccess.subscribe( (res) => {
component.onSuccess.subscribe((res) => {
expect(res).toBeDefined();
expect(component.data).toBeDefined();
expect(component.isListEmpty()).not.toBeTruthy();
@@ -220,7 +233,7 @@ describe('ActivitiTaskList', () => {
const appId = '1';
let change = new SimpleChange(null, appId);
component.onSuccess.subscribe( (res) => {
component.onSuccess.subscribe((res) => {
expect(res).toBeDefined();
expect(component.data).toBeDefined();
expect(component.isListEmpty()).not.toBeTruthy();

View File

@@ -83,7 +83,6 @@ export class ActivitiTaskList implements OnInit, OnChanges {
if (!this.data) {
this.data = this.initDefaultSchemaColumns();
}
this.reload();
}
ngOnChanges(changes: SimpleChanges) {