#1259 - checklist task does not appear on tasklist after creation (#1435)

* #1259 - refresh tasklist when checklist is added

* #1259 - updated readme to reflect changes

* #1259 - fixed wrong rebase import
This commit is contained in:
Vito
2017-01-11 06:32:17 -08:00
committed by Maurizio Vitale
parent 6f24ebd6d4
commit 9e0536bca9
11 changed files with 81 additions and 23 deletions

View File

@@ -36,6 +36,7 @@
[state]="taskFilter.filter.state" [state]="taskFilter.filter.state"
[sort]="taskFilter.filter.sort" [sort]="taskFilter.filter.sort"
[data]="dataTasks" [data]="dataTasks"
[landingTaskId]="currentTaskId"
(rowClick)="onTaskRowClick($event)" (onSuccess)="onSuccessTaskList($event)" (rowClick)="onTaskRowClick($event)" (onSuccess)="onSuccessTaskList($event)"
#activititasklist></activiti-tasklist> #activititasklist></activiti-tasklist>
</div> </div>
@@ -43,6 +44,7 @@
<span><h5>Task Details</h5></span> <span><h5>Task Details</h5></span>
<hr> <hr>
<activiti-task-details [taskId]="currentTaskId" (formCompleted)="onFormCompleted($event)" <activiti-task-details [taskId]="currentTaskId" (formCompleted)="onFormCompleted($event)"
(taskCreated)="onTaskCreated($event)"
#activitidetails></activiti-task-details> #activitidetails></activiti-task-details>
</div> </div>
</div> </div>

View File

