process details improvements (#1408)

* process details improvements

- new: redirect to task instead of opening a dialog (Activiti parity)
- fixed: task headers for nameless tasks
- fixed: cursor style for task list items
- new: stub for ng-1 directive api (Activiti stencils)
- code cleanup

* error handling fixes
This commit is contained in:
Denys Vuika
2017-01-09 12:57:03 +00:00
committed by Will Abson
parent 35d2109f17
commit 885e0e85aa
15 changed files with 130 additions and 188 deletions

View File

@@ -5,9 +5,9 @@
<!-- TABS -->
<div class="mdl-layout__tab-bar mdl-js-ripple-effect">
<a id="tasks-header" href="#tasks" class="mdl-layout__tab is-active">TASKS</a>
<a id="processes-header" href="#processes" class="mdl-layout__tab" (click)="activeProcess()">PROCESSES</a>
<a id="report-header" href="#report" class="mdl-layout__tab" (click)="activeReports()">REPORTS</a>
<a id="tasks-header" href="#tasks" class="mdl-layout__tab" [class.is-active]="activeTab === 'tasks'" (click)="activeTab = 'tasks'">TASKS</a>
<a id="processes-header" href="#processes" class="mdl-layout__tab" [class.is-active]="activeTab === 'processes'" (click)="activeTab = 'processes'">PROCESSES</a>
<a id="report-header" href="#report" class="mdl-layout__tab" [class.is-active]="activeTab === 'reports'" (click)="activeTab = 'reports'">REPORTS</a>
</div>
</header>
@@ -15,7 +15,7 @@
<!-- TASKS COMPONENT -->
<section class="mdl-layout__tab-panel is-active" id="tasks">
<section class="mdl-layout__tab-panel" [class.is-active]="activeTab === 'tasks'" id="tasks">
<div class="page-content">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--2-col task-column mdl-shadow--2dp">
@@ -52,8 +52,8 @@
<!-- PROCESS COMPONENT -->
<section class="mdl-layout__tab-panel" id="processes">
<div class="page-content" *ngIf="processTabActivie">
<section class="mdl-layout__tab-panel" [class.is-active]="activeTab === 'processes'" id="processes">
<div class="page-content" *ngIf="activeTab === 'processes'">
<div class="page-content">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--2-col task-column mdl-shadow--2dp">
@@ -81,9 +81,11 @@
<div class="mdl-cell mdl-cell--7-col task-column mdl-shadow--2dp" *ngIf="!isStartProcessMode()">
<span><h5>Process Details</h5></span>
<hr>
<activiti-process-instance-details [processInstanceId]="currentProcessInstanceId"
(activitiprocesslist)="taskFormCompleted()"
(processCancelled)="processCancelled()"></activiti-process-instance-details>
<activiti-process-instance-details
[processInstanceId]="currentProcessInstanceId"
(processCancelled)="processCancelled()"
(taskClick)="onProcessDetailsTaskClick($event)">
</activiti-process-instance-details>
</div>
<div class="mdl-cell mdl-cell--7-col task-column" *ngIf="isStartProcessMode()">
<span>Start Process</span>
@@ -98,8 +100,8 @@
<!-- ANALYTICS COMPONENT -->
<section class="mdl-layout__tab-panel" id="report">
<div class="page-content" *ngIf="reportsTabActivie">
<section class="mdl-layout__tab-panel" [class.is-active]="activeTab === 'reports'" id="report">
<div class="page-content" *ngIf="activeTab === 'reports'">
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--4-col task-column mdl-shadow--2dp">
<span><h5>Report List</h5></span>

View File

@@ -19,9 +19,9 @@ import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular
import {
ActivitiApps,
ActivitiFilters,
ActivitiTaskDetails,
ActivitiTaskList,
FilterRepresentationModel
FilterRepresentationModel,
TaskDetailsEvent
} from 'ng2-activiti-tasklist';
import {
ActivitiProcessFilters,
@@ -52,18 +52,12 @@ const currentProcessIdNew = '__NEW__';
})
export class ActivitiDemoComponent implements AfterViewInit {
@ViewChild(ActivitiApps)
activitiapps: ActivitiApps;
@ViewChild(ActivitiFilters)
activitifilter: ActivitiFilters;
@ViewChild(ActivitiTaskList)
activititasklist: ActivitiTaskList;
@ViewChild(ActivitiTaskDetails)
activitidetails: ActivitiTaskDetails;
@ViewChild(ActivitiProcessFilters)
activitiprocessfilter: ActivitiProcessFilters;
@@ -89,9 +83,7 @@ export class ActivitiDemoComponent implements AfterViewInit {
taskSchemaColumns: any [] = [];
processSchemaColumns: any [] = [];
processTabActivie: boolean = false;
reportsTabActivie: boolean = false;
activeTab: string = 'tasks'; // tasks|processes|reports
taskFilter: FilterRepresentationModel;
report: any;
@@ -219,10 +211,6 @@ export class ActivitiDemoComponent implements AfterViewInit {
this.activitiprocesslist.reload();
}
taskFormCompleted(data: any) {
this.activitiprocesslist.reload();
}
onFormCompleted(form) {
this.activititasklist.reload();
this.currentTaskId = null;
@@ -237,14 +225,6 @@ export class ActivitiDemoComponent implements AfterViewInit {
this.loadStencilScriptsInPageFromActiviti();
}
activeProcess() {
this.processTabActivie = true;
}
activeReports() {
this.reportsTabActivie = true;
}
loadStencilScriptsInPageFromActiviti() {
this.apiService.getInstance().activiti.scriptFileApi.getControllers().then(response => {
if (response) {
@@ -256,4 +236,10 @@ export class ActivitiDemoComponent implements AfterViewInit {
});
}
onProcessDetailsTaskClick(event: TaskDetailsEvent) {
event.preventDefault();
this.currentTaskId = event.value.id;
this.activeTab = 'tasks';
}
}

View File

@@ -47,7 +47,7 @@ export class EcmModelService {
});
}
},
this.handleError
err => this.handleError(err)
);
});
@@ -72,10 +72,10 @@ export class EcmModelService {
observer.complete();
});
},
this.handleError
err => this.handleError(err)
);
},
this.handleError
err => this.handleError(err)
);
});
}
@@ -95,7 +95,7 @@ export class EcmModelService {
observer.complete();
}
},
this.handleError
err => this.handleError(err)
);
});
}
@@ -111,9 +111,9 @@ export class EcmModelService {
observer.next(typeCreated);
observer.complete();
},
this.handleError);
err => this.handleError(err));
},
this.handleError);
err => this.handleError(err));
});
}
@@ -126,25 +126,25 @@ export class EcmModelService {
public activeEcmModel(modelName: string): Observable<any> {
return Observable.fromPromise(this.apiService.getInstance().core.customModelApi.activateCustomModel(modelName))
.map(this.toJson)
.catch(this.handleError);
.catch(err => this.handleError(err));
}
public createEcmModel(modelName: string, nameSpace: string): Observable<any> {
return Observable.fromPromise(this.apiService.getInstance().core.customModelApi.createCustomModel('DRAFT', '', modelName, modelName, nameSpace))
.map(this.toJson)
.catch(this.handleError);
.catch(err => this.handleError(err));
}
public getEcmModels(): Observable<any> {
return Observable.fromPromise(this.apiService.getInstance().core.customModelApi.getAllCustomModel())
.map(this.toJson)
.catch(this.handleError);
.catch(err => this.handleError(err));
}
public getEcmType(modelName: string): Observable<any> {
return Observable.fromPromise(this.apiService.getInstance().core.customModelApi.getAllCustomType(modelName))
.map(this.toJson)
.catch(this.handleError);
.catch(err => this.handleError(err));
}
public createEcmType(typeName: string, modelName: string, parentType: string): Observable<any> {
@@ -152,7 +152,7 @@ export class EcmModelService {
return Observable.fromPromise(this.apiService.getInstance().core.customModelApi.createCustomType(modelName, name, parentType, typeName, ''))
.map(this.toJson)
.catch(this.handleError);
.catch(err => this.handleError(err));
}
public addPropertyToAType(modelName: string, typeName: string, formFields: any) {
@@ -177,11 +177,11 @@ export class EcmModelService {
return Observable.fromPromise(this.apiService.getInstance().core.customModelApi.addPropertyToType(modelName, name, properties))
.map(this.toJson)
.catch(this.handleError);
.catch(err => this.handleError(err));
}
public cleanNameType(name: string): string {
cleanNameType(name: string): string {
let cleanName = name;
if (name.indexOf(':') !== -1) {
cleanName = name.split(':')[1];

View File

@@ -5,8 +5,11 @@ window.angular = {
return {
controller: function (controllerName) {
console.info('ng1: controller %s requested', controllerName);
return {
}
return {}
},
directive: function (directiveName) {
console.info('ng1: directive %s requested', directiveName);
return {}
}
}
}

View File

@@ -4,8 +4,10 @@
<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>
<activiti-process-instance-tasks
[processInstanceDetails]="processInstanceDetails"
(taskClick)="onTaskClicked($event)">
</activiti-process-instance-tasks>
</div>
</div>
<div class="mdl-cell mdl-cell--4-col" data-automation-id="header-status" *ngIf="isRunning()">

View File

@@ -21,7 +21,7 @@ import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Rx';
import { AlfrescoTranslateService, CoreModule } from 'ng2-alfresco-core';
import { ActivitiFormModule, FormModel, FormService } from 'ng2-activiti-form';
import { ActivitiFormModule, FormService } from 'ng2-activiti-form';
import { ActivitiTaskListModule } from 'ng2-activiti-tasklist';
import { ActivitiProcessInstanceDetails } from './activiti-process-instance-details.component';
@@ -139,20 +139,4 @@ describe('ActivitiProcessInstanceDetails', () => {
});
});
describe('events', () => {
beforeEach(async(() => {
component.processInstanceId = '123';
fixture.detectChanges();
fixture.whenStable();
}));
it('should emit a task form completed event when task form completed', () => {
let emitSpy: jasmine.Spy = spyOn(component.taskFormCompleted, 'emit');
component.bubbleTaskFormCompleted(new FormModel());
expect(emitSpy).toHaveBeenCalled();
});
});
});

View File

@@ -17,13 +17,13 @@
import { Component, Input, ViewChild, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { AlfrescoTranslateService, LogService } from 'ng2-alfresco-core';
import { TaskDetailsEvent } from 'ng2-activiti-tasklist';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { ActivitiProcessInstanceHeader } from './activiti-process-instance-header.component';
import { ActivitiProcessInstanceTasks } from './activiti-process-instance-tasks.component';
import { ProcessInstance } from '../models/process-instance.model';
declare let componentHandler: any;
@Component({
selector: 'activiti-process-instance-details',
moduleId: module.id,
@@ -51,7 +51,7 @@ export class ActivitiProcessInstanceDetails implements OnChanges {
processCancelled: EventEmitter<any> = new EventEmitter<any>();
@Output()
taskFormCompleted: EventEmitter<any> = new EventEmitter<any>();
taskClick: EventEmitter<TaskDetailsEvent> = new EventEmitter<TaskDetailsEvent>();
processInstanceDetails: ProcessInstance;
@@ -98,10 +98,6 @@ export class ActivitiProcessInstanceDetails implements OnChanges {
}
}
bubbleTaskFormCompleted(data: any) {
this.taskFormCompleted.emit(data);
}
isRunning(): boolean {
return this.processInstanceDetails && !this.processInstanceDetails.ended;
}
@@ -114,4 +110,9 @@ export class ActivitiProcessInstanceDetails implements OnChanges {
this.logService.error(err);
});
}
// bubbles (taskClick) event
onTaskClicked(event: TaskDetailsEvent) {
this.taskClick.emit(event);
}
}

View File

@@ -47,3 +47,7 @@
display: block;
padding: 12px;
}
.process-tasks__task-item {
cursor: pointer;
}

View File

@@ -1,6 +1,5 @@
<div *ngIf="showRefreshButton" class="process-tasks-refresh" >
<button (click)="onRefreshClicked()"
class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect">
<button (click)="onRefreshClicked()" class="mdl-button mdl-button--icon mdl-js-button mdl-js-ripple-effect">
<i class="material-icons">refresh</i>
</button>
</div>
@@ -12,11 +11,13 @@
<div class="menu-container" *ngIf="activeTasks?.length > 0" data-automation-id="active-tasks">
<ul class='mdl-list'>
<li class="mdl-list__item mdl-list__item--two-line" *ngFor="let task of activeTasks">
<li class="mdl-list__item mdl-list__item--two-line process-tasks__task-item" *ngFor="let task of activeTasks">
<span class="mdl-list__item-primary-content" (click)="clickTask($event, task)">
<i class="material-icons mdl-list__item-icon">assignment</i>
<span>{{task.name}}</span>
<span class="mdl-list__item-sub-title">{{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user: getUserFullName(task.assignee), created: getFormatDate(task.created, 'mediumDate') } }}</span>
<span>{{task.name || 'Nameless task'}}</span>
<span class="mdl-list__item-sub-title">
{{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user: getUserFullName(task.assignee), created: getFormatDate(task.created, 'mediumDate') } }}
</span>
</span>
</li>
</ul>
@@ -34,12 +35,13 @@
<!--IF START TASK COMPLETED -->
<div class="menu-container">
<ul class='mdl-list'>
<li class="mdl-list__item mdl-list__item--two-line">
<li class="mdl-list__item mdl-list__item--two-line process-tasks__task-item">
<span class="mdl-list__item-primary-content" (click)="clickStartTask($event)">
<i class="material-icons mdl-list__item-icon">assignment</i>
<span>{{ 'DETAILS.LABELS.START_FORM'|translate }}</span>
<span class="mdl-list__item-sub-title">{{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user:
getUserFullName(processInstanceDetails.startedBy), created: getFormatDate(processInstanceDetails.started, 'mediumDate') } }}</span>
<span class="mdl-list__item-sub-title">
{{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user:getUserFullName(processInstanceDetails.startedBy), created: getFormatDate(processInstanceDetails.started, 'mediumDate') } }}
</span>
</span>
</li>
</ul>
@@ -53,12 +55,13 @@
<div class="menu-container" *ngIf="completedTasks?.length > 0" data-automation-id="completed-tasks">
<ul class='mdl-list'>
<li class="mdl-list__item mdl-list__item--two-line" *ngFor="let task of completedTasks">
<li class="mdl-list__item mdl-list__item--two-line process-tasks__task-item" *ngFor="let task of completedTasks">
<span class="mdl-list__item-primary-content" (click)="clickTask($event, task)">
<i class="material-icons mdl-list__item-icon">assignment</i>
<span>{{task.name}}</span>
<span class="mdl-list__item-sub-title">{{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user:
getUserFullName(task.assignee), created: getFormatDate(task.created, 'mediumDate') } }}</span>
<span>{{task.name || 'Nameless task'}}</span>
<span class="mdl-list__item-sub-title">
{{ 'DETAILS.LABELS.TASK_SUBTITLE' | translate:{user:getUserFullName(task.assignee), created: getFormatDate(task.created, 'mediumDate') } }}
</span>
</span>
</li>
</ul>
@@ -68,16 +71,6 @@
{{ 'DETAILS.TASKS.NO_COMPLETED' | translate }}
</div>
<dialog class="mdl-dialog task-details-dialog" #dialog>
<h4 class="mdl-dialog__title">{{ 'DETAILS.TASKS.TASK_DETAILS' | translate }}</h4>
<div class="mdl-dialog__content form__size">
<activiti-task-details [taskId]="selectedTaskId" (formCompleted)="onTaskFormCompleted()" #taskdetails></activiti-task-details>
</div>
<div class="mdl-dialog__actions">
<button type="button" (click)="closeDialog()" class="mdl-button close" data-automation-id="button-task-close">{{ 'DETAILS.TASKS.TASK_CLOSE' | translate }}</button>
</div>
</dialog>
<dialog *ngIf="hasStartFormDefined()" class="mdl-dialog task-details-dialog" #startDialog>
<h4 class="mdl-dialog__title">{{ 'DETAILS.LABELS.START_FORM'|translate }}</h4>
<div class="mdl-dialog__content form__size">

