diff --git a/lib/process-services/src/lib/mock/task/task-details.mock.ts b/lib/process-services/src/lib/mock/task/task-details.mock.ts
index bf04da9c1f..55e42d040d 100644
--- a/lib/process-services/src/lib/mock/task/task-details.mock.ts
+++ b/lib/process-services/src/lib/mock/task/task-details.mock.ts
@@ -206,6 +206,41 @@ export let initiatorCanCompleteTaskDetailsMock = new TaskDetailsModel({
taskDefinitionKey: 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE',
executionId: '86',
involvedGroups: [],
+ involvedPeople: [],
+ memberOfCandidateUsers: false,
+ managerOfCandidateGroup: false,
+ memberOfCandidateGroup: false
+});
+
+export let initiatorWithCandidatesTaskDetailsMock = new TaskDetailsModel({
+ id: '91',
+ name: 'Request translation',
+ description: null,
+ category: null,
+ assignee: null,
+ created: '2016-11-03T15:25:42.749+0000',
+ dueDate: null,
+ endDate: null,
+ duration: null,
+ priority: 50,
+ parentTaskId: null,
+ parentTaskName: null,
+ processInstanceId: '86',
+ processInstanceName: null,
+ processDefinitionId: 'TranslationProcess:2:8',
+ processDefinitionName: 'Translation Process',
+ processDefinitionDescription: null,
+ processDefinitionKey: 'TranslationProcess',
+ processDefinitionCategory: 'http://www.activiti.org/processdef',
+ processDefinitionVersion: 2,
+ processDefinitionDeploymentId: '5',
+ formKey: '4',
+ processInstanceStartUserId: '1001',
+ initiatorCanCompleteTask: true,
+ adhocTaskCanBeReassigned: false,
+ taskDefinitionKey: 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE',
+ executionId: '86',
+ involvedGroups: [],
involvedPeople: [
{
id: 1001,
@@ -220,9 +255,9 @@ export let initiatorCanCompleteTaskDetailsMock = new TaskDetailsModel({
email: 'fake@app.activiti.com'
}
],
- memberOfCandidateUsers: false,
- managerOfCandidateGroup: false,
- memberOfCandidateGroup: false
+ memberOfCandidateUsers: true,
+ managerOfCandidateGroup: true,
+ memberOfCandidateGroup: true
});
export let taskDetailsWithOutAssigneeMock = new TaskDetailsModel({
@@ -316,10 +351,13 @@ export let claimedTaskDetailsMock = new TaskDetailsModel({
endDate: null,
duration: null,
priority: 50,
+ formKey: '4',
parentTaskId: null,
parentTaskName: null,
processInstanceId: '86',
processInstanceName: null,
+ processInstanceStartUserId: '1002',
+ initiatorCanCompleteTask: false,
processDefinitionId: 'TranslationProcess:2:8',
processDefinitionName: 'Translation Process',
involvedGroups: [
@@ -507,7 +545,7 @@ export let taskDetailsWithOutFormMock = new TaskDetailsModel({
'name': 'Request translation',
'description': 'fake description',
'category': null,
- 'assignee': {'id': 1001, 'firstName': 'Admin', 'lastName': 'Paul', 'email': 'my@mymail.com' },
+ 'assignee': {'id': 1001, 'firstName': 'Admin', 'lastName': 'Paul', 'email': 'fake-email@gmail.com' },
'created': '2016-11-03T15:25:42.749+0000',
'dueDate': '2016-11-03T15:25:42.749+0000',
'endDate': null,
diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html
index 3ba824ba04..85d96084d9 100644
--- a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html
+++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html
@@ -5,7 +5,6 @@
[showValidationIcon]="showFormValidationIcon"
[showRefreshButton]="showFormRefreshButton"
[showCompleteButton]="showFormCompleteButton"
- [disableCompleteButton]="!isCompleteButtonEnabled()"
[showSaveButton]="isSaveButtonVisible()"
[readOnly]="isReadOnlyForm()"
[fieldValidators]="fieldValidators"
@@ -61,10 +60,11 @@
-
-
@@ -73,18 +73,25 @@
+
+ {{'ADF_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate}}
+
+ (success)="onClaimTask($event)"
+ (error)="onClaimTaskError($event)">
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.CLAIM' | translate }}
+ (success)="onUnclaimTask($event)"
+ (error)="onUnclaimTaskError($event)">
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}
diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts
index a14c149544..bfae1e5c4b 100644
--- a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts
+++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts
@@ -40,7 +40,8 @@ import {
initiatorCanCompleteTaskDetailsMock,
taskDetailsWithOutCandidateGroup,
claimedTaskDetailsMock,
- claimedByGroupMemberMock
+ claimedByGroupMemberMock,
+ initiatorWithCandidatesTaskDetailsMock
} from '../../../mock/task/task-details.mock';
import { TaskDetailsModel } from '../../models/task-details.model';
import { ProcessTestingModule } from '../../../testing/process.testing.module';
@@ -80,7 +81,7 @@ describe('TaskFormComponent', () => {
taskDetailsMock.processDefinitionId = null;
spyOn(formService, 'getTask').and.returnValue(of(taskDetailsMock));
authService = TestBed.get(AuthenticationService);
- getBpmLoggedUserSpy = spyOn(authService, 'getBpmLoggedUser').and.returnValue(of({ email: 'fake-email' }));
+ getBpmLoggedUserSpy = spyOn(authService, 'getBpmLoggedUser').and.returnValue(of({ id: 1001, email: 'fake-email@gmail.com' }));
});
afterEach(async() => {
@@ -129,22 +130,6 @@ describe('TaskFormComponent', () => {
expect(formCompletedSpy).toHaveBeenCalled();
});
- it('Should be able to complete the task as a process initiator', async () => {
- const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
- const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({}));
- getTaskDetailsSpy.and.returnValue(of(initiatorCanCompleteTaskDetailsMock));
- component.taskId = '123';
- fixture.detectChanges();
- await fixture.whenStable();
- const activitFormSelector = element.querySelector('adf-form');
- const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete');
- expect(activitFormSelector).toBeDefined();
- expect(completeButton['disabled']).toEqual(false);
- completeButton.click();
- expect(completeTaskFormSpy).toHaveBeenCalled();
- expect(formCompletedSpy).toHaveBeenCalled();
- });
-
it('Should emit error event in case form complete service fails', async () => {
const errorSpy: jasmine.Spy = spyOn(component.error, 'emit');
const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(throwError({message: 'servce failed'}));
@@ -160,7 +145,6 @@ describe('TaskFormComponent', () => {
expect(completeTaskFormSpy).toHaveBeenCalled();
expect(errorSpy).toHaveBeenCalled();
});
-
});
describe('change detection', () => {
@@ -500,6 +484,97 @@ describe('TaskFormComponent', () => {
});
});
+ describe('Complete task', () => {
+
+ it('Should be able to complete the assigned task in case process initiator not allowed to complete the task', async () => {
+ getBpmLoggedUserSpy.and.returnValue(of({ id: 1002, firstName: 'Wilbur', lastName: 'Adams', email: 'wilbur@app.activiti.com' }));
+ getTaskDetailsSpy.and.returnValue(of(taskDetailsMock));
+
+ const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
+ const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({}));
+ component.taskId = '123';
+ component.ngOnInit();
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete');
+
+ expect(component.isProcessInitiator()).toEqual(false);
+ expect(completeButton['disabled']).toEqual(false);
+
+ completeButton.click();
+
+ expect(completeTaskFormSpy).toHaveBeenCalled();
+ expect(formCompletedSpy).toHaveBeenCalled();
+ });
+
+ it('Should be able to complete the task if process initiator allowed to complete the task', async () => {
+ getBpmLoggedUserSpy.and.returnValue(of({ id: 1001, firstName: 'Wilbur', lastName: 'Adams', email: 'wilbur@app.activiti.com' }));
+ const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
+ const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({}));
+ getTaskDetailsSpy.and.returnValue(of(initiatorCanCompleteTaskDetailsMock));
+
+ component.taskId = '123';
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const activitFormSelector = element.querySelector('adf-form');
+ const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete');
+
+ expect(activitFormSelector).toBeDefined();
+ expect(completeButton['disabled']).toEqual(false);
+ expect(component.isProcessInitiator()).toEqual(true);
+
+ completeButton.click();
+
+ expect(completeTaskFormSpy).toHaveBeenCalled();
+ expect(formCompletedSpy).toHaveBeenCalled();
+ });
+
+ it('Should not be able to complete a task with candidates users if process initiator allowed to complete the task', async () => {
+ getTaskDetailsSpy.and.returnValue(of(initiatorWithCandidatesTaskDetailsMock));
+
+ component.taskId = '123';
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(component.canInitiatorComplete()).toEqual(true);
+ expect(component.isProcessInitiator()).toEqual(true);
+ expect(component.isCandidateMember()).toEqual(true);
+
+ const activitFormSelector = element.querySelector('adf-form');
+ const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete');
+
+ expect(activitFormSelector).toBeDefined();
+ expect(completeButton['disabled']).toEqual(true);
+ });
+
+ it('Should be able to complete a task with candidates users if process initiator not allowed to complete the task', async () => {
+ const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
+ getBpmLoggedUserSpy.and.returnValue(of({ id: 1001, firstName: 'Wilbur', lastName: 'Adams', email: 'wilbur@app.activiti.com' }));
+ const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({}));
+ getTaskDetailsSpy.and.returnValue(of(claimedTaskDetailsMock));
+
+ component.taskId = '123';
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const activitFormSelector = element.querySelector('adf-form');
+ const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete');
+
+ expect(activitFormSelector).toBeDefined();
+ expect(component.canInitiatorComplete()).toEqual(false);
+ expect(component.isProcessInitiator()).toEqual(false);
+ expect(completeButton['disabled']).toEqual(false);
+
+ completeButton.click();
+
+ expect(completeTaskFormSpy).toHaveBeenCalled();
+ expect(formCompletedSpy).toHaveBeenCalled();
+ });
+ });
+
describe('Claim/Unclaim buttons', () => {
it('should display the claim button if no assignee', async() => {
@@ -589,7 +664,7 @@ describe('TaskFormComponent', () => {
component.taskId = 'mock-task-id';
- component.taskClaimed.subscribe((taskId) => {
+ component.taskClaimed.subscribe((taskId: string) => {
expect(taskId).toEqual(component.taskId);
done();
});
@@ -601,6 +676,25 @@ describe('TaskFormComponent', () => {
claimBtn.nativeElement.click();
});
+ it('should emit error event in case claim task api fails', (done) => {
+ const mockError = { message: 'Api Failed' };
+ spyOn(taskListService, 'claimTask').and.returnValue(throwError(mockError));
+ getTaskDetailsSpy.and.returnValue(of(claimableTaskDetailsMock));
+
+ component.taskId = 'mock-task-id';
+
+ component.error.subscribe((error: any) => {
+ expect(error).toEqual(mockError);
+ done();
+ });
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ const claimBtn = fixture.debugElement.query(By.css('[adf-claim-task]'));
+ claimBtn.nativeElement.click();
+ });
+
it('should emit taskUnClaimed when task is unclaimed', (done) => {
spyOn(taskListService, 'unclaimTask').and.returnValue(of({}));
getBpmLoggedUserSpy.and.returnValue(of(claimedTaskDetailsMock.assignee));
@@ -619,5 +713,25 @@ describe('TaskFormComponent', () => {
const unclaimBtn = fixture.debugElement.query(By.css('[adf-unclaim-task]'));
unclaimBtn.nativeElement.click();
});
+
+ it('should emit error event in case unclaim task api fails', (done) => {
+ const mockError = { message: 'Api Failed' };
+ spyOn(taskListService, 'unclaimTask').and.returnValue(throwError(mockError));
+ getBpmLoggedUserSpy.and.returnValue(of(claimedTaskDetailsMock.assignee));
+ getTaskDetailsSpy.and.returnValue(of(claimedTaskDetailsMock));
+
+ component.taskId = 'mock-task-id';
+
+ component.error.subscribe((error: any) => {
+ expect(error).toEqual(mockError);
+ done();
+ });
+
+ component.ngOnInit();
+ fixture.detectChanges();
+
+ const unclaimBtn = fixture.debugElement.query(By.css('[adf-unclaim-task]'));
+ unclaimBtn.nativeElement.click();
+ });
});
});
diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts
index 77dca180fc..dd477fe1a3 100644
--- a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts
+++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts
@@ -272,15 +272,26 @@ export class TaskFormComponent implements OnInit {
}
isReadOnlyForm(): boolean {
- return this.internalReadOnlyForm || !(this.isAssignedToMe() || this.canInitiatorComplete());
+ let readOnlyForm: boolean;
+ if (this.isCandidateMember()) {
+ readOnlyForm = this.internalReadOnlyForm || !this.isAssignedToMe();
+ } else {
+ readOnlyForm = this.internalReadOnlyForm || !(this.isAssignedToMe() || (this.canInitiatorComplete() && this.isProcessInitiator()));
+ }
+
+ return readOnlyForm;
+ }
+
+ isProcessInitiator(): boolean {
+ return this.currentLoggedUser && ( this.currentLoggedUser.id === +this.taskDetails.processInstanceStartUserId);
}
isSaveButtonVisible(): boolean {
return this.showFormSaveButton && (!this.canInitiatorComplete() || this.isAssignedToMe());
}
- canCompleteTask(): boolean {
- return !this.isCompletedTask() && this.isAssignedToMe();
+ canCompleteNoFormTask(): boolean {
+ return this.isReadOnlyForm();
}
getCompletedTaskTranslatedMessage(): Observable {
@@ -307,7 +318,15 @@ export class TaskFormComponent implements OnInit {
this.taskClaimed.emit(taskId);
}
+ onClaimTaskError(error: any) {
+ this.error.emit(error);
+ }
+
onUnclaimTask(taskId: string) {
this.taskUnclaimed.emit(taskId);
}
+
+ onUnclaimTaskError(error: any) {
+ this.error.emit(error);
+ }
}