@@ -216,6 +216,11 @@ export class ActivitiDemoComponent implements AfterViewInit {
this.currentTaskId = null; this.currentTaskId = null;
} }
onTaskCreated(data: any) {
this.currentTaskId = data.parentTaskId;
this.activititasklist.reload();
}
ngAfterViewInit() { ngAfterViewInit() {
// workaround for MDL issues with dynamic components // workaround for MDL issues with dynamic components
if (componentHandler) { if (componentHandler) {

View File

@@ -218,17 +218,13 @@ platformBrowserDynamic().bootstrapModule(AppModule);
| Name | Description | | Name | Description |
| --- | --- | | --- | --- |
|`appId`| { appId } The id of the app. | |`appId`| { string } The id of the app. |
|`processDefinitionKey`| { processDefinitionKey } The processDefinitionKey of the process. | |`processDefinitionKey`| { string } The processDefinitionKey of the process. |
|`assignment`| { assignment } The assignment of the process. |`assignment`| { string } The assignment of the process. <ul>Possible values are: <li>assignee : where the current user is the assignee</li> <li>candidate: where the current user is a task candidate </li><li>group_x: where the task is assigned to a group where the current user is a member of.</li> <li>no value: where the current user is involved</li> </ul> |
Possible values are: |`state`| { string } Define state of the processes. Possible values are: completed, active |
assignee : where the current user is the assignee |`landingTaskId`| { string } Define which task id should be selected after the reloading. If the task id doesn't exist or nothing is passed it will select the first task |
candidate: where the current user is a task candidate |`sort`| { string } Define the sort of the processes. Possible values are : created-desc, created-asc, due-desc, due-asc |
group_x: where the task is assigned to a group where the current user is a member of. The groups can be fetched through the profile REST endpoint | `data` | { DataTableAdapter } (optional) JSON object that represent the number and the type of the columns that you want show |
no value: where the current user is involved |
|`state`| { state } Define state of the processes. Possible values are: completed, active |
|`sort`| { sort } Define the sort of the processes. Possible values are created-desc, created-asc, due-desc, due-asc |
| `schemaColumn` | { any[] } optional) JSON object that represent the number and the type of the columns that you want show |
Example: Example:
@@ -256,6 +252,7 @@ The component shows the details of the task id passed in input
| `formLoaded` | Invoked when form is loaded or reloaded. | | `formLoaded` | Invoked when form is loaded or reloaded. |
| `formSaved` | Invoked when form is submitted with `Save` or custom outcomes. | | `formSaved` | Invoked when form is submitted with `Save` or custom outcomes. |
| `formCompleted` | Invoked when form is submitted with `Complete` outcome. | | `formCompleted` | Invoked when form is submitted with `Complete` outcome. |
| `taskCreated` | Invoked when a checklist task is created. |
| `executeOutcome` | Invoked when any outcome is executed, default behaviour can be prevented via `event.preventDefault()` | | `executeOutcome` | Invoked when any outcome is executed, default behaviour can be prevented via `event.preventDefault()` |
| `onError` | Invoked at any error | | `onError` | Invoked at any error |

View File

@@ -51,7 +51,9 @@ describe('ActivitiChecklist', () => {
}).compileComponents().then(() => { }).compileComponents().then(() => {
let translateService = TestBed.get(AlfrescoTranslateService); let translateService = TestBed.get(AlfrescoTranslateService);
spyOn(translateService, 'addTranslationFolder').and.stub(); spyOn(translateService, 'addTranslationFolder').and.stub();
spyOn(translateService, 'get').and.callFake((key) => { return Observable.of(key); }); spyOn(translateService, 'get').and.callFake((key) => {
return Observable.of(key);
});
fixture = TestBed.createComponent(ActivitiChecklist); fixture = TestBed.createComponent(ActivitiChecklist);
checklistComponent = fixture.componentInstance; checklistComponent = fixture.componentInstance;
@@ -129,7 +131,7 @@ describe('ActivitiChecklist', () => {
jasmine.Ajax.requests.mostRecent().respondWith({ jasmine.Ajax.requests.mostRecent().respondWith({
status: 200, status: 200,
contentType: 'json', contentType: 'json',
responseText: {id: 'fake-check-added-id', name: 'fake-check-added-name'} responseText: { id: 'fake-check-added-id', name: 'fake-check-added-name' }
}); });
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@@ -149,7 +151,7 @@ describe('ActivitiChecklist', () => {
jasmine.Ajax.requests.mostRecent().respondWith({ jasmine.Ajax.requests.mostRecent().respondWith({
status: 200, status: 200,
contentType: 'json', contentType: 'json',
responseText: {data: [{id: 'fake-check-changed-id', name: 'fake-check-changed-name'}]} responseText: { data: [{ id: 'fake-check-changed-id', name: 'fake-check-changed-name' }] }
}); });
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
fixture.detectChanges(); fixture.detectChanges();
@@ -173,6 +175,25 @@ describe('ActivitiChecklist', () => {
expect(element.querySelector('#checklist-none-message').textContent).toContain('TASK_DETAILS.CHECKLIST.NONE'); expect(element.querySelector('#checklist-none-message').textContent).toContain('TASK_DETAILS.CHECKLIST.NONE');
}); });
})); }));
it('should emit checklist task created event when the checklist is successfully added', (done) => {
checklistComponent.checklistTaskCreated.subscribe((taskAdded: TaskDetailsModel) => {
fixture.detectChanges();
expect(taskAdded.id).toEqual('fake-check-added-id');
expect(taskAdded.name).toEqual('fake-check-added-name');
expect(element.querySelector('#check-fake-check-added-id')).not.toBeNull();
expect(element.querySelector('#check-fake-check-added-id').textContent).toContain('fake-check-added-name');
done();
});
showChecklistDialog.click();
let addButtonDialog = <HTMLElement> element.querySelector('#add-check');
addButtonDialog.click();
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: { id: 'fake-check-added-id', name: 'fake-check-added-name' }
});
});
}); });
}); });

View File