View File

@@ -158,51 +158,4 @@ describe('ActivitiProcessInstanceTasks', () => {
expect(getProcessTasksSpy).toHaveBeenCalled();
});
});
describe('task details', () => {
let closeSpy;
beforeEach(async(() => {
closeSpy = spyOn(component.dialog.nativeElement, 'close');
component.processInstanceDetails = exampleProcessInstance;
fixture.detectChanges();
fixture.whenStable();
component.taskdetails = jasmine.createSpyObj('taskdetails', [
'loadDetails'
]);
}));
it('should display task details dialog when task clicked', () => {
let showModalSpy = spyOn(component.dialog.nativeElement, 'showModal');
component.clickTask({}, new TaskDetailsModel(taskDetailsMock));
expect(showModalSpy).toHaveBeenCalled();
});
it('should close the task details dialog when close button clicked', () => {
component.clickTask({}, new TaskDetailsModel(taskDetailsMock));
fixture.detectChanges();
let closeButton: DebugElement = debugElement.query(By.css('[data-automation-id="button-task-close"]'));
closeButton.triggerEventHandler('click', null);
expect(closeSpy).toHaveBeenCalled();
});
it('should output event when task form completed', async(() => {
let emitSpy = spyOn(component.taskFormCompleted, 'emit');
fixture.detectChanges();
component.clickTask({}, new TaskDetailsModel(taskDetailsMock));
fixture.detectChanges();
component.onTaskFormCompleted();
expect(emitSpy).toHaveBeenCalled();
}));
it('should close dialog when task form completed', async(() => {
component.clickTask({}, new TaskDetailsModel(taskDetailsMock));
fixture.detectChanges();
component.onTaskFormCompleted();
expect(closeSpy).toHaveBeenCalled();
}));
});
});

