[ADF - 1114] Process comments - Share the same comment between Task and Process (#2105)

* changed process comment template and used task comment component

* changed getTaskComment, getProcessInstanceComment, addTaskComment and addProcessInstanceComment to getComments and addComment

* removed provider from task comment component

* extended tasklist service and changed comment-list container css

* changed process service methods and associated test cases

* added error emitter and test cases for process-comments component

* fixed process header component test case
This commit is contained in:
Infad Kachancheri
2017-07-19 22:26:12 +05:30
committed by Eugenio Romano
parent 7fbdf4ed8a
commit 83a6f0d425
12 changed files with 154 additions and 317 deletions

View File

@@ -1,35 +1 @@
<span class="activiti-label mdl-badge" <adf-comments [readOnly]="readOnly" [taskId]="processInstanceId" (error)="onError($event)" #activiticomments></adf-comments>
[attr.data-badge]="comments?.length">{{ 'DETAILS.LABELS.COMMENTS' |translate }}</span>
<div id="addComment" (click)="showDialog()" class="icon material-icons">add</div>
<div class="mdl-tooltip" for="addComment">
{{ 'DETAILS.COMMENTS.BUTTON.ADD' |translate }}
</div>
<div class="menu-container" *ngIf="comments?.length > 0">
<ul class='mdl-list'>
<li class="mdl-list__item list-wrap" *ngFor="let comment of comments">
<span class="mdl-list__item-primary-content hide-long-names">
<i class="material-icons mdl-list__item-icon">comment</i>
{{comment.message}}
</span>
</li>
</ul>
</div>
<div *ngIf="comments?.length === 0" data-automation-id="comments-none">
{{ 'DETAILS.COMMENTS.NONE' | translate }}
</div>
<dialog class="mdl-dialog" #dialog>
<h4 class="mdl-dialog__title">{{ 'DETAILS.COMMENTS.ADD_DIALOG.TITLE' |translate }}</h4>
<div class="mdl-dialog__content">
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<textarea class="mdl-textfield__input" type="text" [(ngModel)]="message" rows="1" id="commentText"></textarea>
<label class="mdl-textfield__label" for="commentText">{{ 'DETAILS.COMMENTS.ADD_DIALOG.LABEL.MESSAGE' |translate }}</label>
</div>
</div>
<div class="mdl-dialog__actions">
<button type="button" (click)="add()" class="mdl-button">{{ 'DETAILS.COMMENTS.ADD_DIALOG.BUTTON.ADD' |translate }}</button>
<button type="button" (click)="cancel()" class="mdl-button close">{{ 'DETAILS.COMMENTS.ADD_DIALOG.BUTTON.CANCEL' |translate }}</button>
</div>
</dialog>

View File

@@ -15,13 +15,18 @@
* limitations under the License. * limitations under the License.
*/ */
import { SimpleChange } from '@angular/core'; import { DatePipe } from '@angular/common';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { MdInputModule } from '@angular/material';
import { Observable } from 'rxjs/Rx'; import { Observable } from 'rxjs/Rx';
import { ActivitiFormModule } from 'ng2-activiti-form'; import {
CommentListComponent,
CommentsComponent,
TaskListService
} from 'ng2-activiti-tasklist';
import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core'; import { AlfrescoTranslationService, CoreModule } from 'ng2-alfresco-core';
import { DataTableModule } from 'ng2-alfresco-datatable';
import { TranslationMock } from './../assets/translation.service.mock'; import { TranslationMock } from './../assets/translation.service.mock';
import { ProcessService } from './../services/process.service'; import { ProcessService } from './../services/process.service';
@@ -30,24 +35,27 @@ import { ProcessCommentsComponent } from './process-comments.component';
describe('ActivitiProcessInstanceComments', () => { describe('ActivitiProcessInstanceComments', () => {
let componentHandler: any; let componentHandler: any;
let service: ProcessService; let service: TaskListService;
let component: ProcessCommentsComponent; let component: ProcessCommentsComponent;
let fixture: ComponentFixture<ProcessCommentsComponent>; let fixture: ComponentFixture<ProcessCommentsComponent>;
let getCommentsSpy: jasmine.Spy; let getCommentsSpy: jasmine.Spy;
let addCommentSpy: jasmine.Spy;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
CoreModule, CoreModule,
ActivitiFormModule DataTableModule,
MdInputModule
], ],
declarations: [ declarations: [
ProcessCommentsComponent ProcessCommentsComponent,
CommentsComponent,
CommentListComponent
], ],
providers: [ providers: [
{ provide: AlfrescoTranslationService, useClass: TranslationMock }, { provide: AlfrescoTranslationService, useClass: TranslationMock },
ProcessService { provide: TaskListService, useClass: ProcessService },
DatePipe
] ]
}).compileComponents(); }).compileComponents();
})); }));
@@ -56,16 +64,13 @@ describe('ActivitiProcessInstanceComments', () => {
fixture = TestBed.createComponent(ProcessCommentsComponent); fixture = TestBed.createComponent(ProcessCommentsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
service = fixture.debugElement.injector.get(ProcessService); service = fixture.debugElement.injector.get(TaskListService);
getCommentsSpy = spyOn(service, 'getProcessInstanceComments').and.returnValue(Observable.of([{ getCommentsSpy = spyOn(service, 'getComments').and.returnValue(Observable.of([
message: 'Test1' { message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} },
}, { { message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} },
message: 'Test2' { message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} }
}, { ]));
message: 'Test3'
}]));
addCommentSpy = spyOn(service, 'addProcessInstanceComment').and.returnValue(Observable.of({id: 123, message: 'Test'}));
componentHandler = jasmine.createSpyObj('componentHandler', [ componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered', 'upgradeAllRegistered',
@@ -75,118 +80,75 @@ describe('ActivitiProcessInstanceComments', () => {
}); });
it('should load comments when processInstanceId specified', () => { it('should load comments when processInstanceId specified', () => {
let change = new SimpleChange(null, '123', true); component.processInstanceId = '123';
component.ngOnChanges({ 'processInstanceId': change }); fixture.detectChanges();
expect(getCommentsSpy).toHaveBeenCalled(); expect(getCommentsSpy).toHaveBeenCalled();
}); });
it('should emit an error when an error occurs loading comments', () => { it('should emit an error when an error occurs loading comments', () => {
let emitSpy = spyOn(component.error, 'emit'); let emitSpy = spyOn(component.error, 'emit');
getCommentsSpy.and.returnValue(Observable.throw({})); getCommentsSpy.and.returnValue(Observable.throw({}));
let change = new SimpleChange(null, '123', true); component.processInstanceId = '123';
component.ngOnChanges({ 'processInstanceId': change }); fixture.detectChanges();
expect(emitSpy).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalled();
}); });
it('should not comments when no processInstanceId is specified', () => { it('should not load comments when no processInstanceId is specified', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(getCommentsSpy).not.toHaveBeenCalled(); expect(getCommentsSpy).not.toHaveBeenCalled();
}); });
it('should display comments when the process has comments', async(() => { it('should display comments when the process has comments', async(() => {
let change = new SimpleChange(null, '123', true); component.processInstanceId = '123';
component.ngOnChanges({ 'processInstanceId': change }); fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelectorAll('#comment-message').length).toBe(3);
expect(fixture.nativeElement.querySelector('#comment-message:empty')).toBeNull();
});
}));
it('should display comments count when the process has comments', () => {
component.processInstanceId = '123';
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('ul.mdl-list li')).length).toBe(3); let element = fixture.nativeElement.querySelector('#comment-header');
expect(element.innerText).toContain('(3)');
}); });
})); });
it('should not display comments when the process has no comments', async(() => { it('should not display comments when the process has no comments', async(() => {
component.processInstanceId = '123'; component.processInstanceId = '123';
getCommentsSpy.and.returnValue(Observable.of([])); getCommentsSpy.and.returnValue(Observable.of([]));
fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('ul.mdl-list li')).length).toBe(0); expect(fixture.nativeElement.querySelector('#comment-container')).toBeNull();
}); });
})); }));
describe('change detection', () => { it('should not display comments input by default', async(() => {
component.processInstanceId = '123';
let change = new SimpleChange('123', '456', true); fixture.whenStable().then(() => {
let nullChange = new SimpleChange('123', null, true);
beforeEach(async(() => {
component.processInstanceId = '123';
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { expect(fixture.nativeElement.querySelector('#comment-input')).toBeNull();
getCommentsSpy.calls.reset();
});
}));
it('should fetch new comments when processInstanceId changed', () => {
component.ngOnChanges({ 'processInstanceId': change });
expect(getCommentsSpy).toHaveBeenCalledWith('456');
}); });
}));
it('should NOT fetch new comments when empty changeset made', () => { it('should not display comments input when the process is readonly', async(() => {
component.ngOnChanges({}); component.readOnly = true;
expect(getCommentsSpy).not.toHaveBeenCalled(); fixture.detectChanges();
}); fixture.whenStable().then(() => {
it('should NOT fetch new comments when processInstanceId changed to null', () => {
component.ngOnChanges({ 'processInstanceId': nullChange });
expect(getCommentsSpy).not.toHaveBeenCalled();
});
it('should set a placeholder message when processInstanceId changed to null', () => {
component.ngOnChanges({ 'processInstanceId': nullChange });
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.query(By.css('[data-automation-id="comments-none"]'))).not.toBeNull(); expect(fixture.nativeElement.querySelector('#comment-input')).toBeNull();
}); });
}); }));
describe('Add comment', () => { it('should display comments input when the process isn\'t readonly', async(() => {
component.readOnly = false;
beforeEach(async(() => { fixture.detectChanges();
component.processInstanceId = '123'; fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable(); expect(fixture.nativeElement.querySelector('#comment-input')).not.toBeNull();
}));
it('should display a dialog to the user when the Add button clicked', () => {
let dialogEl = fixture.debugElement.query(By.css('.mdl-dialog')).nativeElement;
let showSpy: jasmine.Spy = spyOn(dialogEl, 'showModal');
component.showDialog();
expect(showSpy).toHaveBeenCalled();
}); });
}));
it('should call service to add a comment', () => {
component.showDialog();
component.message = 'Test comment';
component.add();
expect(addCommentSpy).toHaveBeenCalledWith('123', 'Test comment');
});
it('should emit an error when an error occurs adding the comment', () => {
let emitSpy = spyOn(component.error, 'emit');
addCommentSpy.and.returnValue(Observable.throw({}));
component.showDialog();
component.message = 'Test comment';
component.add();
expect(emitSpy).toHaveBeenCalled();
});
it('should close add dialog when close button clicked', () => {
let dialogEl = fixture.debugElement.query(By.css('.mdl-dialog')).nativeElement;
let closeSpy: jasmine.Spy = spyOn(dialogEl, 'close');
component.showDialog();
component.cancel();
expect(closeSpy).toHaveBeenCalled();
});
});
}); });