@@ -15,11 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, OnInit, ViewChild, OnChanges, SimpleChanges } from '@angular/core'; import { Component, Input, OnInit, ViewChild, OnChanges, SimpleChanges, EventEmitter, Output } from '@angular/core';
import { Observer, Observable } from 'rxjs/Rx';
import { AlfrescoTranslateService, LogService } from 'ng2-alfresco-core'; import { AlfrescoTranslateService, LogService } from 'ng2-alfresco-core';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
import { TaskDetailsModel } from '../models/task-details.model'; import { TaskDetailsModel } from '../models/task-details.model';
import { Observer, Observable } from 'rxjs/Rx';
declare let dialogPolyfill: any; declare let dialogPolyfill: any;
@@ -41,6 +41,9 @@ export class ActivitiChecklist implements OnInit, OnChanges {
@Input() @Input()
assignee: string; assignee: string;
@Output()
checklistTaskCreated: EventEmitter<TaskDetailsModel> = new EventEmitter<TaskDetailsModel>();
@ViewChild('dialog') @ViewChild('dialog')
dialog: any; dialog: any;
@@ -116,6 +119,7 @@ export class ActivitiChecklist implements OnInit, OnChanges {
this.activitiTaskList.addTask(newTask).subscribe( this.activitiTaskList.addTask(newTask).subscribe(
(res: TaskDetailsModel) => { (res: TaskDetailsModel) => {
this.checklist.push(res); this.checklist.push(res);
this.checklistTaskCreated.emit(res);
}, },
(err) => { (err) => {
this.logService.error(err); this.logService.error(err);

View File

@@ -24,7 +24,7 @@
</div> </div>
<div class="mdl-cell mdl-cell--4-col"> <div class="mdl-cell mdl-cell--4-col">
<activiti-checklist [readOnly]="readOnlyForm" [taskId]="taskDetails.id" [assignee]="taskDetails?.assignee?.id" <activiti-checklist [readOnly]="readOnlyForm" [taskId]="taskDetails.id" [assignee]="taskDetails?.assignee?.id"
#activitichecklist></activiti-checklist> (checklistTaskCreated)="onChecklistTaskCreated($event)" #activitichecklist></activiti-checklist>
</div> </div>
</div> </div>
<div *ngIf="isAssignedToMe()"> <div *ngIf="isAssignedToMe()">

View File

@@ -27,6 +27,7 @@ import { ActivitiTaskDetails } from './activiti-task-details.component';
import { ActivitiTaskListService } from './../services/activiti-tasklist.service'; import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
import { ActivitiPeopleService } from './../services/activiti-people.service'; import { ActivitiPeopleService } from './../services/activiti-people.service';
import { taskDetailsMock, taskFormMock, tasksMock, noDataMock } from './../assets/task-details.mock'; import { taskDetailsMock, taskFormMock, tasksMock, noDataMock } from './../assets/task-details.mock';
import { TaskDetailsModel } from '../models/task-details.model';
describe('ActivitiTaskDetails', () => { describe('ActivitiTaskDetails', () => {
@@ -246,6 +247,13 @@ describe('ActivitiTaskDetails', () => {
expect(closeSpy).toHaveBeenCalled(); expect(closeSpy).toHaveBeenCalled();
}); });
it('should emit a task created event when checklist task is created', () => {
let emitSpy: jasmine.Spy = spyOn(component.taskCreated, 'emit');
let mockTask = new TaskDetailsModel(taskDetailsMock);
component.onChecklistTaskCreated(mockTask);
expect(emitSpy).toHaveBeenCalled();
});
}); });
}); });

View File

