diff --git a/ng2-components/ng2-activiti-tasklist/src/assets/task-details.mock.ts b/ng2-components/ng2-activiti-tasklist/src/assets/task-details.mock.ts
index 9f5bbfe99b..802b01d4f3 100644
--- a/ng2-components/ng2-activiti-tasklist/src/assets/task-details.mock.ts
+++ b/ng2-components/ng2-activiti-tasklist/src/assets/task-details.mock.ts
@@ -45,12 +45,122 @@ export let taskDetailsMock = new TaskDetailsModel({
'adhocTaskCanBeReassigned': false,
'taskDefinitionKey': 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE',
'executionId': '86',
+ 'involvedGroups': [],
'involvedPeople': [],
'memberOfCandidateUsers': false,
'managerOfCandidateGroup': false,
'memberOfCandidateGroup': false
});
+export let completedTaskDetailsMock = new TaskDetailsModel({
+ 'id': '91',
+ 'name': 'Request translation',
+ 'description': null,
+ 'category': null,
+ 'assignee': {'id': 1001, 'firstName': 'Wilbur', 'lastName': 'Adams', 'email': 'wilbur@app.activiti.com'},
+ 'created': '2016-11-03T15:25:42.749+0000',
+ 'dueDate': null,
+ 'endDate': '2016-11-03T15:25:42.749+0000',
+ 'duration': null,
+ 'priority': 50,
+ 'parentTaskId': null,
+ 'parentTaskName': null,
+ 'processInstanceId': '86',
+ 'processInstanceName': null,
+ 'processDefinitionId': 'TranslationProcess:2:8',
+ 'processDefinitionName': 'Translation Process',
+ 'involvedGroups': [],
+ 'involvedPeople': []
+});
+
+export let taskDetailsWithOutAssigneeMock = 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',
+ 'involvedGroups': [{'id': 7007, 'name': 'group1', 'externalId': null, 'status': 'active', 'groups': null},
+ {'id': 8008, 'name': 'group2', 'externalId': null, 'status': 'active', 'groups': null}],
+ 'involvedPeople': []
+});
+
+export let taskDetailsWithInvolvedGroupMock = new TaskDetailsModel({
+ 'id': '91',
+ 'name': 'Request translation',
+ 'description': null,
+ 'category': null,
+ 'assignee': {'id': 1001, 'firstName': 'Wilbur', 'lastName': 'Adams', 'email': 'wilbur@app.activiti.com'},
+ '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',
+ 'involvedGroups': [{'id': 7007, 'name': 'group1', 'externalId': null, 'status': 'active', 'groups': null},
+ {'id': 8008, 'name': 'group2', 'externalId': null, 'status': 'active', 'groups': null}],
+ 'involvedPeople': []
+});
+
+export let taskDetailsWithInvolvedPeopleMock = new TaskDetailsModel({
+ 'id': '91',
+ 'name': 'Request translation',
+ 'description': null,
+ 'category': null,
+ 'assignee': {'id': 1001, 'firstName': 'Wilbur', 'lastName': 'Adams', 'email': 'wilbur@app.activiti.com'},
+ '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',
+ 'involvedGroups': [],
+ 'involvedPeople': [{'id': 1001, 'firstName': 'Wilbur', 'lastName': 'Adams', 'email': 'wilbur@app.activiti.com'},
+ {'id': 111, 'firstName': 'fake-first-name', 'lastName': 'fake-last-name', 'email': 'fake@app.activiti.com'}]
+});
+
+export let taskDetailsWithAssigneeMock = new TaskDetailsModel({
+ 'id': '91',
+ 'name': 'Request translation',
+ 'description': null,
+ 'category': null,
+ 'assignee': {'id': 111, 'firstName': 'fake-first-name', 'lastName': 'fake-last-name', 'email': 'fake@app.activiti.com'},
+ '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',
+ 'involvedGroups': [],
+ 'involvedPeople': [{'id': 1001, 'firstName': 'Wilbur', 'lastName': 'Adams', 'email': 'wilbur@app.activiti.com'},
+ {'id': 111, 'firstName': 'fake-first-name', 'lastName': 'fake-last-name', 'email': 'fake@app.activiti.com'}]
+});
+
export let taskFormMock = new TaskDetailsModel({
'id': 4,
'name': 'Translation request',
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.html b/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.html
index 5b84d77316..f60a2e496a 100644
--- a/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.html
+++ b/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.html
@@ -4,9 +4,9 @@
-
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.spec.ts
index ccabd4fe14..bff14c1973 100644
--- a/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.spec.ts
+++ b/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.spec.ts
@@ -19,12 +19,20 @@ import { DebugElement } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { AppConfigService, CardViewUpdateService, CoreModule, TranslationService, UserProcessModel } from 'ng2-alfresco-core';
+import { BpmUserService } from 'ng2-alfresco-userinfo';
import { Observable } from 'rxjs/Rx';
import { AppConfigServiceMock } from '../assets/app-config.service.mock';
import { TranslationMock } from '../assets/translation.service.mock';
import { TaskDetailsModel } from '../models/task-details.model';
-import { taskDetailsMock } from './../assets/task-details.mock';
+import {
+ completedTaskDetailsMock,
+ taskDetailsMock,
+ taskDetailsWithAssigneeMock,
+ taskDetailsWithInvolvedGroupMock,
+ taskDetailsWithInvolvedPeopleMock,
+ taskDetailsWithOutAssigneeMock
+} from './../assets/task-details.mock';
import { TaskListService } from './../services/tasklist.service';
import { TaskHeaderComponent } from './task-header.component';
@@ -34,6 +42,22 @@ describe('TaskHeaderComponent', () => {
let component: TaskHeaderComponent;
let fixture: ComponentFixture;
let debugElement: DebugElement;
+ let userBpmService: BpmUserService;
+ let getCurrentUserInfoSpy: jasmine.Spy;
+
+ let fakeBpmAssignedUser = {
+ id: 1001,
+ apps: [],
+ capabilities: 'fake-capability',
+ company: 'fake-company',
+ created: 'fake-create-date',
+ email: 'wilbur@app.activiti.com',
+ externalId: 'fake-external-id',
+ firstName: 'Wilbur',
+ lastName: 'Adams',
+ fullname: 'Wilbur Adams',
+ groups: []
+ };
beforeEach(async(() => {
TestBed.configureTestingModule({
@@ -45,20 +69,21 @@ describe('TaskHeaderComponent', () => {
],
providers: [
TaskListService,
+ BpmUserService,
CardViewUpdateService,
{ provide: AppConfigService, useClass: AppConfigServiceMock },
{ provide: TranslationService, useClass: TranslationMock }
]
}).compileComponents();
-
}));
beforeEach(() => {
fixture = TestBed.createComponent(TaskHeaderComponent);
component = fixture.componentInstance;
service = TestBed.get(TaskListService);
+ userBpmService = TestBed.get(BpmUserService);
debugElement = fixture.debugElement;
-
+ getCurrentUserInfoSpy = spyOn(userBpmService, 'getCurrentUserInfo').and.returnValue(Observable.of(fakeBpmAssignedUser));
component.taskDetails = new TaskDetailsModel(taskDetailsMock);
});
@@ -111,7 +136,7 @@ describe('TaskHeaderComponent', () => {
describe('Claiming', () => {
it('should display the claim button if no assignee', () => {
- component.taskDetails.assignee = null;
+ component.taskDetails = new TaskDetailsModel(taskDetailsWithOutAssigneeMock);
component.ngOnChanges({});
fixture.detectChanges();
@@ -119,44 +144,89 @@ describe('TaskHeaderComponent', () => {
let claimButton = fixture.debugElement.query(By.css('[data-automation-id="header-claim-button"]'));
expect(claimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.CLAIM');
});
+
+ it('should display the claim button to the invovled group', () => {
+ component.taskDetails = new TaskDetailsModel(taskDetailsWithOutAssigneeMock);
+ component.ngOnChanges({});
+ fixture.detectChanges();
+ let claimButton = fixture.debugElement.query(By.css('[data-automation-id="header-claim-button"]'));
+ expect(claimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.CLAIM');
+ });
});
- describe('Unclaim', () => {
+ it('should display the requeue button if the current logged-in user is a part of the invovled group', () => {
+ component.taskDetails = new TaskDetailsModel(taskDetailsWithInvolvedGroupMock);
+ component.ngOnChanges({});
+ fixture.detectChanges();
+ let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
+ expect(unclaimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM');
+ });
+ it('should display the requeue button if the current logged-in user is a part of the invovled people', () => {
+ component.taskDetails = new TaskDetailsModel(taskDetailsWithInvolvedPeopleMock);
+ component.ngOnChanges({});
+ fixture.detectChanges();
+ let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
+ expect(unclaimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM');
+ });
+
+ it('should not display the claim/requeue button if the current logged-in user is not part of the group', () => {
+ component.taskDetails = new TaskDetailsModel(taskDetailsWithInvolvedGroupMock);
+ component.ngOnChanges({});
+ fixture.detectChanges();
+ let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
+ expect(unclaimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM');
+ });
+
+ it('should not display the requeue button if the task is assigned to others', () => {
const batman = new UserProcessModel({ id : 1, email: 'bruce.wayne@gotham.com', firstName: 'Bruce', lastName: 'Wayne', userImage: 'batman.jpg' });
- let taskListService;
+ component.taskDetails.assignee = batman;
+ component.ngOnChanges({});
+ fixture.detectChanges();
+ let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
+ expect(unclaimButton).toBeNull();
+ });
- beforeEach(() => {
- taskListService = TestBed.get(TaskListService);
- component.taskDetails.assignee = batman;
- component.taskDetails.id = 'repair-batmobile';
- fixture.detectChanges();
- });
+ it('should not display the requeue button if the task is assigned to others in a group', () => {
+ component.taskDetails = new TaskDetailsModel(taskDetailsWithAssigneeMock);
+ component.ngOnChanges({});
+ fixture.detectChanges();
+ let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
+ expect(unclaimButton).toBeNull();
+ });
- it('should display the requeue button if there is assignee', () => {
- let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
- expect(unclaimButton.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM');
- });
+ it('should not display the requeue button if the task is completed', () => {
+ component.taskDetails = new TaskDetailsModel(completedTaskDetailsMock);
+ component.ngOnChanges({});
+ fixture.detectChanges();
+ let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
+ expect(unclaimButton).toBeNull();
+ });
- it('should call the service\'s unclaim method on unclaiming' , () => {
- spyOn(taskListService, 'unclaimTask');
+ it('should call the service\'s unclaim method on unclaiming', () => {
+ spyOn(service, 'unclaimTask');
+ component.taskDetails = new TaskDetailsModel(taskDetailsWithInvolvedGroupMock);
+ component.ngOnChanges({});
+ fixture.detectChanges();
- let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
- unclaimButton.triggerEventHandler('click', {});
+ let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
+ unclaimButton.triggerEventHandler('click', {});
- expect(taskListService.unclaimTask).toHaveBeenCalledWith('repair-batmobile');
- });
+ expect(service.unclaimTask).toHaveBeenCalledWith('91');
+ });
- it('should trigger the unclaim event on successfull unclaiming', () => {
- let unclaimed: boolean = false;
- component.unclaim.subscribe(() => { unclaimed = true; });
- spyOn(taskListService, 'unclaimTask').and.returnValue(Observable.of(true));
+ it('should trigger the unclaim event on successfull unclaiming', () => {
+ let unclaimed: boolean = false;
+ spyOn(service, 'unclaimTask').and.returnValue(Observable.of(true));
+ component.taskDetails = new TaskDetailsModel(taskDetailsWithInvolvedGroupMock);
+ component.ngOnChanges({});
+ fixture.detectChanges();
+ component.unclaim.subscribe(() => { unclaimed = true; });
- let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
- unclaimButton.triggerEventHandler('click', {});
+ let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
+ unclaimButton.triggerEventHandler('click', {});
- expect(unclaimed).toBeTruthy();
- });
+ expect(unclaimed).toBeTruthy();
});
it('should display due date', () => {
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.ts
index 9c90b0553b..f99769bb54 100644
--- a/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.ts
+++ b/ng2-components/ng2-activiti-tasklist/src/components/task-header.component.ts
@@ -15,8 +15,9 @@
* limitations under the License.
*/
-import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
+import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { CardViewDateItemModel, CardViewItem, CardViewMapItemModel, CardViewTextItemModel, LogService } from 'ng2-alfresco-core';
+import { BpmUserService } from 'ng2-alfresco-userinfo';
import { TaskDetailsModel } from '../models/task-details.model';
import { TaskListService } from './../services/tasklist.service';
@@ -25,7 +26,7 @@ import { TaskListService } from './../services/tasklist.service';
templateUrl: './task-header.component.html',
styleUrls: ['./task-header.component.scss']
})
-export class TaskHeaderComponent implements OnChanges {
+export class TaskHeaderComponent implements OnChanges, OnInit {
@Input()
formName: string = null;
@@ -39,13 +40,20 @@ export class TaskHeaderComponent implements OnChanges {
@Output()
unclaim: EventEmitter = new EventEmitter();
+ private currentUserId: number;
+
properties: CardViewItem [];
inEdit: boolean = false;
constructor(private activitiTaskService: TaskListService,
+ private bpmUserService: BpmUserService,
private logService: LogService) {
}
+ ngOnInit() {
+ this.getCurrentUserId();
+ }
+
ngOnChanges(changes: SimpleChanges) {
this.refreshData();
}
@@ -148,6 +156,15 @@ export class TaskHeaderComponent implements OnChanges {
}
}
+ /**
+ * Return the bpmUser
+ */
+ private getCurrentUserId(): void {
+ this.bpmUserService.getCurrentUserInfo().subscribe((res) => {
+ this.currentUserId = res ? +res.id : null;
+ });
+ }
+
/**
* Return the process parent information
*/
@@ -161,14 +178,49 @@ export class TaskHeaderComponent implements OnChanges {
* Does the task have an assignee
*/
public hasAssignee(): boolean {
- return (this.taskDetails && this.taskDetails.assignee) ? true : false;
+ return !!this.taskDetails.assignee ? true : false;
}
/**
- * Is the task assigned to the currently loggedin user
+ * Returns true if the task is assigne to logged in user
*/
- isAssignedToMe(): boolean {
- return this.taskDetails.assignee ? true : false;
+ public isAssignedTo(userId): boolean {
+ return this.hasAssignee() ? this.taskDetails.assignee.id === userId : false;
+ }
+
+ /**
+ * Return true if the task assigned
+ */
+ public isAssignedToCurrentUser(): boolean {
+ return this.hasAssignee() && this.isAssignedTo(this.currentUserId);
+ }
+
+ /**
+ * Return true if the task has involvedGroup
+ */
+ public hasInvolvedGroup(): boolean {
+ return this.taskDetails.involvedGroups.length > 0 ? true : false;
+ }
+
+ /**
+ * Return true if the task has involvedPeople
+ */
+ public hasInvolvedPeople(): boolean {
+ return this.taskDetails.involvedPeople.length > 0 ? true : false;
+ }
+
+ /**
+ * Return true if the task claimable
+ */
+ public isTaskClaimable(): boolean {
+ return !this.isCompleted() && (this.hasInvolvedGroup() || this.hasInvolvedPeople()) && !this.hasAssignee() ;
+ }
+
+ /**
+ * Return true if the task claimed by currentUser
+ */
+ public isTaskClaimedByCurrentUser(): boolean {
+ return !this.isCompleted() && (this.hasInvolvedGroup() || this.hasInvolvedPeople()) && this.isAssignedToCurrentUser();
}
/**
@@ -207,7 +259,7 @@ export class TaskHeaderComponent implements OnChanges {
/**
* Returns true if the task is completed
*/
- isCompleted() {
- return !!this.taskDetails.endDate;
+ isCompleted(): boolean {
+ return this.taskDetails && !!this.taskDetails.endDate;
}
}
diff --git a/ng2-components/ng2-activiti-tasklist/src/models/task-details.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/task-details.model.ts
index 5f395a8981..d57488cfb6 100644
--- a/ng2-components/ng2-activiti-tasklist/src/models/task-details.model.ts
+++ b/ng2-components/ng2-activiti-tasklist/src/models/task-details.model.ts
@@ -24,6 +24,7 @@
*/
import { TaskRepresentation } from 'alfresco-js-api';
import { UserProcessModel } from 'ng2-alfresco-core';
+import { UserGroupModel } from './user-group.model';
export class TaskDetailsModel implements TaskRepresentation {
id: string;
@@ -43,6 +44,7 @@ export class TaskDetailsModel implements TaskRepresentation {
managerOfCandidateGroup: boolean;
memberOfCandidateGroup: boolean;
memberOfCandidateUsers: boolean;
+ involvedGroups: UserGroupModel [];
involvedPeople: UserProcessModel [];
parentTaskId: string;
parentTaskName: string;
@@ -77,6 +79,7 @@ export class TaskDetailsModel implements TaskRepresentation {
this.managerOfCandidateGroup = !!obj.managerOfCandidateGroup;
this.memberOfCandidateGroup = !!obj.memberOfCandidateGroup;
this.memberOfCandidateUsers = !!obj.memberOfCandidateUsers;
+ this.involvedGroups = obj.involvedGroups;
this.involvedPeople = obj.involvedPeople;
this.parentTaskId = obj.parentTaskId || null;
this.parentTaskName = obj.parentTaskName || null;
diff --git a/ng2-components/ng2-activiti-tasklist/src/models/user-group.model.ts b/ng2-components/ng2-activiti-tasklist/src/models/user-group.model.ts
new file mode 100644
index 0000000000..117237c9a7
--- /dev/null
+++ b/ng2-components/ng2-activiti-tasklist/src/models/user-group.model.ts
@@ -0,0 +1,36 @@
+/*!
+ * @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.
+ */
+
+/**
+ * This object represent the process service user group.*
+ */
+
+export class UserGroupModel {
+ id: number;
+ name: string;
+ externalId: string;
+ status: string;
+ groups: any = {};
+
+ constructor(obj?: any) {
+ this.id = obj && obj.id;
+ this.name = obj && obj.name;
+ this.externalId = obj && obj.externalId;
+ this.status = obj && obj.status;
+ this.groups = obj && obj.groups;
+ }
+}