View File

@@ -20,7 +20,7 @@ import { DatePipe } from '@angular/common';
import { Observable, Observer } from 'rxjs/Rx';
import { AlfrescoTranslateService, LogService } from 'ng2-alfresco-core';
import { ActivitiProcessService } from './../services/activiti-process.service';
import { TaskDetailsModel } from 'ng2-activiti-tasklist';
import { TaskDetailsModel, TaskDetailsEvent } from 'ng2-activiti-tasklist';
import { ProcessInstance } from '../models/process-instance.model';
declare let componentHandler: any;
@@ -40,9 +40,6 @@ export class ActivitiProcessInstanceTasks implements OnInit, OnChanges {
@Input()
showRefreshButton: boolean = true;
@Output()
taskFormCompleted = new EventEmitter();
activeTasks: TaskDetailsModel[] = [];
completedTasks: TaskDetailsModel[] = [];
@@ -53,9 +50,6 @@ export class ActivitiProcessInstanceTasks implements OnInit, OnChanges {
completedTask$: Observable<TaskDetailsModel>;
message: string;
selectedTaskId: string;
processId: string;
@ViewChild('dialog')
@@ -67,6 +61,9 @@ export class ActivitiProcessInstanceTasks implements OnInit, OnChanges {
@ViewChild('taskdetails')
taskdetails: any;
@Output()
taskClick: EventEmitter<TaskDetailsEvent> = new EventEmitter<TaskDetailsEvent>();
constructor(private translate: AlfrescoTranslateService,
private activitiProcess: ActivitiProcessService,
private logService: LogService) {
@@ -94,12 +91,12 @@ export class ActivitiProcessInstanceTasks implements OnInit, OnChanges {
}
}
public load(processId: string) {
load(processId: string) {
this.loadActive(processId);
this.loadCompleted(processId);
}
public loadActive(processId: string) {
loadActive(processId: string) {
this.activeTasks = [];
if (processId) {
this.activitiProcess.getProcessTasks(processId, null).subscribe(
@@ -117,7 +114,7 @@ export class ActivitiProcessInstanceTasks implements OnInit, OnChanges {
}
}
public loadCompleted(processId: string) {
loadCompleted(processId: string) {
this.completedTasks = [];
if (processId) {
this.activitiProcess.getProcessTasks(processId, 'completed').subscribe(
@@ -157,17 +154,17 @@ export class ActivitiProcessInstanceTasks implements OnInit, OnChanges {
}
}
public clickTask($event: any, task: TaskDetailsModel) {
this.selectedTaskId = task.id;
this.showDialog();
clickTask($event: any, task: TaskDetailsModel) {
let args = new TaskDetailsEvent(task);
this.taskClick.emit(args);
}
public clickStartTask() {
clickStartTask() {
this.processId = this.processInstanceDetails.id;
this.showStartDialog();
}
public showStartDialog() {
showStartDialog() {
if (!this.startDialog.nativeElement.showModal) {
dialogPolyfill.registerDialog(this.startDialog.nativeElement);
}
@@ -177,35 +174,13 @@ export class ActivitiProcessInstanceTasks implements OnInit, OnChanges {
}
}
public showDialog() {
if (!this.dialog.nativeElement.showModal) {
dialogPolyfill.registerDialog(this.dialog.nativeElement);
}
if (this.dialog) {
this.dialog.nativeElement.showModal();
}
}
public closeSartDialog() {
closeSartDialog() {
if (this.startDialog) {
this.startDialog.nativeElement.close();
}
}
private closeDialog() {
if (this.dialog) {
this.dialog.nativeElement.close();
}
this.selectedTaskId = null;
}
public onTaskFormCompleted() {
this.closeDialog();
this.load(this.processInstanceDetails.id);
this.taskFormCompleted.emit(this.processInstanceDetails.id);
}
public onRefreshClicked() {
onRefreshClicked() {
this.load(this.processInstanceDetails.id);
}
}

View File

@@ -20,3 +20,4 @@ export * from './filter.model';
export * from './icon.model';
export * from './user.model';
export * from './task-details.model';
export * from './task-details.event';

View File

@@ -0,0 +1,40 @@
/*!
* @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 { TaskDetailsModel } from './task-details.model';
export class TaskDetailsEvent {
private _value: TaskDetailsModel;
private _defaultPrevented: boolean = false;
get value(): TaskDetailsModel {
return this._value;
}
get defaultPrevented() {
return this._defaultPrevented;
}
constructor(value: TaskDetailsModel) {
this._value = value;
}
preventDefault() {
this._defaultPrevented = true;
}
}

View File

@@ -40,7 +40,7 @@ export class BpmUserService {
getCurrentUserInfo(): Observable<BpmUserModel> {
return Observable.fromPromise(this.alfrescoJsApi.getInstance().activiti.profileApi.getProfile())
.map((data) => <BpmUserModel> data)
.catch(this.handleError);
.catch(err => this.handleError(err));
}
getCurrentUserProfileImage(): string {

View File

@@ -40,10 +40,8 @@ export class EcmUserService {
*/
getUserInfo(userName: string): Observable<EcmUserModel> {
return Observable.fromPromise(this.callApiGetPersonInfo(userName))
.map(
(data) => <EcmUserModel> data['entry']
)
.catch(this.handleError);
.map(data => <EcmUserModel> data['entry'])
.catch(err => this.handleError(err));
}
getCurrentUserInfo() {