View File

@@ -15,117 +15,40 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Comment } from 'ng2-activiti-tasklist'; import { TaskListService } from 'ng2-activiti-tasklist';
import { AlfrescoTranslationService } from 'ng2-alfresco-core'; import { AlfrescoTranslationService } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { ProcessService } from './../services/process.service'; import { ProcessService } from './../services/process.service';
declare let componentHandler: any;
declare let dialogPolyfill: any;
@Component({ @Component({
selector: 'adf-process-instance-comments, activiti-process-instance-comments', selector: 'adf-process-instance-comments, activiti-process-instance-comments',
templateUrl: './process-comments.component.html', templateUrl: './process-comments.component.html',
styleUrls: ['./process-comments.component.css'], styleUrls: ['./process-comments.component.css'],
providers: [ProcessService] providers: [{provide: TaskListService, useClass: ProcessService}]
}) })
export class ProcessCommentsComponent implements OnChanges { export class ProcessCommentsComponent {
@Input() @Input()
processInstanceId: string; processInstanceId: string;
@Input()
readOnly: boolean = true;
@Output() @Output()
error: EventEmitter<any> = new EventEmitter<any>(); error: EventEmitter<any> = new EventEmitter<any>();
@ViewChild('dialog')
dialog: any;
comments: Comment [] = [];
commentObserver: Observer<Comment>;
comment$: Observable<Comment>;
message: string;
/** /**
* Constructor * Constructor
* @param translate Translation service * @param translate Translation service
* @param activitiProcess Process service
*/ */
constructor(private translate: AlfrescoTranslationService, constructor(private translate: AlfrescoTranslationService) {
private activitiProcess: ProcessService) {
if (translate) { if (translate) {
translate.addTranslationFolder('ng2-activiti-processlist', 'assets/ng2-activiti-processlist'); translate.addTranslationFolder('ng2-activiti-processlist', 'assets/ng2-activiti-processlist');
} }
this.comment$ = new Observable<Comment>(observer => this.commentObserver = observer).share();
this.comment$.subscribe((comment: Comment) => {
this.comments.push(comment);
});
} }
ngOnChanges(changes: SimpleChanges) { onError(error: any) {
let processInstanceId = changes['processInstanceId']; this.error.emit(error);
if (processInstanceId) {
if (processInstanceId.currentValue) {
this.getProcessComments(processInstanceId.currentValue);
} else {
this.resetComments();
}
}
}
private getProcessComments(processInstanceId: string) {
this.comments = [];
if (processInstanceId) {
this.activitiProcess.getProcessInstanceComments(processInstanceId).subscribe(
(res: Comment[]) => {
res.forEach((comment) => {
this.commentObserver.next(comment);
});
},
(err) => {
this.error.emit(err);
}
);
} else {
this.resetComments();
}
}
private resetComments() {
this.comments = [];
}
public showDialog() {
if (!this.dialog.nativeElement.showModal) {
dialogPolyfill.registerDialog(this.dialog.nativeElement);
}
if (this.dialog) {
this.dialog.nativeElement.showModal();
}
}
public add() {
this.activitiProcess.addProcessInstanceComment(this.processInstanceId, this.message).subscribe(
(res: Comment) => {
this.comments.push(res);
this.message = '';
},
(err) => {
this.error.emit(err);
}
);
this.cancel();
}
public cancel() {
if (this.dialog) {
this.dialog.nativeElement.close();
}
} }
} }