@@ -70,6 +70,9 @@ export class ActivitiTaskDetails implements OnInit, OnChanges {
@Output() @Output()
formLoaded: EventEmitter<FormModel> = new EventEmitter<FormModel>(); formLoaded: EventEmitter<FormModel> = new EventEmitter<FormModel>();
@Output()
taskCreated: EventEmitter<TaskDetailsModel> = new EventEmitter<TaskDetailsModel>();
@Output() @Output()
onError: EventEmitter<any> = new EventEmitter<any>(); onError: EventEmitter<any> = new EventEmitter<any>();
@@ -224,6 +227,10 @@ export class ActivitiTaskDetails implements OnInit, OnChanges {
this.formLoaded.emit(form); this.formLoaded.emit(form);
} }
onChecklistTaskCreated(task: TaskDetailsModel) {
this.taskCreated.emit(task);
}
onFormError(error: any) { onFormError(error: any) {
this.errorDialog.nativeElement.showModal(); this.errorDialog.nativeElement.showModal();
this.onError.emit(error); this.onError.emit(error);

View File

@@ -161,7 +161,7 @@ describe('ActivitiTaskList', () => {
}); });
it('should return a currentId null when the taskList is empty', () => { it('should return a currentId null when the taskList is empty', () => {
component.selectFirst(); component.selectTask(null);
expect(component.getCurrentId()).toBeNull(); expect(component.getCurrentId()).toBeNull();
}); });

View File

@@ -49,6 +49,9 @@ export class ActivitiTaskList implements OnInit, OnChanges {
@Input() @Input()
name: string; name: string;
@Input()
landingTaskId: string;
requestNode: TaskQueryRequestRepresentationModel; requestNode: TaskQueryRequestRepresentationModel;
@Input() @Input()
@@ -142,7 +145,7 @@ export class ActivitiTaskList implements OnInit, OnChanges {
(response) => { (response) => {
let instancesRow = this.createDataRow(response); let instancesRow = this.createDataRow(response);
this.renderInstances(instancesRow); this.renderInstances(instancesRow);
this.selectFirst(); this.selectTask(requestNode.landingTaskId);
this.onSuccess.emit(response); this.onSuccess.emit(response);
}, (error) => { }, (error) => {
this.logService.error(error); this.logService.error(error);
@@ -182,16 +185,24 @@ export class ActivitiTaskList implements OnInit, OnChanges {
} }
/** /**
* Select the first instance of a list if present * Select the task given in input if present
*/ */
selectFirst() { selectTask(taskIdToSelect: string) {
if (!this.isListEmpty()) { if (!this.isListEmpty()) {
this.currentInstanceId = this.data.getRows()[0].getValue('id'); let dataRow = this.data.getRows().find(row => row.getValue('id') === taskIdToSelect);
this.currentInstanceId = dataRow ? dataRow.getValue('id') : this.selectFirst();
} else { } else {
this.currentInstanceId = null; this.currentInstanceId = null;
} }
} }
/**
* Select the first instance of a list if present
*/
selectFirst() {
return this.data.getRows()[0].getValue('id');
}
/** /**
* Return the current id * Return the current id
* @returns {string} * @returns {string}
@@ -239,7 +250,8 @@ export class ActivitiTaskList implements OnInit, OnChanges {
text: this.name, text: this.name,
assignment: this.assignment, assignment: this.assignment,
state: this.state, state: this.state,
sort: this.sort sort: this.sort,
landingTaskId: this.landingTaskId
}; };
return new TaskQueryRequestRepresentationModel(requestNode); return new TaskQueryRequestRepresentationModel(requestNode);
} }

View File

@@ -116,6 +116,7 @@ export class TaskQueryRequestRepresentationModel {
sort: string; sort: string;
page: number; page: number;
size: number; size: number;
landingTaskId: string;
constructor(obj?: any) { constructor(obj?: any) {
this.appDefinitionId = obj && obj.appDefinitionId || null; this.appDefinitionId = obj && obj.appDefinitionId || null;
@@ -128,5 +129,6 @@ export class TaskQueryRequestRepresentationModel {
this.sort = obj && obj.sort || null; this.sort = obj && obj.sort || null;
this.page = obj && obj.page || 0; this.page = obj && obj.page || 0;
this.size = obj && obj.size || 25; this.size = obj && obj.size || 25;
this.landingTaskId = obj && obj.landingTaskId || '';
} }
} }