View File

@@ -22,7 +22,6 @@ import { Observable } from 'rxjs/Rx';
import { ProcessInstance } from '../models/process-instance.model'; import { ProcessInstance } from '../models/process-instance.model';
import { exampleProcess } from './../assets/process.model.mock'; import { exampleProcess } from './../assets/process.model.mock';
import { ProcessService } from './../services/process.service'; import { ProcessService } from './../services/process.service';
import { ProcessCommentsComponent } from './process-comments.component';
import { ProcessInstanceHeaderComponent } from './process-instance-header.component'; import { ProcessInstanceHeaderComponent } from './process-instance-header.component';
describe('ProcessInstanceHeaderComponent', () => { describe('ProcessInstanceHeaderComponent', () => {
@@ -38,8 +37,7 @@ describe('ProcessInstanceHeaderComponent', () => {
CoreModule.forRoot() CoreModule.forRoot()
], ],
declarations: [ declarations: [
ProcessInstanceHeaderComponent, ProcessInstanceHeaderComponent
ProcessCommentsComponent
], ],
providers: [ providers: [
ProcessService, ProcessService,

View File

@@ -430,13 +430,13 @@ describe('ProcessService', () => {
}); });
it('should return the correct number of comments', async(() => { it('should return the correct number of comments', async(() => {
service.getProcessInstanceComments(processId).subscribe((tasks) => { service.getComments(processId).subscribe((tasks) => {
expect(tasks.length).toBe(2); expect(tasks.length).toBe(2);
}); });
})); }));
it('should return the correct comment data', async(() => { it('should return the correct comment data', async(() => {
service.getProcessInstanceComments(processId).subscribe((comments) => { service.getComments(processId).subscribe((comments) => {
let comment = comments[0]; let comment = comments[0];
expect(comment.id).toBe(fakeComment.id); expect(comment.id).toBe(fakeComment.id);
expect(comment.created).toBe(fakeComment.created); expect(comment.created).toBe(fakeComment.created);
@@ -446,13 +446,13 @@ describe('ProcessService', () => {
})); }));
it('should call service to fetch process instance comments', () => { it('should call service to fetch process instance comments', () => {
service.getProcessInstanceComments(processId); service.getComments(processId);
expect(getProcessInstanceComments).toHaveBeenCalledWith(processId); expect(getProcessInstanceComments).toHaveBeenCalledWith(processId);
}); });
it('should pass on any error that is returned by the API', async(() => { it('should pass on any error that is returned by the API', async(() => {
getProcessInstanceComments = getProcessInstanceComments.and.returnValue(Promise.reject(fakeError)); getProcessInstanceComments = getProcessInstanceComments.and.returnValue(Promise.reject(fakeError));
service.getProcessInstanceComments(processId).subscribe( service.getComments(processId).subscribe(
() => {}, () => {},
(res) => { (res) => {
expect(res).toBe(fakeError); expect(res).toBe(fakeError);
@@ -462,7 +462,7 @@ describe('ProcessService', () => {
it('should return a default error if no data is returned by the API', async(() => { it('should return a default error if no data is returned by the API', async(() => {
getProcessInstanceComments = getProcessInstanceComments.and.returnValue(Promise.reject(null)); getProcessInstanceComments = getProcessInstanceComments.and.returnValue(Promise.reject(null));
service.getProcessInstanceComments(processId).subscribe( service.getComments(processId).subscribe(
() => {}, () => {},
(res) => { (res) => {
expect(res).toBe('Server error'); expect(res).toBe('Server error');
@@ -484,14 +484,14 @@ describe('ProcessService', () => {
}); });
it('should call service to add comment', () => { it('should call service to add comment', () => {
service.addProcessInstanceComment(processId, message); service.addComment(processId, message);
expect(addProcessInstanceComment).toHaveBeenCalledWith({ expect(addProcessInstanceComment).toHaveBeenCalledWith({
message: message message: message
}, processId); }, processId);
}); });
it('should return the created comment', async(() => { it('should return the created comment', async(() => {
service.addProcessInstanceComment(processId, message).subscribe((comment) => { service.addComment(processId, message).subscribe((comment) => {
expect(comment.id).toBe(fakeComment.id); expect(comment.id).toBe(fakeComment.id);
expect(comment.created).toBe(fakeComment.created); expect(comment.created).toBe(fakeComment.created);
expect(comment.message).toBe(fakeComment.message); expect(comment.message).toBe(fakeComment.message);
@@ -501,7 +501,7 @@ describe('ProcessService', () => {
it('should pass on any error that is returned by the API', async(() => { it('should pass on any error that is returned by the API', async(() => {
addProcessInstanceComment = addProcessInstanceComment.and.returnValue(Promise.reject(fakeError)); addProcessInstanceComment = addProcessInstanceComment.and.returnValue(Promise.reject(fakeError));
service.addProcessInstanceComment(processId, message).subscribe( service.addComment(processId, message).subscribe(
() => {}, () => {},
(res) => { (res) => {
expect(res).toBe(fakeError); expect(res).toBe(fakeError);
@@ -511,7 +511,7 @@ describe('ProcessService', () => {
it('should return a default error if no data is returned by the API', async(() => { it('should return a default error if no data is returned by the API', async(() => {
addProcessInstanceComment = addProcessInstanceComment.and.returnValue(Promise.reject(null)); addProcessInstanceComment = addProcessInstanceComment.and.returnValue(Promise.reject(null));
service.addProcessInstanceComment(processId, message).subscribe( service.addComment(processId, message).subscribe(
() => {}, () => {},
(res) => { (res) => {
expect(res).toBe('Server error'); expect(res).toBe('Server error');
@@ -638,7 +638,7 @@ describe('ProcessService', () => {
})); }));
it('should return the default filters', (done) => { it('should return the default filters', (done) => {
service.createDefaultFilters('1234').subscribe( service.createDefaultProcessFilters('1234').subscribe(
(res: FilterProcessRepresentationModel []) => { (res: FilterProcessRepresentationModel []) => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res.length).toEqual(3); expect(res.length).toEqual(3);
@@ -668,12 +668,12 @@ describe('ProcessService', () => {
let filter = fakeFilters.data[0]; let filter = fakeFilters.data[0];
it('should call the API to create the filter', () => { it('should call the API to create the filter', () => {
service.addFilter(filter); service.addProcessFilter(filter);
expect(createFilter).toHaveBeenCalledWith(filter); expect(createFilter).toHaveBeenCalledWith(filter);
}); });
it('should return the created filter', async(() => { it('should return the created filter', async(() => {
service.addFilter(filter).subscribe((createdFilter: FilterProcessRepresentationModel) => { service.addProcessFilter(filter).subscribe((createdFilter: FilterProcessRepresentationModel) => {
expect(createdFilter).toBe(filter); expect(createdFilter).toBe(filter);
}); });
})); }));
@@ -681,7 +681,7 @@ describe('ProcessService', () => {
it('should pass on any error that is returned by the API', async(() => { it('should pass on any error that is returned by the API', async(() => {
createFilter = createFilter.and.returnValue(Promise.reject(fakeError)); createFilter = createFilter.and.returnValue(Promise.reject(fakeError));
service.addFilter(filter).subscribe( service.addProcessFilter(filter).subscribe(
() => {}, () => {},
(res) => { (res) => {
expect(res).toBe(fakeError); expect(res).toBe(fakeError);
@@ -691,7 +691,7 @@ describe('ProcessService', () => {
it('should return a default error if no data is returned by the API', async(() => { it('should return a default error if no data is returned by the API', async(() => {
createFilter = createFilter.and.returnValue(Promise.reject(null)); createFilter = createFilter.and.returnValue(Promise.reject(null));
service.addFilter(filter).subscribe( service.addProcessFilter(filter).subscribe(
() => {}, () => {},
(res) => { (res) => {
expect(res).toBe('Server error'); expect(res).toBe('Server error');

View File

@@ -17,7 +17,11 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { RestVariable } from 'alfresco-js-api'; import { RestVariable } from 'alfresco-js-api';
import { AppDefinitionRepresentationModel, Comment, TaskDetailsModel, User } from 'ng2-activiti-tasklist'; import {
Comment,
TaskDetailsModel,
TaskListService,
User } from 'ng2-activiti-tasklist';
import { AlfrescoApiService, LogService } from 'ng2-alfresco-core'; import { AlfrescoApiService, LogService } from 'ng2-alfresco-core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { FilterProcessRepresentationModel } from '../models/filter-process.model'; import { FilterProcessRepresentationModel } from '../models/filter-process.model';
@@ -29,44 +33,22 @@ import { ProcessInstanceVariable } from './../models/process-instance-variable.m
declare let moment: any; declare let moment: any;
@Injectable() @Injectable()
export class ProcessService { export class ProcessService extends TaskListService {
constructor(private apiService: AlfrescoApiService, constructor(private alfrescoApiService: AlfrescoApiService,
private logService: LogService) { private processLogService: LogService) {
} super(alfrescoApiService, processLogService);
/**
* Retrieve all deployed apps
* @returns {Observable<any>}
*/
getDeployedApplications(name: string): Observable<AppDefinitionRepresentationModel> {
return Observable.fromPromise(this.apiService.getInstance().activiti.appsApi.getAppDefinitions())
.map((response: any) => response.data.find((app: AppDefinitionRepresentationModel) => app.name === name))
.catch(err => this.handleError(err));
}
/**
* Retrieve deployed apps details by id
* @param appId - number - optional - The id of app
* @returns {Observable<any>}
*/
getApplicationDetailsById(appId: number): Observable<any> {
return Observable.fromPromise(this.apiService.getInstance().activiti.appsApi.getAppDefinitions())
.map((response: any) => {
return response.data.find(app => app.id === appId);
})
.catch(err => this.handleError(err));
} }
getProcessInstances(requestNode: ProcessFilterRequestRepresentation): Observable<ProcessInstance[]> { getProcessInstances(requestNode: ProcessFilterRequestRepresentation): Observable<ProcessInstance[]> {
return Observable.fromPromise(this.apiService.getInstance().activiti.processApi.getProcessInstances(requestNode)) return Observable.fromPromise(this.alfrescoApiService.getInstance().activiti.processApi.getProcessInstances(requestNode))
.map((res: any) => { .map((res: any) => {
if (requestNode.processDefinitionKey) { if (requestNode.processDefinitionKey) {
return res.data.filter(process => process.processDefinitionKey === requestNode.processDefinitionKey); return res.data.filter(process => process.processDefinitionKey === requestNode.processDefinitionKey);
} else { } else {
return res.data; return res.data;
} }
}).catch(err => this.handleError(err)); }).catch(err => this.handleProcessError(err));
} }
getProcessFilters(appId: string): Observable<FilterProcessRepresentationModel[]> { getProcessFilters(appId: string): Observable<FilterProcessRepresentationModel[]> {
@@ -79,7 +61,7 @@ export class ProcessService {
}); });
return filters; return filters;
}) })
.catch(err => this.handleError(err)); .catch(err => this.handleProcessError(err));
} }
/** /**
@@ -92,7 +74,7 @@ export class ProcessService {
return Observable.fromPromise(this.callApiProcessFilters(appId)) return Observable.fromPromise(this.callApiProcessFilters(appId))
.map((response: any) => { .map((response: any) => {
return response.data.find(filter => filter.id === filterId); return response.data.find(filter => filter.id === filterId);
}).catch(err => this.handleError(err)); }).catch(err => this.handleProcessError(err));
} }
/** /**
@@ -105,7 +87,7 @@ export class ProcessService {
return Observable.fromPromise(this.callApiProcessFilters(appId)) return Observable.fromPromise(this.callApiProcessFilters(appId))
.map((response: any) => { .map((response: any) => {
return response.data.find(filter => filter.name === filterName); return response.data.find(filter => filter.name === filterName);
}).catch(err => this.handleError(err)); }).catch(err => this.handleProcessError(err));
} }
/** /**
@@ -113,15 +95,15 @@ export class ProcessService {
* @param appId * @param appId
* @returns {FilterProcessRepresentationModel[]} * @returns {FilterProcessRepresentationModel[]}
*/ */
public createDefaultFilters(appId: string): Observable<FilterProcessRepresentationModel[]> { public createDefaultProcessFilters(appId: string): Observable<FilterProcessRepresentationModel[]> {
let runnintFilter = this.getRunningFilterInstance(appId); let runnintFilter = this.getRunningFilterInstance(appId);
let runnintObservable = this.addFilter(runnintFilter); let runnintObservable = this.addProcessFilter(runnintFilter);
let completedFilter = this.getCompletedFilterInstance(appId); let completedFilter = this.getCompletedFilterInstance(appId);
let completedObservable = this.addFilter(completedFilter); let completedObservable = this.addProcessFilter(completedFilter);
let allFilter = this.getAllFilterInstance(appId); let allFilter = this.getAllFilterInstance(appId);
let allObservable = this.addFilter(allFilter); let allObservable = this.addProcessFilter(allFilter);
return Observable.create(observer => { return Observable.create(observer => {
Observable.forkJoin( Observable.forkJoin(
@@ -144,7 +126,7 @@ export class ProcessService {
observer.complete(); observer.complete();
}, },
(err: any) => { (err: any) => {
this.logService.error(err); this.processLogService.error(err);
}); });
}); });
} }
@@ -194,17 +176,17 @@ export class ProcessService {
* @param filter - FilterProcessRepresentationModel * @param filter - FilterProcessRepresentationModel
* @returns {FilterProcessRepresentationModel} * @returns {FilterProcessRepresentationModel}
*/ */
addFilter(filter: FilterProcessRepresentationModel): Observable<FilterProcessRepresentationModel> { addProcessFilter(filter: FilterProcessRepresentationModel): Observable<FilterProcessRepresentationModel> {
return Observable.fromPromise(this.callApiAddFilter(filter)) return Observable.fromPromise(this.callApiAddProccessFilter(filter))
.map(res => res) .map(res => res)
.map((response: FilterProcessRepresentationModel) => { .map((response: FilterProcessRepresentationModel) => {
return response; return response;
}).catch(err => this.handleError(err)); }).catch(err => this.handleProcessError(err));
} }
getProcess(id: string): Observable<ProcessInstance> { getProcess(id: string): Observable<ProcessInstance> {
return Observable.fromPromise(this.apiService.getInstance().activiti.processApi.getProcessInstance(id)) return Observable.fromPromise(this.alfrescoApiService.getInstance().activiti.processApi.getProcessInstance(id))
.catch(err => this.handleError(err)); .catch(err => this.handleProcessError(err));
} }
getProcessTasks(id: string, state?: string): Observable<TaskDetailsModel[]> { getProcessTasks(id: string, state?: string): Observable<TaskDetailsModel[]> {
@@ -214,13 +196,13 @@ export class ProcessService {
} : { } : {
processInstanceId: id processInstanceId: id
}; };
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.listTasks(taskOpts)) return Observable.fromPromise(this.alfrescoApiService.getInstance().activiti.taskApi.listTasks(taskOpts))
.map(this.extractData) .map(this.extractData)
.map(tasks => tasks.map((task: any) => { .map(tasks => tasks.map((task: any) => {
task.created = moment(task.created, 'YYYY-MM-DD').format(); task.created = moment(task.created, 'YYYY-MM-DD').format();
return task; return task;
})) }))
.catch(err => this.handleError(err)); .catch(err => this.handleProcessError(err));
} }
/** /**
@@ -228,8 +210,8 @@ export class ProcessService {
* @param id - process instance ID * @param id - process instance ID
* @returns {<Comment[]>} * @returns {<Comment[]>}
*/ */
getProcessInstanceComments(id: string): Observable<Comment[]> { getComments(id: string): Observable<Comment[]> {
return Observable.fromPromise(this.apiService.getInstance().activiti.commentsApi.getProcessInstanceComments(id)) return Observable.fromPromise(this.alfrescoApiService.getInstance().activiti.commentsApi.getProcessInstanceComments(id))
.map(res => res) .map(res => res)
.map((response: any) => { .map((response: any) => {
let comments: Comment[] = []; let comments: Comment[] = [];
@@ -243,7 +225,7 @@ export class ProcessService {
comments.push(new Comment(comment.id, comment.message, comment.created, user)); comments.push(new Comment(comment.id, comment.message, comment.created, user));
}); });
return comments; return comments;
}).catch(err => this.handleError(err)); }).catch(err => this.handleProcessError(err));
} }
/** /**
@@ -252,13 +234,13 @@ export class ProcessService {
* @param message - content of the comment * @param message - content of the comment
* @returns {Comment} * @returns {Comment}
*/ */
addProcessInstanceComment(id: string, message: string): Observable<Comment> { addComment(id: string, message: string): Observable<Comment> {
return Observable.fromPromise( return Observable.fromPromise(
this.apiService.getInstance().activiti.commentsApi.addProcessInstanceComment({ message: message }, id) this.alfrescoApiService.getInstance().activiti.commentsApi.addProcessInstanceComment({ message: message }, id)
) )
.map((response: Comment) => { .map((response: Comment) => {
return new Comment(response.id, response.message, response.created, response.createdBy); return new Comment(response.id, response.message, response.created, response.createdBy);
}).catch(err => this.handleError(err)); }).catch(err => this.handleProcessError(err));
} }
@@ -270,11 +252,11 @@ export class ProcessService {
latest: true latest: true
}; };
return Observable.fromPromise( return Observable.fromPromise(
this.apiService.getInstance().activiti.processApi.getProcessDefinitions(opts) this.alfrescoApiService.getInstance().activiti.processApi.getProcessDefinitions(opts)
) )
.map(this.extractData) .map(this.extractData)
.map(processDefs => processDefs.map((pd) => new ProcessDefinitionRepresentation(pd))) .map(processDefs => processDefs.map((pd) => new ProcessDefinitionRepresentation(pd)))
.catch(err => this.handleError(err)); .catch(err => this.handleProcessError(err));
} }
startProcess(processDefinitionId: string, name: string, outcome?: string, startFormValues?: any, variables?: RestVariable[]): Observable<ProcessInstance> { startProcess(processDefinitionId: string, name: string, outcome?: string, startFormValues?: any, variables?: RestVariable[]): Observable<ProcessInstance> {
@@ -292,50 +274,50 @@ export class ProcessService {
startRequest.variables = variables; startRequest.variables = variables;
} }
return Observable.fromPromise( return Observable.fromPromise(
this.apiService.getInstance().activiti.processApi.startNewProcessInstance(startRequest) this.alfrescoApiService.getInstance().activiti.processApi.startNewProcessInstance(startRequest)
) )
.map((pd) => new ProcessInstance(pd)) .map((pd) => new ProcessInstance(pd))
.catch(err => this.handleError(err)); .catch(err => this.handleProcessError(err));
} }
cancelProcess(processInstanceId: string): Observable<void> { cancelProcess(processInstanceId: string): Observable<void> {
return Observable.fromPromise( return Observable.fromPromise(
this.apiService.getInstance().activiti.processApi.deleteProcessInstance(processInstanceId) this.alfrescoApiService.getInstance().activiti.processApi.deleteProcessInstance(processInstanceId)
) )
.catch(err => this.handleError(err)); .catch(err => this.handleProcessError(err));
} }
getProcessInstanceVariables(processDefinitionId: string): Observable<ProcessInstanceVariable[]> { getProcessInstanceVariables(processDefinitionId: string): Observable<ProcessInstanceVariable[]> {
return Observable.fromPromise( return Observable.fromPromise(
this.apiService.getInstance().activiti.processInstanceVariablesApi.getProcessInstanceVariables(processDefinitionId) this.alfrescoApiService.getInstance().activiti.processInstanceVariablesApi.getProcessInstanceVariables(processDefinitionId)
) )
.map((processVars: any[]) => processVars.map((pd) => new ProcessInstanceVariable(pd))) .map((processVars: any[]) => processVars.map((pd) => new ProcessInstanceVariable(pd)))
.catch(err => this.handleError(err)); .catch(err => this.handleProcessError(err));
} }
createOrUpdateProcessInstanceVariables(processDefinitionId: string, variables: ProcessInstanceVariable[]): Observable<ProcessInstanceVariable[]> { createOrUpdateProcessInstanceVariables(processDefinitionId: string, variables: ProcessInstanceVariable[]): Observable<ProcessInstanceVariable[]> {
return Observable.fromPromise( return Observable.fromPromise(
this.apiService.getInstance().activiti.processInstanceVariablesApi.createOrUpdateProcessInstanceVariables(processDefinitionId, variables) this.alfrescoApiService.getInstance().activiti.processInstanceVariablesApi.createOrUpdateProcessInstanceVariables(processDefinitionId, variables)
) )
.catch(err => this.handleError(err)); .catch(err => this.handleProcessError(err));
} }
deleteProcessInstanceVariable(processDefinitionId: string, variableName: string): Observable<void> { deleteProcessInstanceVariable(processDefinitionId: string, variableName: string): Observable<void> {
return Observable.fromPromise( return Observable.fromPromise(
this.apiService.getInstance().activiti.processInstanceVariablesApi.deleteProcessInstanceVariable(processDefinitionId, variableName) this.alfrescoApiService.getInstance().activiti.processInstanceVariablesApi.deleteProcessInstanceVariable(processDefinitionId, variableName)
) )
.catch(err => this.handleError(err)); .catch(err => this.handleProcessError(err));
} }
private callApiAddFilter(filter: FilterProcessRepresentationModel) { private callApiAddProccessFilter(filter: FilterProcessRepresentationModel) {
return this.apiService.getInstance().activiti.userFiltersApi.createUserProcessInstanceFilter(filter); return this.alfrescoApiService.getInstance().activiti.userFiltersApi.createUserProcessInstanceFilter(filter);
} }
callApiProcessFilters(appId?: string) { callApiProcessFilters(appId?: string) {
if (appId) { if (appId) {
return this.apiService.getInstance().activiti.userFiltersApi.getUserProcessInstanceFilters({ appId: appId }); return this.alfrescoApiService.getInstance().activiti.userFiltersApi.getUserProcessInstanceFilters({ appId: appId });
} else { } else {
return this.apiService.getInstance().activiti.userFiltersApi.getUserProcessInstanceFilters(); return this.alfrescoApiService.getInstance().activiti.userFiltersApi.getUserProcessInstanceFilters();
} }
} }
@@ -343,7 +325,7 @@ export class ProcessService {
return res.data || {}; return res.data || {};
} }
private handleError(error: any) { private handleProcessError(error: any) {
return Observable.throw(error || 'Server error'); return Observable.throw(error || 'Server error');
} }
} }

View File

@@ -22,4 +22,11 @@
.adf-full-width { .adf-full-width {
width: 100%; width: 100%;
}
adf-comment-list {
float: left;
overflow: auto;
height: calc(100% - 101px);
width: 100%;
} }

View File

@@ -66,12 +66,12 @@ describe('CommentsComponent', () => {
component = fixture.componentInstance; component = fixture.componentInstance;
service = fixture.debugElement.injector.get(TaskListService); service = fixture.debugElement.injector.get(TaskListService);
getCommentsSpy = spyOn(service, 'getTaskComments').and.returnValue(Observable.of([ getCommentsSpy = spyOn(service, 'getComments').and.returnValue(Observable.of([
{ message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} }, { message: 'Test1', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} },
{ message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} }, { message: 'Test2', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} },
{ message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} } { message: 'Test3', created: Date.now(), createdBy: {firstName: 'Admin', lastName: 'User'} }
])); ]));
addCommentSpy = spyOn(service, 'addTaskComment').and.returnValue(Observable.of({id: 123, message: 'Test Comment'})); addCommentSpy = spyOn(service, 'addComment').and.returnValue(Observable.of({id: 123, message: 'Test Comment'}));
componentHandler = jasmine.createSpyObj('componentHandler', [ componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered', 'upgradeAllRegistered',

View File

@@ -25,8 +25,7 @@ import { TaskListService } from '../services/tasklist.service';
@Component({ @Component({
selector: 'adf-comments, activiti-comments', selector: 'adf-comments, activiti-comments',
templateUrl: './comments.component.html', templateUrl: './comments.component.html',
styleUrls: ['./comments.component.css'], styleUrls: ['./comments.component.css']
providers: [TaskListService]
}) })
export class CommentsComponent implements OnChanges { export class CommentsComponent implements OnChanges {
@@ -80,7 +79,7 @@ export class CommentsComponent implements OnChanges {
private getTaskComments(taskId: string): void { private getTaskComments(taskId: string): void {
this.resetComments(); this.resetComments();
if (taskId) { if (taskId) {
this.activitiTaskList.getTaskComments(taskId).subscribe( this.activitiTaskList.getComments(taskId).subscribe(
(res: Comment[]) => { (res: Comment[]) => {
res = res.sort((comment1: Comment, comment2: Comment) => { res = res.sort((comment1: Comment, comment2: Comment) => {
let date1 = new Date(comment1.created); let date1 = new Date(comment1.created);
@@ -106,7 +105,7 @@ export class CommentsComponent implements OnChanges {
add(): void { add(): void {
if (this.message && this.message.trim() && !this.beingAdded) { if (this.message && this.message.trim() && !this.beingAdded) {
this.beingAdded = true; this.beingAdded = true;
this.activitiTaskList.addTaskComment(this.taskId, this.message).subscribe( this.activitiTaskList.addComment(this.taskId, this.message).subscribe(
(res: Comment) => { (res: Comment) => {
this.comments.unshift(res); this.comments.unshift(res);
this.message = ''; this.message = '';

View File

@@ -73,7 +73,7 @@ describe('TaskDetailsComponent', () => {
getFormSpy = spyOn(formService, 'getTaskForm').and.returnValue(Observable.of(taskFormMock)); getFormSpy = spyOn(formService, 'getTaskForm').and.returnValue(Observable.of(taskFormMock));
getTasksSpy = spyOn(service, 'getTasks').and.returnValue(Observable.of(tasksMock)); getTasksSpy = spyOn(service, 'getTasks').and.returnValue(Observable.of(tasksMock));
completeTaskSpy = spyOn(service, 'completeTask').and.returnValue(Observable.of({})); completeTaskSpy = spyOn(service, 'completeTask').and.returnValue(Observable.of({}));
spyOn(service, 'getTaskComments').and.returnValue(Observable.of(noDataMock)); spyOn(service, 'getComments').and.returnValue(Observable.of(noDataMock));
spyOn(service, 'getTaskChecklist').and.returnValue(Observable.of(noDataMock)); spyOn(service, 'getTaskChecklist').and.returnValue(Observable.of(noDataMock));
componentHandler = jasmine.createSpyObj('componentHandler', [ componentHandler = jasmine.createSpyObj('componentHandler', [

View File

@@ -161,7 +161,7 @@ describe('Activiti TaskList Service', () => {
}); });
it('should return the task list filtered', (done) => { it('should return the task list filtered', (done) => {
service.getTasks(<TaskQueryRequestRepresentationModel>fakeFilter).subscribe( service.getTasks(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
res => { res => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res.length).toEqual(1); expect(res.length).toEqual(1);
@@ -181,7 +181,7 @@ describe('Activiti TaskList Service', () => {
}); });
it('should return the task list filtered by processDefinitionKey', (done) => { it('should return the task list filtered by processDefinitionKey', (done) => {
service.getTasks(<TaskQueryRequestRepresentationModel>fakeFilterWithProcessDefinitionKey).subscribe( service.getTasks(<TaskQueryRequestRepresentationModel> fakeFilterWithProcessDefinitionKey).subscribe(
res => { res => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res.length).toEqual(1); expect(res.length).toEqual(1);
@@ -202,7 +202,7 @@ describe('Activiti TaskList Service', () => {
}); });
it('should throw an exception when the response is wrong', () => { it('should throw an exception when the response is wrong', () => {
service.getTasks(<TaskQueryRequestRepresentationModel>fakeFilter).subscribe( service.getTasks(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
(res) => { (res) => {
}, },
(err: any) => { (err: any) => {
@@ -240,7 +240,7 @@ describe('Activiti TaskList Service', () => {
}); });
it('should return the tasks comments ', (done) => { it('should return the tasks comments ', (done) => {
service.getTaskComments('999').subscribe( service.getComments('999').subscribe(
(res: Comment[]) => { (res: Comment[]) => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res.length).toEqual(2); expect(res.length).toEqual(2);
@@ -327,7 +327,7 @@ describe('Activiti TaskList Service', () => {
}); });
it('should add a comment task ', (done) => { it('should add a comment task ', (done) => {
service.addTaskComment('999', 'fake-comment-message').subscribe( service.addComment('999', 'fake-comment-message').subscribe(
(res: Comment) => { (res: Comment) => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res.id).not.toEqual(''); expect(res.id).not.toEqual('');
@@ -367,7 +367,7 @@ describe('Activiti TaskList Service', () => {
}); });
it('should return the total number of tasks', (done) => { it('should return the total number of tasks', (done) => {
service.getTotalTasks(<TaskQueryRequestRepresentationModel>fakeFilter).subscribe( service.getTotalTasks(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
res => { res => {
expect(res).toBeDefined(); expect(res).toBeDefined();
expect(res.size).toEqual(1); expect(res.size).toEqual(1);

View File

@@ -178,7 +178,7 @@ export class TaskListService {
* @param id - taskId * @param id - taskId
* @returns {<Comment[]>} * @returns {<Comment[]>}
*/ */
getTaskComments(id: string): Observable<Comment[]> { getComments(id: string): Observable<Comment[]> {
return Observable.fromPromise(this.callApiTaskComments(id)) return Observable.fromPromise(this.callApiTaskComments(id))
.map(res => res) .map(res => res)
.map((response: any) => { .map((response: any) => {
@@ -321,7 +321,7 @@ export class TaskListService {
* @param message - content of the comment * @param message - content of the comment
* @returns {Comment} * @returns {Comment}
*/ */
addTaskComment(id: string, message: string): Observable<Comment> { addComment(id: string, message: string): Observable<Comment> {
return Observable.fromPromise(this.callApiAddTaskComment(id, message)) return Observable.fromPromise(this.callApiAddTaskComment(id, message))
.map(res => res) .map(res => res)
.map((response: Comment) => { .map((response: Comment) => {