mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
@@ -0,0 +1,41 @@
|
||||
<div class="adf-checklist-control">
|
||||
<mat-chip-list id="checklist-label">
|
||||
<span class="activiti-label">{{ 'ADF_TASK_LIST.DETAILS.LABELS.CHECKLIST' | translate }}</span>
|
||||
<mat-chip class="adf-process-badge" color="accent" selected="true">{{checklist?.length}}</mat-chip>
|
||||
</mat-chip-list>
|
||||
|
||||
|
||||
<button mat-icon-button *ngIf="!readOnly" matTooltip="Add a checklist" [matTooltipPosition]="'before'" id="add-checklist" class="adf-add-to-checklist-button" (click)="showDialog()">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="adf-checklist-menu-container" *ngIf="checklist?.length > 0">
|
||||
<mat-chip-list class="mat-chip-list-stacked">
|
||||
<mat-chip id="check-{{check.id}}" class="adf-checklist-chip" *ngFor="let check of checklist">
|
||||
<span>{{check.name}}</span>
|
||||
<button *ngIf="!readOnly" mat-icon-button type="button" class="adf-checklist-cancel-button" (click)="delete(check.id)">
|
||||
<mat-icon id="remove-{{check.id}}" matChipRemove>cancel</mat-icon>
|
||||
</button>
|
||||
</mat-chip>
|
||||
</mat-chip-list>
|
||||
|
||||
</div>
|
||||
<div *ngIf="checklist?.length === 0" id="checklist-none-message">
|
||||
{{ 'ADF_TASK_LIST.DETAILS.CHECKLIST.NONE' | translate }}
|
||||
</div>
|
||||
|
||||
<ng-template #dialog>
|
||||
<div class="adf-checklist-dialog" id="checklist-dialog">
|
||||
<h4 matDialogTitle id="add-checklist-title">New Check</h4>
|
||||
<mat-dialog-content>
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="Name" [(ngModel)]="taskName" id="checklist-name" data-automation-id="checklist-name">
|
||||
</mat-form-field>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions class="adf-checklist-dialog-actions">
|
||||
<button mat-button type="button" id="close-check-dialog" (click)="cancel()">Cancel</button>
|
||||
<button mat-button type="button" id="add-check" (click)="add()">Add Checklist</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
</ng-template>
|
@@ -0,0 +1,49 @@
|
||||
:host {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.activiti-label {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.adf-checklist-cancel-button {
|
||||
margin-top: -13px;
|
||||
margin-right: -13px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.adf-checklist-chip {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.adf-checklist-menu-container{
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#checklist-none-message {
|
||||
margin-top: 10px;
|
||||
color: rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
.adf-checklist-control {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.activiti-label {
|
||||
margin-top: 6px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.adf-add-to-checklist-button {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.adf-checklist-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
@@ -0,0 +1,270 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { TaskListService } from '../services/tasklist.service';
|
||||
import { ChecklistComponent } from './checklist.component';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const fakeTaskDetail = new TaskDetailsModel({
|
||||
id: 'fake-check-id',
|
||||
name: 'fake-check-name'
|
||||
});
|
||||
|
||||
describe('ChecklistComponent', () => {
|
||||
|
||||
let checklistComponent: ChecklistComponent;
|
||||
let fixture: ComponentFixture<ChecklistComponent>;
|
||||
let element: HTMLElement;
|
||||
let showChecklistDialog, closeCheckDialogButton;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule,
|
||||
TranslateModule
|
||||
],
|
||||
declarations: [
|
||||
ChecklistComponent
|
||||
],
|
||||
providers: [
|
||||
TaskListService
|
||||
]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(ChecklistComponent);
|
||||
checklistComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show checklist component title', () => {
|
||||
expect(element.querySelector('#checklist-label')).toBeDefined();
|
||||
expect(element.querySelector('#checklist-label')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show no checklist message', () => {
|
||||
expect(element.querySelector('#checklist-none-message')).not.toBeNull();
|
||||
expect(element.querySelector('#checklist-none-message').textContent).toContain('ADF_TASK_LIST.DETAILS.CHECKLIST.NONE');
|
||||
});
|
||||
|
||||
describe('when is readonly mode', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
checklistComponent.taskId = 'fake-task-id';
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
checklistComponent.readOnly = true;
|
||||
|
||||
fixture.detectChanges();
|
||||
showChecklistDialog = <HTMLElement> element.querySelector('#add-checklist');
|
||||
closeCheckDialogButton = <HTMLElement> element.querySelector('#close-check-dialog');
|
||||
});
|
||||
|
||||
it('should NOT show add checklist button', () => {
|
||||
expect(element.querySelector('#add-checklist')).toBeNull();
|
||||
});
|
||||
|
||||
it('should NOT show cancel checklist button', () => {
|
||||
expect(element.querySelector('#remove-fake-check-id')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when is not in readonly mode', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
checklistComponent.taskId = 'fake-task-id';
|
||||
checklistComponent.readOnly = false;
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
|
||||
fixture.detectChanges();
|
||||
showChecklistDialog = <HTMLElement> element.querySelector('#add-checklist');
|
||||
closeCheckDialogButton = <HTMLElement> element.querySelector('#close-check-dialog');
|
||||
});
|
||||
|
||||
it('should show add checklist button', () => {
|
||||
expect(element.querySelector('#add-checklist')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show cancel checklist button', () => {
|
||||
expect(element.querySelector('#remove-fake-check-id')).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when interact with checklist dialog', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
checklistComponent.taskId = 'fake-task-id';
|
||||
checklistComponent.checklist = [];
|
||||
|
||||
fixture.detectChanges();
|
||||
showChecklistDialog = <HTMLElement> element.querySelector('#add-checklist');
|
||||
closeCheckDialogButton = <HTMLElement> element.querySelector('#close-check-dialog');
|
||||
});
|
||||
|
||||
it('should show dialog when clicked on add', () => {
|
||||
expect(showChecklistDialog).not.toBeNull();
|
||||
showChecklistDialog.click();
|
||||
|
||||
expect(window.document.querySelector('#checklist-dialog')).not.toBeNull();
|
||||
expect(window.document.querySelector('#add-checklist-title')).not.toBeNull();
|
||||
expect(window.document.querySelector('#add-checklist-title').textContent).toContain('New Check');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are task checklist', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
checklistComponent.taskId = 'fake-task-id';
|
||||
checklistComponent.checklist = [];
|
||||
fixture.detectChanges();
|
||||
showChecklistDialog = <HTMLElement> element.querySelector('#add-checklist');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
const overlayContainers = <any> window.document.querySelectorAll('.cdk-overlay-container');
|
||||
overlayContainers.forEach((overlayContainer) => {
|
||||
overlayContainer.innerHTML = '';
|
||||
});
|
||||
});
|
||||
|
||||
it('should show task checklist', () => {
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#check-fake-check-id')).not.toBeNull();
|
||||
expect(element.querySelector('#check-fake-check-id').textContent).toContain('fake-check-name');
|
||||
});
|
||||
|
||||
xit('should add checklist', async(() => {
|
||||
showChecklistDialog.click();
|
||||
let addButtonDialog = <HTMLElement> window.document.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' }
|
||||
});
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#check-fake-check-added-id')).not.toBeNull();
|
||||
expect(element.querySelector('#check-fake-check-added-id').textContent).toContain('fake-check-added-name');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should remove a checklist element', async(() => {
|
||||
checklistComponent.taskId = 'new-fake-task-id';
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
fixture.detectChanges();
|
||||
let checklistElementRemove = <HTMLElement> element.querySelector('#remove-fake-check-id');
|
||||
expect(checklistElementRemove).toBeDefined();
|
||||
expect(checklistElementRemove).not.toBeNull();
|
||||
checklistElementRemove.click();
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json'
|
||||
});
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#fake-check-id')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should send an event when the checklist is deleted', (done) => {
|
||||
checklistComponent.taskId = 'new-fake-task-id';
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
fixture.detectChanges();
|
||||
let checklistElementRemove = <HTMLElement> element.querySelector('#remove-fake-check-id');
|
||||
expect(checklistElementRemove).toBeDefined();
|
||||
expect(checklistElementRemove).not.toBeNull();
|
||||
checklistElementRemove.click();
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json'
|
||||
});
|
||||
checklistComponent.checklistTaskDeleted.subscribe(() => {
|
||||
expect(element.querySelector('#fake-check-id')).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show load task checklist on change', async(() => {
|
||||
checklistComponent.taskId = 'new-fake-task-id';
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
fixture.detectChanges();
|
||||
let change = new SimpleChange(null, 'new-fake-task-id', true);
|
||||
checklistComponent.ngOnChanges({
|
||||
taskId: change
|
||||
});
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: { data: [{ id: 'fake-check-changed-id', name: 'fake-check-changed-name' }] }
|
||||
});
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#check-fake-check-changed-id')).not.toBeNull();
|
||||
expect(element.querySelector('#check-fake-check-changed-id').textContent).toContain('fake-check-changed-name');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show empty checklist when task id is null', async(() => {
|
||||
checklistComponent.taskId = 'new-fake-task-id';
|
||||
checklistComponent.checklist.push(fakeTaskDetail);
|
||||
fixture.detectChanges();
|
||||
checklistComponent.taskId = null;
|
||||
let change = new SimpleChange(null, 'new-fake-task-id', true);
|
||||
checklistComponent.ngOnChanges({
|
||||
taskId: change
|
||||
});
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#checklist-none-message')).not.toBeNull();
|
||||
expect(element.querySelector('#checklist-none-message').textContent).toContain('ADF_TASK_LIST.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> window.document.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' }
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
145
lib/process-services/task-list/components/checklist.component.ts
Normal file
145
lib/process-services/task-list/components/checklist.component.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material';
|
||||
import { Observable, Observer } from 'rxjs/Rx';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-checklist',
|
||||
templateUrl: './checklist.component.html',
|
||||
styleUrls: ['./checklist.component.scss'],
|
||||
providers: [TaskListService]
|
||||
})
|
||||
export class ChecklistComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input()
|
||||
taskId: string;
|
||||
|
||||
@Input()
|
||||
readOnly: boolean = false;
|
||||
|
||||
@Input()
|
||||
assignee: string;
|
||||
|
||||
@Output()
|
||||
checklistTaskCreated: EventEmitter<TaskDetailsModel> = new EventEmitter<TaskDetailsModel>();
|
||||
|
||||
@Output()
|
||||
checklistTaskDeleted: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@ViewChild('dialog')
|
||||
addNewDialog: any;
|
||||
|
||||
taskName: string;
|
||||
|
||||
checklist: TaskDetailsModel [] = [];
|
||||
|
||||
private taskObserver: Observer<TaskDetailsModel>;
|
||||
task$: Observable<TaskDetailsModel>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param auth
|
||||
* @param translate
|
||||
*/
|
||||
constructor(
|
||||
private activitiTaskList: TaskListService,
|
||||
private dialog: MatDialog
|
||||
) {
|
||||
this.task$ = new Observable<TaskDetailsModel>(observer => this.taskObserver = observer).share();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.task$.subscribe((task: TaskDetailsModel) => {
|
||||
this.checklist.push(task);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
let taskId = changes['taskId'];
|
||||
if (taskId && taskId.currentValue) {
|
||||
this.getTaskChecklist(taskId.currentValue);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public getTaskChecklist(taskId: string) {
|
||||
this.checklist = [];
|
||||
if (this.taskId) {
|
||||
this.activitiTaskList.getTaskChecklist(this.taskId).subscribe(
|
||||
(res: TaskDetailsModel[]) => {
|
||||
res.forEach((task) => {
|
||||
this.taskObserver.next(task);
|
||||
});
|
||||
},
|
||||
(error) => {
|
||||
this.error.emit(error);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.checklist = [];
|
||||
}
|
||||
}
|
||||
|
||||
showDialog() {
|
||||
this.dialog.open(this.addNewDialog, { width: '350px' });
|
||||
}
|
||||
|
||||
public add() {
|
||||
let newTask = new TaskDetailsModel({
|
||||
name: this.taskName,
|
||||
parentTaskId: this.taskId,
|
||||
assignee: { id: this.assignee }
|
||||
});
|
||||
this.activitiTaskList.addTask(newTask).subscribe(
|
||||
(res: TaskDetailsModel) => {
|
||||
this.checklist.push(res);
|
||||
this.checklistTaskCreated.emit(res);
|
||||
this.taskName = '';
|
||||
},
|
||||
(error) => {
|
||||
this.error.emit(error);
|
||||
}
|
||||
);
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
public delete(taskId: string) {
|
||||
this.activitiTaskList.deleteTask(taskId).subscribe(
|
||||
() => {
|
||||
this.checklist = this.checklist.filter(check => check.id !== taskId);
|
||||
this.checklistTaskDeleted.emit(taskId);
|
||||
},
|
||||
(error) => {
|
||||
this.error.emit(error);
|
||||
});
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
this.dialog.closeAll();
|
||||
// if (this.addNewDialog) {
|
||||
// this.addNewDialog.nativeElement.close();
|
||||
// }
|
||||
this.taskName = '';
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NoTaskDetailsTemplateDirective } from './no-task-detail-template.directive';
|
||||
import { TaskDetailsComponent } from './task-details.component';
|
||||
|
||||
describe('NoTaskDetailsTemplateDirective', () => {
|
||||
|
||||
let component: NoTaskDetailsTemplateDirective;
|
||||
let detailsComponent: TaskDetailsComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
detailsComponent = new TaskDetailsComponent(null, null, null, null, null, null);
|
||||
component = new NoTaskDetailsTemplateDirective(detailsComponent);
|
||||
});
|
||||
|
||||
it('should set "no task details" template on task details component', () => {
|
||||
let testTemplate: any = 'test template';
|
||||
component.template = testTemplate;
|
||||
component.ngAfterContentInit();
|
||||
expect(detailsComponent.noTaskDetailsTemplateComponent).toBe(testTemplate);
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
AfterContentInit,
|
||||
ContentChild,
|
||||
Directive,
|
||||
TemplateRef
|
||||
} from '@angular/core';
|
||||
import { TaskDetailsComponent } from './task-details.component';
|
||||
|
||||
@Directive({
|
||||
selector: 'no-task-details-template'
|
||||
})
|
||||
export class NoTaskDetailsTemplateDirective implements AfterContentInit {
|
||||
|
||||
@ContentChild(TemplateRef)
|
||||
template: any;
|
||||
|
||||
constructor(
|
||||
private activitiTaskDetails: TaskDetailsComponent) {
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.activitiTaskDetails.noTaskDetailsTemplateComponent = this.template;
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
<mat-card class="adf-new-task-layout-card">
|
||||
<mat-grid-list cols="1" rowHeight="60px">
|
||||
<mat-grid-tile>
|
||||
<div class="adf-new-task-heading">{{'ADF_TASK_LIST.START_TASK.FORM.TITLE'|translate}}</div>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
<mat-card-content>
|
||||
<mat-grid-list cols="1" rowHeight="80px">
|
||||
<mat-grid-tile>
|
||||
<mat-form-field class="adf-new-task-text-width">
|
||||
<input matInput placeholder="{{'ADF_TASK_LIST.START_TASK.FORM.LABEL.NAME'|translate}}"
|
||||
[(ngModel)]="startTaskmodel.name" required id="name_id">
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
<mat-grid-list cols="1" rowHeight="80px">
|
||||
<mat-grid-tile>
|
||||
<mat-form-field class="adf-new-task-text-width">
|
||||
<textarea matInput placeholder="{{'ADF_TASK_LIST.START_TASK.FORM.LABEL.DESCRIPTION'|translate}}"
|
||||
[(ngModel)]="startTaskmodel.description" id="description_id"></textarea>
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
<mat-grid-list cols="2" rowHeight="80px">
|
||||
<mat-grid-tile>
|
||||
<mat-form-field class="adf-start-task-input-container">
|
||||
<input matInput
|
||||
[matDatepicker]="taskDatePicker"
|
||||
(keydown)="true"
|
||||
(focusout)="onDateChanged($event.srcElement.value)"
|
||||
placeholder="{{'ADF_TASK_LIST.START_TASK.FORM.LABEL.DATE'|translate}}"
|
||||
[(ngModel)]="startTaskmodel.dueDate" id="date_id">
|
||||
<mat-datepicker-toggle matSuffix [for]="taskDatePicker"></mat-datepicker-toggle>
|
||||
</mat-form-field>
|
||||
<mat-datepicker #taskDatePicker [touchUi]="true"
|
||||
(dateChanged)="onDateChanged($event)"></mat-datepicker>
|
||||
<div class="adf-error-text-container">
|
||||
<div *ngIf="dateError">
|
||||
<div class="adf-error-text">{{'ADF_TASK_LIST.START_TASK.FORM.DATE.ERROR'|translate}}</div>
|
||||
<mat-icon class="adf-error-icon">warning</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile>
|
||||
<mat-form-field class="adf-start-task-input-container">
|
||||
<mat-select placeholder="{{'ADF_TASK_LIST.START_TASK.FORM.LABEL.ASSIGNEE'|translate}}" id="assignee_id"
|
||||
class="adf-mat-select" [(ngModel)]="assigneeId">
|
||||
<mat-option>{{'ADF_TASK_LIST.START_TASK.FORM.LABEL.NONE'|translate}}</mat-option>
|
||||
<span *ngFor="let user of people">
|
||||
<mat-option [value]="user.id" *ngIf="!isUserNameEmpty(user)">{{ getDisplayUser(user.firstName,
|
||||
user.lastName, ' ')}}
|
||||
</mat-option>
|
||||
</span>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
<mat-grid-list cols="2" rowHeight="80px">
|
||||
<mat-grid-tile>
|
||||
<mat-form-field class="adf-start-task-input-container">
|
||||
<mat-select placeholder="{{'ADF_TASK_LIST.START_TASK.FORM.LABEL.FORM'|translate}}" id="form_id" [(ngModel)]="formKey">
|
||||
<mat-option>{{'ADF_TASK_LIST.START_TASK.FORM.LABEL.NONE'|translate}}</mat-option>
|
||||
<mat-option *ngFor="let form of forms" [value]="form.id">{{ form.name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</mat-grid-tile>
|
||||
<mat-grid-tile></mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<mat-grid-list cols="1" rowHeight="60px">
|
||||
<mat-grid-tile>
|
||||
<div class="adf-new-task-footer">
|
||||
<button mat-button (click)="onCancel()" id="button-cancle">
|
||||
{{'ADF_TASK_LIST.START_TASK.FORM.ACTION.CANCEL'|translate}}
|
||||
</button>
|
||||
<button mat-button [disabled]="!startTaskmodel.name || dateError" (click)="start()" id="button-start">
|
||||
{{'ADF_TASK_LIST.START_TASK.FORM.ACTION.START'|translate}}
|
||||
</button>
|
||||
</div>
|
||||
</mat-grid-tile>
|
||||
</mat-grid-list>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
@@ -0,0 +1,106 @@
|
||||
@mixin adf-task-list-start-task-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
|
||||
.adf-new-task-heading {
|
||||
padding: 12px 20px;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 18px;
|
||||
float: left;
|
||||
text-align: left;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.adf-new-task-layout-card {
|
||||
width: 66.6667%;
|
||||
margin-right: calc(33.3333% / 2 - 24px);
|
||||
margin-left: calc(33.3333% / 2 - 24px);
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.adf-new-task-footer {
|
||||
padding: 4px;
|
||||
font-size: 18px;
|
||||
border-top: 1px solid #eee;
|
||||
float: left;
|
||||
width: calc(100% - 40px);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.adf-start-task-input-container {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.adf-new-task-text-width {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.adf-mat-select {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
adf-start-task {
|
||||
.adf {
|
||||
|
||||
&-start-task-input-container .mat-input-wrapper {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
&-error-text-container {
|
||||
position: absolute;
|
||||
width: 81%;
|
||||
height: 20px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
&-error-text {
|
||||
padding: 1px;
|
||||
height: 16px;
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
float: left;
|
||||
color: mat-color($warn);
|
||||
}
|
||||
|
||||
&-error-icon {
|
||||
float: right;
|
||||
font-size: 17px;
|
||||
color: mat-color($warn);
|
||||
}
|
||||
|
||||
&-label {
|
||||
color: rgb(186, 186, 186);;
|
||||
}
|
||||
|
||||
&-invalid {
|
||||
|
||||
.mat-input-underline {
|
||||
background-color: #f44336 !important;
|
||||
}
|
||||
|
||||
.adf-file {
|
||||
border-color: mat-color($warn);
|
||||
}
|
||||
|
||||
.mat-input-prefix {
|
||||
color: mat-color($warn);
|
||||
}
|
||||
|
||||
.adf-input {
|
||||
border-color: mat-color($warn);
|
||||
}
|
||||
|
||||
.adf-label {
|
||||
color: mat-color($warn);
|
||||
&:after {
|
||||
background-color: mat-color($warn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,387 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { PeopleProcessService } from '@alfresco/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { startTaskMock } from '../../mock';
|
||||
import { StartTaskModel } from '../models/start-task.model';
|
||||
import { TaskListService } from '../services/tasklist.service';
|
||||
import { } from './../assets/start-task.mock';
|
||||
import { StartTaskComponent } from './start-task.component';
|
||||
|
||||
describe('StartTaskComponent', () => {
|
||||
|
||||
let component: StartTaskComponent;
|
||||
let fixture: ComponentFixture<StartTaskComponent>;
|
||||
let service: TaskListService;
|
||||
let peopleService: PeopleProcessService;
|
||||
let element: HTMLElement;
|
||||
let getFormlistSpy: jasmine.Spy;
|
||||
let getWorkflowUsersSpy: jasmine.Spy;
|
||||
let createNewTaskSpy: jasmine.Spy;
|
||||
let attachFormSpy: jasmine.Spy;
|
||||
let assignUserSpy: jasmine.Spy;
|
||||
let fakeForms = [
|
||||
{
|
||||
id: 123,
|
||||
name: 'Display Data'
|
||||
},
|
||||
{
|
||||
id: 1111,
|
||||
name: 'Employee Info'
|
||||
}
|
||||
];
|
||||
let testUser = {id: 1001, firstName: 'fakeName', email: 'fake@app.activiti.com'};
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
StartTaskComponent
|
||||
],
|
||||
providers: [
|
||||
TaskListService,
|
||||
PeopleProcessService
|
||||
]
|
||||
}).compileComponents().then(() => {
|
||||
|
||||
fixture = TestBed.createComponent(StartTaskComponent);
|
||||
component = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
|
||||
service = fixture.debugElement.injector.get(TaskListService);
|
||||
peopleService = fixture.debugElement.injector.get(PeopleProcessService);
|
||||
getFormlistSpy = spyOn(service, 'getFormList').and.returnValue(Observable.of(fakeForms));
|
||||
getWorkflowUsersSpy = spyOn(peopleService, 'getWorkflowUsers').and.returnValue(Observable.of([
|
||||
{
|
||||
id: 1,
|
||||
firstName: 'fakeName',
|
||||
lastName: 'fakeName',
|
||||
email: 'fake@app.activiti.com',
|
||||
company: 'Alfresco.com',
|
||||
pictureId: 3003
|
||||
},
|
||||
{
|
||||
id: 1001,
|
||||
firstName: 'fake-name',
|
||||
lastName: 'fake-name',
|
||||
email: 'fake-@app.com',
|
||||
company: 'app'
|
||||
}
|
||||
]));
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should create instance of StartTaskComponent', () => {
|
||||
expect(fixture.componentInstance instanceof StartTaskComponent).toBe(true, 'should create StartTaskComponent');
|
||||
});
|
||||
|
||||
it('should fetch fakeform on ngonint', () => {
|
||||
component.ngOnInit();
|
||||
expect(component.forms).toEqual(fakeForms);
|
||||
expect(component.forms[0].name).toEqual('Display Data');
|
||||
expect(component.forms[1].name).toEqual('Employee Info');
|
||||
expect(component.forms[1].id).toEqual(1111);
|
||||
expect(getFormlistSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('create task', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
createNewTaskSpy = spyOn(service, 'createNewTask').and.returnValue(Observable.of(
|
||||
{
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: null,
|
||||
assignee: null
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
it('should create new task when start is clicked', () => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
component.appId = 42;
|
||||
component.startTaskmodel = new StartTaskModel(startTaskMock);
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
createTaskButton.click();
|
||||
expect(successSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should send on success event when the task is started', () => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
component.appId = 42;
|
||||
component.startTaskmodel = new StartTaskModel(startTaskMock);
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
createTaskButton.click();
|
||||
expect(successSpy).toHaveBeenCalledWith({
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: null,
|
||||
assignee: null
|
||||
});
|
||||
});
|
||||
|
||||
it('should send on success event when only name is given', () => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
component.appId = 42;
|
||||
component.startTaskmodel.name = 'fakeName';
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
createTaskButton.click();
|
||||
expect(successSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not emit success event when data not present', () => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
component.startTaskmodel = new StartTaskModel(null);
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
createTaskButton.click();
|
||||
expect(createNewTaskSpy).not.toHaveBeenCalled();
|
||||
expect(successSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('attach form', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'createNewTask').and.returnValue(Observable.of(
|
||||
{
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: null,
|
||||
assignee: null
|
||||
}
|
||||
));
|
||||
attachFormSpy = spyOn(service, 'attachFormToATask').and.returnValue(Observable.of(
|
||||
{
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: 1204,
|
||||
assignee: null
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
it('should attach form to the task when a form is selected', () => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
component.appId = 42;
|
||||
component.startTaskmodel = new StartTaskModel(startTaskMock);
|
||||
component.formKey = 1204;
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
createTaskButton.click();
|
||||
expect(successSpy).toHaveBeenCalledWith({
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: 1204,
|
||||
assignee: null
|
||||
});
|
||||
});
|
||||
|
||||
it('should not attach form to the task when a no form is selected', () => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
component.appId = 42;
|
||||
component.startTaskmodel = new StartTaskModel(startTaskMock);
|
||||
component.formKey = null;
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
createTaskButton.click();
|
||||
expect(successSpy).toHaveBeenCalledWith({
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: null,
|
||||
assignee: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('assign user', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'createNewTask').and.returnValue(Observable.of(
|
||||
{
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: null,
|
||||
assignee: null
|
||||
}
|
||||
));
|
||||
spyOn(service, 'attachFormToATask').and.returnValue(Observable.of(
|
||||
{
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: 1204,
|
||||
assignee: null
|
||||
}
|
||||
));
|
||||
assignUserSpy = spyOn(service, 'assignTaskByUserId').and.returnValue(Observable.of(
|
||||
{
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: 1204,
|
||||
assignee: testUser
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
it('should assign task when an assignee is selected', () => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
component.appId = 42;
|
||||
component.startTaskmodel = new StartTaskModel(startTaskMock);
|
||||
component.formKey = 1204;
|
||||
component.assigneeId = testUser.id;
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
createTaskButton.click();
|
||||
expect(successSpy).toHaveBeenCalledWith({
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: 1204,
|
||||
assignee: testUser
|
||||
});
|
||||
});
|
||||
|
||||
it('should not assign task when no assignee is selected', () => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
component.appId = 42;
|
||||
component.formKey = 1204;
|
||||
component.assigneeId = null;
|
||||
component.startTaskmodel = new StartTaskModel(startTaskMock);
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
createTaskButton.click();
|
||||
expect(successSpy).toHaveBeenCalledWith({
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
formKey: 1204,
|
||||
assignee: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not attach a form when a form id is not slected', () => {
|
||||
let attachFormToATask = spyOn(service, 'attachFormToATask').and.returnValue(Observable.of());
|
||||
spyOn(service, 'createNewTask').and.callFake(
|
||||
function() {
|
||||
return Observable.create(observer => {
|
||||
observer.next({ id: 'task-id'});
|
||||
observer.complete();
|
||||
});
|
||||
});
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
component.startTaskmodel.name = 'fake-name';
|
||||
fixture.detectChanges();
|
||||
createTaskButton.click();
|
||||
expect(attachFormToATask).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show start task button', () => {
|
||||
expect(element.querySelector('#button-start')).toBeDefined();
|
||||
expect(element.querySelector('#button-start')).not.toBeNull();
|
||||
expect(element.querySelector('#button-start').textContent).toContain('ADF_TASK_LIST.START_TASK.FORM.ACTION.START');
|
||||
});
|
||||
|
||||
it('should fetch all users on ngonint', () => {
|
||||
component.ngOnInit();
|
||||
expect(component.people).toBeDefined();
|
||||
expect(component.people[0].firstName).toEqual('fakeName');
|
||||
expect(component.people[1].firstName).toEqual('fake-name');
|
||||
expect(component.people[0].id).toEqual(1);
|
||||
expect(component.people[1].id).toEqual(1001);
|
||||
expect(getWorkflowUsersSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not emit TaskDetails OnCancel', () => {
|
||||
let emitSpy = spyOn(component.cancel, 'emit');
|
||||
component.onCancel();
|
||||
expect(emitSpy).not.toBeNull();
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should disable start button if name is empty', () => {
|
||||
component.startTaskmodel.name = '';
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = fixture.nativeElement.querySelector('#button-start');
|
||||
expect(createTaskButton.disabled).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should cancel start task on cancel button click', () => {
|
||||
let emitSpy = spyOn(component.cancel, 'emit');
|
||||
let cancleTaskButton = fixture.nativeElement.querySelector('#button-cancle');
|
||||
component.startTaskmodel.name = '';
|
||||
fixture.detectChanges();
|
||||
cancleTaskButton.click();
|
||||
expect(emitSpy).not.toBeNull();
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should enable start button if name is filled out', () => {
|
||||
component.startTaskmodel.name = 'fakeName';
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = fixture.nativeElement.querySelector('#button-start');
|
||||
expect(createTaskButton.disabled).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should define the select option for Assignee', async(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
let selectElement = fixture.nativeElement.querySelector('#assignee_id');
|
||||
expect(selectElement).not.toBeNull();
|
||||
expect(selectElement).toBeDefined();
|
||||
expect(selectElement.attributes['aria-label'].value).toContain('ADF_TASK_LIST.START_TASK.FORM.LABEL.ASSIGNEE');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should define the select option for Forms', () => {
|
||||
component.forms = fakeForms;
|
||||
fixture.detectChanges();
|
||||
let selectElement = fixture.nativeElement.querySelector('#form_id');
|
||||
expect(selectElement.attributes['aria-label'].value).toContain('ADF_TASK_LIST.START_TASK.FORM.LABEL.FORM');
|
||||
});
|
||||
|
||||
it('should get formatted fullname', () => {
|
||||
let testUser1 = {'id': 1001, 'firstName': 'Wilbur', 'lastName': 'Adams', 'email': 'wilbur@app.activiti.com'};
|
||||
let testUser2 = {'id': 1002, 'firstName': '', 'lastName': 'Adams', 'email': 'adams@app.activiti.com'};
|
||||
let testUser3 = {'id': 1003, 'firstName': 'Wilbur', 'lastName': '', 'email': 'wilbur@app.activiti.com'};
|
||||
let testUser4 = {'id': 1004, 'firstName': '', 'lastName': '', 'email': 'test@app.activiti.com'};
|
||||
|
||||
let testFullname1 = component.getDisplayUser(testUser1.firstName, testUser1.lastName, ' ');
|
||||
let testFullname2 = component.getDisplayUser(testUser2.firstName, testUser2.lastName, ' ');
|
||||
let testFullname3 = component.getDisplayUser(testUser3.firstName, testUser3.lastName, ' ');
|
||||
let testFullname4 = component.getDisplayUser(testUser4.firstName, testUser4.lastName, ' ');
|
||||
|
||||
expect(testFullname1.trim()).toBe('Wilbur Adams');
|
||||
expect(testFullname2.trim()).toBe('Adams');
|
||||
expect(testFullname3.trim()).toBe('Wilbur');
|
||||
expect(testFullname4.trim()).toBe('');
|
||||
});
|
||||
|
||||
xit('should emit error when there is an error while creating task', () => {
|
||||
let errorSpy = spyOn(component.error, 'emit');
|
||||
spyOn(service, 'createNewTask').and.returnValue(Observable.throw({}));
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
component.startTaskmodel.name = 'fake-name';
|
||||
fixture.detectChanges();
|
||||
createTaskButton.click();
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
@@ -0,0 +1,177 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { LogService, MOMENT_DATE_FORMATS,
|
||||
MomentDateAdapter, PeopleProcessService, UserPreferencesService, UserProcessModel } from '@alfresco/core';
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material';
|
||||
import * as moment from 'moment';
|
||||
import { Moment } from 'moment';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Form } from '../models/form.model';
|
||||
import { StartTaskModel } from '../models/start-task.model';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-start-task',
|
||||
templateUrl: './start-task.component.html',
|
||||
styleUrls: ['./start-task.component.scss'],
|
||||
providers: [
|
||||
{provide: DateAdapter, useClass: MomentDateAdapter},
|
||||
{provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS}],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class StartTaskComponent implements OnInit {
|
||||
|
||||
public FORMAT_DATE: string = 'DD/MM/YYYY';
|
||||
|
||||
@Input()
|
||||
appId: number;
|
||||
|
||||
@Output()
|
||||
success: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
cancel: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
people: UserProcessModel[] = [];
|
||||
|
||||
startTaskmodel: StartTaskModel = new StartTaskModel();
|
||||
|
||||
forms: Form[];
|
||||
|
||||
assigneeId: number;
|
||||
|
||||
formKey: number;
|
||||
|
||||
taskId: string;
|
||||
|
||||
dateError: boolean;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param auth
|
||||
* @param translate
|
||||
* @param taskService
|
||||
*/
|
||||
constructor(
|
||||
private taskService: TaskListService,
|
||||
private peopleService: PeopleProcessService,
|
||||
private dateAdapter: DateAdapter<Moment>,
|
||||
private preferences: UserPreferencesService,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.preferences.locale$.subscribe( (locale) => {
|
||||
this.dateAdapter.setLocale(locale);
|
||||
});
|
||||
this.loadFormsTask();
|
||||
this.getUsers();
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
if (this.startTaskmodel.name) {
|
||||
if (this.appId) {
|
||||
this.startTaskmodel.category = this.appId.toString();
|
||||
}
|
||||
this.taskService.createNewTask(new TaskDetailsModel(this.startTaskmodel))
|
||||
.switchMap((createRes: any) =>
|
||||
this.attachForm(createRes.id, this.formKey).defaultIfEmpty(createRes)
|
||||
.switchMap((attachRes: any) =>
|
||||
this.assignTaskByUserId(createRes.id, this.assigneeId).defaultIfEmpty(attachRes ? attachRes : createRes)
|
||||
)
|
||||
)
|
||||
.subscribe(
|
||||
(res: any) => {
|
||||
this.success.emit(res);
|
||||
},
|
||||
(err) => {
|
||||
this.error.emit(err);
|
||||
this.logService.error('An error occurred while creating new task');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private attachForm(taskId: string, formKey: number): Observable<any> {
|
||||
let response = Observable.of();
|
||||
if (taskId && formKey) {
|
||||
response = this.taskService.attachFormToATask(taskId, formKey);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private assignTaskByUserId(taskId: string, userId: any): Observable<any> {
|
||||
let response = Observable.of();
|
||||
if (taskId && userId) {
|
||||
response = this.taskService.assignTaskByUserId(taskId, userId);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
public onCancel(): void {
|
||||
this.cancel.emit();
|
||||
}
|
||||
|
||||
private loadFormsTask(): void {
|
||||
this.taskService.getFormList().subscribe((res: Form[]) => {
|
||||
this.forms = res;
|
||||
},
|
||||
(err) => {
|
||||
this.error.emit(err);
|
||||
this.logService.error('An error occurred while trying to get the forms');
|
||||
});
|
||||
}
|
||||
|
||||
private getUsers(): void {
|
||||
this.peopleService.getWorkflowUsers().subscribe((users) => {
|
||||
this.people = users;
|
||||
}, (err) => {
|
||||
this.error.emit(err);
|
||||
this.logService.error('Could not load users');
|
||||
});
|
||||
}
|
||||
|
||||
public isUserNameEmpty(user: UserProcessModel): boolean {
|
||||
return !user || (this.isEmpty(user.firstName) && this.isEmpty(user.lastName));
|
||||
}
|
||||
|
||||
private isEmpty(data: string): boolean {
|
||||
return data === undefined || data === null || data.trim().length === 0;
|
||||
}
|
||||
|
||||
public getDisplayUser(firstName: string, lastName: string, delimiter: string = '-'): string {
|
||||
firstName = (firstName !== null ? firstName : '');
|
||||
lastName = (lastName !== null ? lastName : '');
|
||||
return firstName + delimiter + lastName;
|
||||
}
|
||||
|
||||
onDateChanged(newDateValue): void {
|
||||
this.dateError = false;
|
||||
|
||||
if (newDateValue) {
|
||||
let momentDate = moment(newDateValue, this.FORMAT_DATE, true);
|
||||
if (!momentDate.isValid()) {
|
||||
this.dateError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import {
|
||||
async,
|
||||
ComponentFixture,
|
||||
fakeAsync,
|
||||
TestBed
|
||||
} from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
import { TaskAuditDirective } from './task-audit.directive';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('TaskAuditDirective', () => {
|
||||
|
||||
let fixture: ComponentFixture<BasicButtonComponent>;
|
||||
let component: BasicButtonComponent;
|
||||
let service: TaskListService;
|
||||
|
||||
function createFakePdfBlob(): Blob {
|
||||
let pdfData = atob(
|
||||
'JVBERi0xLjcKCjEgMCBvYmogICUgZW50cnkgcG9pbnQKPDwKICAvVHlwZSAvQ2F0YWxvZwog' +
|
||||
'IC9QYWdlcyAyIDAgUgo+PgplbmRvYmoKCjIgMCBvYmoKPDwKICAvVHlwZSAvUGFnZXMKICAv' +
|
||||
'TWVkaWFCb3ggWyAwIDAgMjAwIDIwMCBdCiAgL0NvdW50IDEKICAvS2lkcyBbIDMgMCBSIF0K' +
|
||||
'Pj4KZW5kb2JqCgozIDAgb2JqCjw8CiAgL1R5cGUgL1BhZ2UKICAvUGFyZW50IDIgMCBSCiAg' +
|
||||
'L1Jlc291cmNlcyA8PAogICAgL0ZvbnQgPDwKICAgICAgL0YxIDQgMCBSIAogICAgPj4KICA+' +
|
||||
'PgogIC9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKCjQgMCBvYmoKPDwKICAvVHlwZSAvRm9u' +
|
||||
'dAogIC9TdWJ0eXBlIC9UeXBlMQogIC9CYXNlRm9udCAvVGltZXMtUm9tYW4KPj4KZW5kb2Jq' +
|
||||
'Cgo1IDAgb2JqICAlIHBhZ2UgY29udGVudAo8PAogIC9MZW5ndGggNDQKPj4Kc3RyZWFtCkJU' +
|
||||
'CjcwIDUwIFRECi9GMSAxMiBUZgooSGVsbG8sIHdvcmxkISkgVGoKRVQKZW5kc3RyZWFtCmVu' +
|
||||
'ZG9iagoKeHJlZgowIDYKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDEwIDAwMDAwIG4g' +
|
||||
'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' +
|
||||
'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' +
|
||||
'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G');
|
||||
return new Blob([pdfData], {type: 'application/pdf'});
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [BasicButtonComponent, TaskAuditDirective],
|
||||
providers: [TaskListService]
|
||||
});
|
||||
|
||||
TestBed.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(BasicButtonComponent);
|
||||
component = fixture.componentInstance;
|
||||
service = TestBed.get(TaskListService);
|
||||
|
||||
jasmine.Ajax.install();
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should fetch the pdf Blob when the format is pdf', fakeAsync(() => {
|
||||
component.fileName = 'FakeAuditName';
|
||||
component.format = 'pdf';
|
||||
let blob = createFakePdfBlob();
|
||||
spyOn(service, 'fetchTaskAuditPdfById').and.returnValue(Observable.of(blob));
|
||||
spyOn(component, 'onAuditClick').and.callThrough();
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
let button = fixture.nativeElement.querySelector('#auditButton');
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.onAuditClick).toHaveBeenCalledWith({ format: 'pdf', value: blob, fileName: 'FakeAuditName' });
|
||||
});
|
||||
|
||||
button.click();
|
||||
|
||||
}));
|
||||
|
||||
it('should fetch the json info when the format is json', fakeAsync(() => {
|
||||
component.fileName = 'FakeAuditName';
|
||||
component.format = 'json';
|
||||
component.download = true;
|
||||
const auditJson = { taskId: '77', taskName: 'Fake Task Naem', assignee: 'FirstName LastName', formData: [], selectedOutcome: null, comments: [] };
|
||||
spyOn(service, 'fetchTaskAuditJsonById').and.returnValue(Observable.of(auditJson));
|
||||
spyOn(component, 'onAuditClick').and.callThrough();
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
let button = fixture.nativeElement.querySelector('#auditButton');
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.onAuditClick).toHaveBeenCalledWith({ format: 'json', value: auditJson, fileName: 'FakeAuditName' });
|
||||
});
|
||||
|
||||
button.click();
|
||||
|
||||
}));
|
||||
|
||||
it('should fetch the pdf Blob as default when the format is UNKNOW', fakeAsync(() => {
|
||||
component.fileName = 'FakeAuditName';
|
||||
component.format = 'fakeFormat';
|
||||
let blob = createFakePdfBlob();
|
||||
spyOn(service, 'fetchTaskAuditPdfById').and.returnValue(Observable.of(blob));
|
||||
spyOn(component, 'onAuditClick').and.callThrough();
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
let button = fixture.nativeElement.querySelector('#auditButton');
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(component.onAuditClick).toHaveBeenCalledWith({ format: 'pdf', value: blob, fileName: 'FakeAuditName' });
|
||||
});
|
||||
|
||||
button.click();
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
@Component({
|
||||
selector: 'adf-basic-button',
|
||||
template: `
|
||||
<button id="auditButton"
|
||||
adf-task-audit
|
||||
[task-id]="currentTaskId"
|
||||
[download]="download"
|
||||
[fileName]="fileName"
|
||||
[format]="format"
|
||||
(clicked)="onAuditClick($event)">My button
|
||||
</button>`
|
||||
})
|
||||
class BasicButtonComponent {
|
||||
|
||||
download: boolean = false;
|
||||
fileName: string;
|
||||
format: string;
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
onAuditClick(event: any) {
|
||||
}
|
||||
}
|
@@ -0,0 +1,120 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ContentService } from '@alfresco/core';
|
||||
import { Directive, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
|
||||
const JSON_FORMAT: string = 'json';
|
||||
const PDF_FORMAT: string = 'pdf';
|
||||
|
||||
@Directive({
|
||||
selector: 'button[adf-task-audit]',
|
||||
host: {
|
||||
'role': 'button',
|
||||
'(click)': 'onClickAudit()'
|
||||
}
|
||||
})
|
||||
export class TaskAuditDirective implements OnChanges {
|
||||
|
||||
@Input('task-id')
|
||||
taskId: string;
|
||||
|
||||
@Input()
|
||||
fileName: string = 'Audit';
|
||||
|
||||
@Input()
|
||||
format: string = 'pdf';
|
||||
|
||||
@Input()
|
||||
download: boolean = true;
|
||||
|
||||
@Output()
|
||||
clicked: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
public audit: any;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param translateService
|
||||
* @param taskListService
|
||||
*/
|
||||
constructor(private contentService: ContentService,
|
||||
private taskListService: TaskListService) {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (!this.isValidType()) {
|
||||
this.setDefaultFormatType();
|
||||
}
|
||||
}
|
||||
|
||||
isValidType() {
|
||||
if (this.format && (this.isJsonFormat() || this.isPdfFormat())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setDefaultFormatType(): void {
|
||||
this.format = PDF_FORMAT;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the audit information in the requested format
|
||||
*/
|
||||
fetchAuditInfo(): void {
|
||||
if (this.isPdfFormat()) {
|
||||
this.taskListService.fetchTaskAuditPdfById(this.taskId).subscribe(
|
||||
(blob: Blob) => {
|
||||
this.audit = blob;
|
||||
if (this.download) {
|
||||
this.contentService.downloadBlob(this.audit, this.fileName + '.pdf');
|
||||
}
|
||||
this.clicked.emit({ format: this.format, value: this.audit, fileName: this.fileName });
|
||||
},
|
||||
(err) => {
|
||||
this.error.emit(err);
|
||||
});
|
||||
} else {
|
||||
this.taskListService.fetchTaskAuditJsonById(this.taskId).subscribe(
|
||||
(res) => {
|
||||
this.audit = res;
|
||||
this.clicked.emit({ format: this.format, value: this.audit, fileName: this.fileName });
|
||||
},
|
||||
(err) => {
|
||||
this.error.emit(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onClickAudit() {
|
||||
this.fetchAuditInfo();
|
||||
}
|
||||
|
||||
isJsonFormat() {
|
||||
return this.format === JSON_FORMAT;
|
||||
}
|
||||
|
||||
isPdfFormat() {
|
||||
return this.format === PDF_FORMAT;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
<div *ngIf="!taskDetails">
|
||||
<ng-template *ngIf="noTaskDetailsTemplateComponent" ngFor [ngForOf]="[data]"
|
||||
[ngForTemplate]="noTaskDetailsTemplateComponent">
|
||||
{{ 'ADF_TASK_LIST.DETAILS.MESSAGES.NONE' | translate }}
|
||||
</ng-template>
|
||||
<div *ngIf="!noTaskDetailsTemplateComponent">
|
||||
{{ 'ADF_TASK_LIST.DETAILS.MESSAGES.NONE' | translate }}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="taskDetails" class="adf-task-details">
|
||||
|
||||
<div *ngIf="showHeader" class="adf-task-details-header">
|
||||
<h2 class="activiti-task-details__header" (click)="toggleHeaderContent()">
|
||||
<span>{{taskDetails.name || 'No name'}}</span>
|
||||
</h2>
|
||||
<div matRipple [matRippleUnbounded]="true" [matRippleCentered]="true" class="adf-task-details-header-toggle">
|
||||
<mat-icon *ngIf="!showHeaderContent" (click)="toggleHeaderContent()">web</mat-icon>
|
||||
<mat-icon *ngIf="showHeaderContent" (click)="toggleHeaderContent()">web_asset</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="adf-task-details-core"
|
||||
fxLayout
|
||||
fxLayout.lt-lg="column"
|
||||
fxLayoutGap.lt-lg="0px">
|
||||
|
||||
<div class="adf-task-details-core-form">
|
||||
<div *ngIf="isAssigned()">
|
||||
<adf-form *ngIf="hasFormKey()" #activitiForm
|
||||
[showDebugButton]="debugMode"
|
||||
[taskId]="taskDetails.id"
|
||||
[showTitle]="showFormTitle"
|
||||
[showRefreshButton]="showFormRefreshButton"
|
||||
[showCompleteButton]="showFormCompleteButton"
|
||||
[disableCompleteButton]="!isAssignedToMe()"
|
||||
[showSaveButton]="showFormSaveButton"
|
||||
[readOnly]="readOnlyForm"
|
||||
[fieldValidators]="fieldValidators"
|
||||
(formSaved)='onFormSaved($event)'
|
||||
(formCompleted)='onFormCompleted($event)'
|
||||
(formContentClicked)='onFormContentClick($event)'
|
||||
(formLoaded)='onFormLoaded($event)'
|
||||
(error)='onFormError($event)'
|
||||
(executeOutcome)='onFormExecuteOutcome($event)'>
|
||||
<div empty-form><h3 class="adf-task-title">Please select a Task</h3></div>
|
||||
</adf-form>
|
||||
</div>
|
||||
<div *ngIf="!isAssigned()">
|
||||
{{ 'ADF_TASK_LIST.DETAILS.MESSAGES.CLAIM' | translate }}
|
||||
</div>
|
||||
<button mat-raised-button class="activiti-task-details__action-button"
|
||||
*ngIf="!hasFormKey() && isTaskActive()" (click)="onComplete()">
|
||||
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.COMPLETE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="adf-task-details-core-sidebar">
|
||||
<adf-info-drawer *ngIf="showHeaderContent" title="{{ 'ADF_TASK_LIST.DETAILS.LABELS.INFO_DRAWER_TITLE' | translate }}" class="adf-task-details-core-sidebar-drawer">
|
||||
<div info-drawer-buttons>
|
||||
<mat-icon (click)="toggleHeaderContent()">clear</mat-icon>
|
||||
</div>
|
||||
|
||||
<adf-info-drawer-tab label="{{ 'ADF_TASK_LIST.DETAILS.LABELS.INFO_DRAWER_TAB_DETAILS_TITLE' | translate }}">
|
||||
<div class="assignment-container" *ngIf="showAssignee">
|
||||
<adf-people-search
|
||||
(searchPeople)="searchUser($event)"
|
||||
(success)="assignTaskToUser($event)"
|
||||
(closeSearch)="onCloseSearch()"
|
||||
[results]="peopleSearch$">
|
||||
<ng-container people-search-title>{{ 'ADF_TASK_LIST.DETAILS.LABELS.ADD_ASSIGNEE' | translate }}
|
||||
</ng-container>
|
||||
<ng-container people-search-action-label>{{ 'ADF_TASK_LIST.PEOPLE.ADD_ASSIGNEE' | translate }}
|
||||
</ng-container>
|
||||
</adf-people-search>
|
||||
</div>
|
||||
<adf-task-header
|
||||
[class]="getTaskHeaderViewClass()"
|
||||
[taskDetails]="taskDetails"
|
||||
[formName]="taskFormName"
|
||||
(claim)="onClaimAction($event)"
|
||||
(unclaim)="onClaimAction($event)">
|
||||
</adf-task-header>
|
||||
<adf-people *ngIf="showInvolvePeople" #people
|
||||
[people]="taskPeople"
|
||||
[readOnly]="readOnlyForm"
|
||||
[taskId]="taskDetails.id">
|
||||
</adf-people>
|
||||
</adf-info-drawer-tab>
|
||||
|
||||
<adf-info-drawer-tab label="{{ 'ADF_TASK_LIST.DETAILS.LABELS.INFO_DRAWER_TAB_ACTIVITY_TITLE' | translate }}">
|
||||
<mat-card *ngIf="showComments">
|
||||
<mat-card-content>
|
||||
<adf-comments #activiticomments
|
||||
[readOnly]="isReadOnlyComment()"
|
||||
[taskId]="taskDetails.id">
|
||||
</adf-comments>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</adf-info-drawer-tab>
|
||||
|
||||
</adf-info-drawer>
|
||||
|
||||
<div *ngIf="showHeaderContent" class="adf-task-details-core-sidebar-checklist">
|
||||
<div *ngIf="showChecklist">
|
||||
<adf-checklist #activitichecklist
|
||||
[readOnly]="readOnlyForm"
|
||||
[taskId]="taskDetails.id"
|
||||
[assignee]="taskDetails?.assignee?.id"
|
||||
(checklistTaskCreated)="onChecklistTaskCreated($event)"
|
||||
(checklistTaskDeleted)="onChecklistTaskDeleted($event)">
|
||||
</adf-checklist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #errorDialog>
|
||||
<h3 matDialogTitle>{{'ADF_TASK_LIST.DETAILS.ERROR.TITLE'|translate}}</h3>
|
||||
<mat-dialog-content>
|
||||
<p>{{'ADF_TASK_LIST.DETAILS.ERROR.DESCRIPTION'|translate}}</p>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button type="button" (click)="closeErrorDialog()">{{'ADF_TASK_LIST.DETAILS.ERROR.CLOSE'|translate}}
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</ng-template>
|
||||
|
||||
</div>
|
@@ -0,0 +1,105 @@
|
||||
:host {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error-dialog h3 {
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.activiti-task-details__header {
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
line-height: normal;
|
||||
overflow: hidden;
|
||||
margin: 8px 0 16px 0;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.activiti-task-details__action-button {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
adf-people ::ng-deep .assignment-top-container{
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.assignment-container {
|
||||
background: #fff;
|
||||
border: 1px solid #e1e1e1;
|
||||
padding: 10px 20px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
adf-task-header.assign-edit-view ::ng-deep adf-card-view ::ng-deep .adf-property[data-automation-id="header-assignee"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.adf-task-details {
|
||||
&-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&-toggle {
|
||||
position: relative;
|
||||
top: 10px;
|
||||
margin-right: 2px;
|
||||
height: 23px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-core {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&-sidebar {
|
||||
&-drawer {
|
||||
margin-left: 10px;
|
||||
|
||||
@media screen and (max-width: 1279px) {
|
||||
margin-left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&-checklist {
|
||||
margin-top: 30px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&-form {
|
||||
flex-grow: 1;
|
||||
|
||||
& ::ng-deep .adf-form-debug-container {
|
||||
padding: 20px 0 0 0;
|
||||
|
||||
.mat-slide-toggle {
|
||||
float: right;
|
||||
|
||||
& + div {
|
||||
background-color: black;
|
||||
padding: 20px;
|
||||
clear: both;
|
||||
margin-top: 30px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& ::ng-deep .mat-tab-label {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,424 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatButtonModule, MatInputModule } from '@angular/material';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
import { FormModule, FormModel, FormOutcomeEvent, FormOutcomeModel, FormService } from '@alfresco/core';
|
||||
import { CommentProcessService, LogService } from '@alfresco/core';
|
||||
|
||||
import { PeopleProcessService, UserProcessModel } from '@alfresco/core';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { noDataMock, taskDetailsMock, taskFormMock, tasksMock } from '../../mock';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
import { PeopleSearchComponent } from '../../people';
|
||||
import { TaskDetailsComponent } from './task-details.component';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const fakeUser: UserProcessModel = new UserProcessModel({
|
||||
id: 'fake-id',
|
||||
firstName: 'fake-name',
|
||||
lastName: 'fake-last',
|
||||
email: 'fake@mail.com'
|
||||
});
|
||||
|
||||
describe('TaskDetailsComponent', () => {
|
||||
|
||||
let service: TaskListService;
|
||||
let formService: FormService;
|
||||
let component: TaskDetailsComponent;
|
||||
let fixture: ComponentFixture<TaskDetailsComponent>;
|
||||
let getTaskDetailsSpy: jasmine.Spy;
|
||||
let getFormSpy: jasmine.Spy;
|
||||
let getTasksSpy: jasmine.Spy;
|
||||
let assignTaskSpy: jasmine.Spy;
|
||||
let getFormTaskSpy: jasmine.Spy;
|
||||
let completeTaskSpy: jasmine.Spy;
|
||||
let logService: LogService;
|
||||
let commentProcessService: CommentProcessService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
FormModule,
|
||||
MatButtonModule,
|
||||
MatInputModule
|
||||
],
|
||||
declarations: [
|
||||
TaskDetailsComponent,
|
||||
PeopleSearchComponent
|
||||
],
|
||||
providers: [
|
||||
TaskListService,
|
||||
PeopleProcessService,
|
||||
CommentProcessService
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
||||
logService = TestBed.get(LogService);
|
||||
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
fixture = TestBed.createComponent(TaskDetailsComponent);
|
||||
component = fixture.componentInstance;
|
||||
service = fixture.debugElement.injector.get(TaskListService);
|
||||
formService = fixture.debugElement.injector.get(FormService);
|
||||
commentProcessService = TestBed.get(CommentProcessService);
|
||||
|
||||
getTaskDetailsSpy = spyOn(service, 'getTaskDetails').and.returnValue(Observable.of(taskDetailsMock));
|
||||
getFormSpy = spyOn(formService, 'getTaskForm').and.returnValue(Observable.of(taskFormMock));
|
||||
taskDetailsMock.processDefinitionId = null;
|
||||
getFormTaskSpy = spyOn(formService, 'getTask').and.returnValue(Observable.of(taskDetailsMock));
|
||||
|
||||
getTasksSpy = spyOn(service, 'getTasks').and.returnValue(Observable.of(tasksMock));
|
||||
assignTaskSpy = spyOn(service, 'assignTask').and.returnValue(Observable.of(fakeUser));
|
||||
completeTaskSpy = spyOn(service, 'completeTask').and.returnValue(Observable.of({}));
|
||||
spyOn(commentProcessService, 'getTaskComments').and.returnValue(Observable.of(noDataMock));
|
||||
spyOn(service, 'getTaskChecklist').and.returnValue(Observable.of(noDataMock));
|
||||
|
||||
});
|
||||
|
||||
it('should load task details when taskId specified', () => {
|
||||
component.taskId = '123';
|
||||
fixture.detectChanges();
|
||||
expect(getTaskDetailsSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not load task details when no taskId is specified', () => {
|
||||
fixture.detectChanges();
|
||||
expect(getTaskDetailsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set a placeholder message when taskId not initialised', () => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.MESSAGES.NONE');
|
||||
});
|
||||
|
||||
it('shoud display a form when the task has an associated form', () => {
|
||||
component.taskId = '123';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('adf-form'))).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not display a form when the task does not have an associated form', async(() => {
|
||||
component.taskId = '123';
|
||||
taskDetailsMock.formKey = undefined;
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.query(By.css('adf-form'))).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('change detection', () => {
|
||||
|
||||
let change = new SimpleChange('123', '456', true);
|
||||
let nullChange = new SimpleChange('123', null, true);
|
||||
|
||||
beforeEach(async(() => {
|
||||
component.taskId = '123';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
getTaskDetailsSpy.calls.reset();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should fetch new task details when taskId changed', () => {
|
||||
component.ngOnChanges({'taskId': change});
|
||||
expect(getTaskDetailsSpy).toHaveBeenCalledWith('456');
|
||||
});
|
||||
|
||||
it('should NOT fetch new task details when empty changeset made', () => {
|
||||
component.ngOnChanges({});
|
||||
expect(getTaskDetailsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT fetch new task details when taskId changed to null', () => {
|
||||
component.ngOnChanges({'taskId': nullChange});
|
||||
expect(getTaskDetailsSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set a placeholder message when taskId changed to null', () => {
|
||||
component.ngOnChanges({'taskId': nullChange});
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.MESSAGES.NONE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Form events', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
component.taskId = '123';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable();
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
const overlayContainers = <any> window.document.querySelectorAll('.cdk-overlay-container');
|
||||
|
||||
overlayContainers.forEach((overlayContainer) => {
|
||||
overlayContainer.innerHTML = '';
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit a save event when form saved', () => {
|
||||
let emitSpy: jasmine.Spy = spyOn(component.formSaved, 'emit');
|
||||
component.onFormSaved(new FormModel());
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit a outcome execution event when form outcome executed', () => {
|
||||
let emitSpy: jasmine.Spy = spyOn(component.executeOutcome, 'emit');
|
||||
component.onFormExecuteOutcome(new FormOutcomeEvent(new FormOutcomeModel(new FormModel())));
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit a complete event when form completed', () => {
|
||||
let emitSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
|
||||
component.onFormCompleted(new FormModel());
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should load next task when form completed', () => {
|
||||
component.onComplete();
|
||||
expect(getTasksSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should show placeholder message if there is no next task', () => {
|
||||
getTasksSpy.and.returnValue(Observable.of(noDataMock));
|
||||
component.onComplete();
|
||||
fixture.detectChanges();
|
||||
expect(fixture.nativeElement.innerText).toBe('ADF_TASK_LIST.DETAILS.MESSAGES.NONE');
|
||||
});
|
||||
|
||||
it('should emit an error event if an error occurs fetching the next task', () => {
|
||||
let emitSpy: jasmine.Spy = spyOn(component.error, 'emit');
|
||||
getTasksSpy.and.returnValue(Observable.throw({}));
|
||||
component.onComplete();
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT load next task when form completed if showNextTask is false', () => {
|
||||
component.showNextTask = false;
|
||||
component.onComplete();
|
||||
expect(getTasksSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call service to complete task when complete button clicked', () => {
|
||||
component.onComplete();
|
||||
expect(completeTaskSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit a complete event when complete button clicked and task completed', () => {
|
||||
let emitSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit');
|
||||
component.onComplete();
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call service to load next task when complete button clicked', () => {
|
||||
component.onComplete();
|
||||
expect(getTasksSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit a load event when form loaded', () => {
|
||||
let emitSpy: jasmine.Spy = spyOn(component.formLoaded, 'emit');
|
||||
component.onFormLoaded(new FormModel());
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should emit an error event when form error occurs', () => {
|
||||
let emitSpy: jasmine.Spy = spyOn(component.error, 'emit');
|
||||
component.onFormError({});
|
||||
expect(emitSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should display a dialog to the user when a form error occurs', () => {
|
||||
let dialogEl = window.document.querySelector('mat-dialog-content');
|
||||
expect(dialogEl).toBeNull();
|
||||
|
||||
component.onFormError({});
|
||||
fixture.detectChanges();
|
||||
|
||||
dialogEl = window.document.querySelector('mat-dialog-content');
|
||||
expect(dialogEl).not.toBeNull();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Comments', () => {
|
||||
|
||||
it('should comments be readonly if the task is complete and no user are involved', () => {
|
||||
component.showComments = true;
|
||||
component.showHeaderContent = true;
|
||||
component.ngOnChanges({'taskId': new SimpleChange('123', '456', true)});
|
||||
component.taskPeople = [];
|
||||
component.taskDetails = new TaskDetailsModel(taskDetailsMock);
|
||||
component.taskDetails.endDate = new Date('2017-10-03T17:03:57.311+0000');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect((component.activiticomments as any).nativeElement.readOnly).toBe(true);
|
||||
});
|
||||
|
||||
it('should comments be readonly if the task is complete and user are NOT involved', () => {
|
||||
component.showComments = true;
|
||||
component.showHeaderContent = true;
|
||||
component.ngOnChanges({'taskId': new SimpleChange('123', '456', true)});
|
||||
component.taskPeople = [];
|
||||
component.taskDetails = new TaskDetailsModel(taskDetailsMock);
|
||||
component.taskDetails.endDate = new Date('2017-10-03T17:03:57.311+0000');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect((component.activiticomments as any).nativeElement.readOnly).toBe(true);
|
||||
});
|
||||
|
||||
it('should comments NOT be readonly if the task is NOT complete and user are NOT involved', () => {
|
||||
component.showComments = true;
|
||||
component.showHeaderContent = true;
|
||||
component.ngOnChanges({'taskId': new SimpleChange('123', '456', true)});
|
||||
component.taskPeople = [fakeUser];
|
||||
component.taskDetails = new TaskDetailsModel(taskDetailsMock);
|
||||
component.taskDetails.endDate = null;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect((component.activiticomments as any).nativeElement.readOnly).toBe(false);
|
||||
});
|
||||
|
||||
it('should comments NOT be readonly if the task is complete and user are involved', () => {
|
||||
component.showComments = true;
|
||||
component.showHeaderContent = true;
|
||||
component.ngOnChanges({'taskId': new SimpleChange('123', '456', true)});
|
||||
component.taskPeople = [fakeUser];
|
||||
component.taskDetails = new TaskDetailsModel(taskDetailsMock);
|
||||
component.taskDetails.endDate = new Date('2017-10-03T17:03:57.311+0000');
|
||||
|
||||
fixture.detectChanges();
|
||||
expect((component.activiticomments as any).nativeElement.readOnly).toBe(false);
|
||||
});
|
||||
|
||||
it('should comments be present if showComments is true', () => {
|
||||
component.showComments = true;
|
||||
component.showHeaderContent = true;
|
||||
component.ngOnChanges({'taskId': new SimpleChange('123', '456', true)});
|
||||
component.taskPeople = [];
|
||||
component.taskDetails = new TaskDetailsModel(taskDetailsMock);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(component.activiticomments).toBeDefined();
|
||||
});
|
||||
|
||||
it('should comments NOT be present if showComments is false', () => {
|
||||
component.showComments = false;
|
||||
component.ngOnChanges({'taskId': new SimpleChange('123', '456', true)});
|
||||
component.taskPeople = [];
|
||||
component.taskDetails = new TaskDetailsModel(taskDetailsMock);
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(component.activiticomments).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('assign task to user', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.taskId = '123';
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should return an observable with user search results', (done) => {
|
||||
component.peopleSearch$.subscribe((users) => {
|
||||
expect(users.length).toBe(2);
|
||||
expect(users[0].firstName).toBe('fake-test-1');
|
||||
expect(users[0].lastName).toBe('fake-last-1');
|
||||
expect(users[0].email).toBe('fake-test-1@test.com');
|
||||
expect(users[0].id).toBe(1);
|
||||
done();
|
||||
});
|
||||
component.searchUser('fake-search-word');
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {
|
||||
data: [{
|
||||
id: 1,
|
||||
firstName: 'fake-test-1',
|
||||
lastName: 'fake-last-1',
|
||||
email: 'fake-test-1@test.com'
|
||||
}, {
|
||||
id: 2,
|
||||
firstName: 'fake-test-2',
|
||||
lastName: 'fake-last-2',
|
||||
email: 'fake-test-2@test.com'
|
||||
}]
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an empty list for not valid search', (done) => {
|
||||
component.peopleSearch$.subscribe((users) => {
|
||||
expect(users.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
component.searchUser('fake-search-word');
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {}
|
||||
});
|
||||
});
|
||||
|
||||
it('should log error message when search fails', async(() => {
|
||||
component.peopleSearch$.subscribe(() => {
|
||||
expect(logService.error).toHaveBeenCalledWith('Could not load users');
|
||||
});
|
||||
component.searchUser('fake-search');
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
}));
|
||||
|
||||
it('should assign task to user', () => {
|
||||
component.assignTaskToUser(fakeUser);
|
||||
expect(assignTaskSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,365 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { PeopleProcessService, UserProcessModel } from '@alfresco/core';
|
||||
import { AuthenticationService, CardViewUpdateService, ClickNotification, LogService, UpdateNotification } from '@alfresco/core';
|
||||
import { Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
TemplateRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { MatDialog, MatDialogRef } from '@angular/material';
|
||||
import { Observable, Observer } from 'rxjs/Rx';
|
||||
import { ContentLinkModel, FormFieldValidator, FormModel, FormOutcomeEvent } from '@alfresco/core';
|
||||
import { TaskQueryRequestRepresentationModel } from '../models/filter.model';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
import { CommentsComponent } from '../../comments';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-task-details',
|
||||
templateUrl: './task-details.component.html',
|
||||
styleUrls: ['./task-details.component.scss'],
|
||||
providers: [
|
||||
CardViewUpdateService
|
||||
]
|
||||
})
|
||||
export class TaskDetailsComponent implements OnInit, OnChanges {
|
||||
|
||||
@ViewChild('activiticomments')
|
||||
activiticomments: CommentsComponent;
|
||||
|
||||
@ViewChild('activitichecklist')
|
||||
activitichecklist: any;
|
||||
|
||||
@ViewChild('errorDialog')
|
||||
errorDialog: TemplateRef<any>;
|
||||
|
||||
@Input()
|
||||
debugMode: boolean = false;
|
||||
|
||||
@Input()
|
||||
taskId: string;
|
||||
|
||||
@Input()
|
||||
showNextTask: boolean = true;
|
||||
|
||||
@Input()
|
||||
showHeader: boolean = true;
|
||||
|
||||
@Input()
|
||||
showHeaderContent: boolean = true;
|
||||
|
||||
@Input()
|
||||
showInvolvePeople: boolean = true;
|
||||
|
||||
@Input()
|
||||
showComments: boolean = true;
|
||||
|
||||
@Input()
|
||||
showChecklist: boolean = true;
|
||||
|
||||
@Input()
|
||||
showFormTitle: boolean = true;
|
||||
|
||||
@Input()
|
||||
showFormCompleteButton: boolean = true;
|
||||
|
||||
@Input()
|
||||
showFormSaveButton: boolean = true;
|
||||
|
||||
@Input()
|
||||
readOnlyForm: boolean = false;
|
||||
|
||||
@Input()
|
||||
showFormRefreshButton: boolean = true;
|
||||
|
||||
@Input()
|
||||
fieldValidators: FormFieldValidator[] = [];
|
||||
|
||||
@Output()
|
||||
formSaved: EventEmitter<FormModel> = new EventEmitter<FormModel>();
|
||||
|
||||
@Output()
|
||||
formCompleted: EventEmitter<FormModel> = new EventEmitter<FormModel>();
|
||||
|
||||
@Output()
|
||||
formContentClicked: EventEmitter<ContentLinkModel> = new EventEmitter<ContentLinkModel>();
|
||||
|
||||
@Output()
|
||||
formLoaded: EventEmitter<FormModel> = new EventEmitter<FormModel>();
|
||||
|
||||
@Output()
|
||||
taskCreated: EventEmitter<TaskDetailsModel> = new EventEmitter<TaskDetailsModel>();
|
||||
|
||||
@Output()
|
||||
taskDeleted: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
executeOutcome: EventEmitter<FormOutcomeEvent> = new EventEmitter<FormOutcomeEvent>();
|
||||
|
||||
@Output()
|
||||
assignTask: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
taskDetails: TaskDetailsModel;
|
||||
taskFormName: string = null;
|
||||
|
||||
taskPeople: UserProcessModel[] = [];
|
||||
|
||||
noTaskDetailsTemplateComponent: TemplateRef<any>;
|
||||
|
||||
showAssignee: boolean = false;
|
||||
|
||||
private peopleSearchObserver: Observer<UserProcessModel[]>;
|
||||
public errorDialogRef: MatDialogRef<TemplateRef<any>>;
|
||||
|
||||
peopleSearch$: Observable<UserProcessModel[]>;
|
||||
|
||||
constructor(private taskListService: TaskListService,
|
||||
private authService: AuthenticationService,
|
||||
private peopleProcessService: PeopleProcessService,
|
||||
private logService: LogService,
|
||||
private cardViewUpdateService: CardViewUpdateService,
|
||||
private dialog: MatDialog) {
|
||||
this.peopleSearch$ = new Observable<UserProcessModel[]>(observer => this.peopleSearchObserver = observer).share();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.taskId) {
|
||||
this.loadDetails(this.taskId);
|
||||
}
|
||||
|
||||
this.cardViewUpdateService.itemUpdated$.subscribe(this.updateTaskDetails.bind(this));
|
||||
this.cardViewUpdateService.itemClicked$.subscribe(this.clickTaskDetails.bind(this));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
let taskId = changes.taskId;
|
||||
this.showAssignee = false;
|
||||
|
||||
if (taskId && !taskId.currentValue) {
|
||||
this.reset();
|
||||
} else if (taskId && taskId.currentValue) {
|
||||
this.taskFormName = null;
|
||||
this.loadDetails(taskId.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the task details
|
||||
*/
|
||||
private reset() {
|
||||
this.taskDetails = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the task has a form
|
||||
* @returns {TaskDetailsModel|string|boolean}
|
||||
*/
|
||||
hasFormKey() {
|
||||
return (this.taskDetails
|
||||
&& this.taskDetails.formKey
|
||||
&& this.taskDetails.formKey !== 'null');
|
||||
}
|
||||
|
||||
isTaskActive() {
|
||||
return this.taskDetails && this.taskDetails.duration === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a task detail and update it after a successful response
|
||||
*
|
||||
* @param updateNotification
|
||||
*/
|
||||
private updateTaskDetails(updateNotification: UpdateNotification) {
|
||||
this.taskListService.updateTask(this.taskId, updateNotification.changed)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.loadDetails(this.taskId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private clickTaskDetails(clickNotification: ClickNotification) {
|
||||
if (clickNotification.target.key === 'assignee') {
|
||||
this.showAssignee = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the activiti task details
|
||||
* @param taskId
|
||||
*/
|
||||
private loadDetails(taskId: string) {
|
||||
this.taskPeople = [];
|
||||
this.readOnlyForm = false;
|
||||
|
||||
if (taskId) {
|
||||
this.taskListService.getTaskDetails(taskId).subscribe(
|
||||
(res: TaskDetailsModel) => {
|
||||
this.taskDetails = res;
|
||||
|
||||
if (this.taskDetails.name === 'null') {
|
||||
this.taskDetails.name = 'No name';
|
||||
}
|
||||
|
||||
let endDate: any = res.endDate;
|
||||
this.readOnlyForm = this.readOnlyForm ? this.readOnlyForm : !!(endDate && !isNaN(endDate.getTime()));
|
||||
if (this.taskDetails && this.taskDetails.involvedPeople) {
|
||||
this.taskDetails.involvedPeople.forEach((user) => {
|
||||
this.taskPeople.push(new UserProcessModel(user));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isAssigned(): boolean {
|
||||
return this.taskDetails.assignee ? true : false;
|
||||
}
|
||||
|
||||
isAssignedToMe(): boolean {
|
||||
return this.taskDetails.assignee.email === this.authService.getBpmUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next open task
|
||||
* @param processInstanceId
|
||||
* @param processDefinitionId
|
||||
*/
|
||||
private loadNextTask(processInstanceId: string, processDefinitionId: string): void {
|
||||
let requestNode = new TaskQueryRequestRepresentationModel(
|
||||
{
|
||||
processInstanceId: processInstanceId,
|
||||
processDefinitionId: processDefinitionId
|
||||
}
|
||||
);
|
||||
this.taskListService.getTasks(requestNode).subscribe(
|
||||
(response) => {
|
||||
if (response && response.length > 0) {
|
||||
this.taskDetails = new TaskDetailsModel(response[0]);
|
||||
} else {
|
||||
this.reset();
|
||||
}
|
||||
}, (error) => {
|
||||
this.error.emit(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete button clicked
|
||||
*/
|
||||
onComplete(): void {
|
||||
this.taskListService.completeTask(this.taskId).subscribe(
|
||||
(res) => this.onFormCompleted(null)
|
||||
);
|
||||
}
|
||||
|
||||
onFormContentClick(content: ContentLinkModel): void {
|
||||
this.formContentClicked.emit(content);
|
||||
}
|
||||
|
||||
onFormSaved(form: FormModel): void {
|
||||
this.formSaved.emit(form);
|
||||
}
|
||||
|
||||
onFormCompleted(form: FormModel): void {
|
||||
this.formCompleted.emit(form);
|
||||
if (this.showNextTask && (this.taskDetails.processInstanceId || this.taskDetails.processDefinitionId)) {
|
||||
this.loadNextTask(this.taskDetails.processInstanceId, this.taskDetails.processDefinitionId);
|
||||
}
|
||||
}
|
||||
|
||||
onFormLoaded(form: FormModel): void {
|
||||
this.taskFormName = (form && form.name ? form.name : null);
|
||||
this.formLoaded.emit(form);
|
||||
}
|
||||
|
||||
onChecklistTaskCreated(task: TaskDetailsModel): void {
|
||||
this.taskCreated.emit(task);
|
||||
}
|
||||
|
||||
onChecklistTaskDeleted(taskId: string): void {
|
||||
this.taskDeleted.emit(taskId);
|
||||
}
|
||||
|
||||
onFormError(error: any): void {
|
||||
this.errorDialogRef = this.dialog.open(this.errorDialog, {width: '500px'});
|
||||
this.error.emit(error);
|
||||
}
|
||||
|
||||
onFormExecuteOutcome(event: FormOutcomeEvent): void {
|
||||
this.executeOutcome.emit(event);
|
||||
}
|
||||
|
||||
closeErrorDialog(): void {
|
||||
this.dialog.closeAll();
|
||||
}
|
||||
|
||||
onClaimAction(taskId: string): void {
|
||||
this.loadDetails(taskId);
|
||||
}
|
||||
|
||||
toggleHeaderContent(): void {
|
||||
this.showHeaderContent = !this.showHeaderContent;
|
||||
}
|
||||
|
||||
isCompletedTask(): boolean {
|
||||
return this.taskDetails && this.taskDetails.endDate ? true : undefined;
|
||||
}
|
||||
|
||||
searchUser(searchedWord: string) {
|
||||
this.peopleProcessService.getWorkflowUsers(null, searchedWord)
|
||||
.subscribe((users) => {
|
||||
users = users.filter((user) => user.id !== this.taskDetails.assignee.id);
|
||||
this.peopleSearchObserver.next(users);
|
||||
}, error => this.logService.error('Could not load users'));
|
||||
}
|
||||
|
||||
onCloseSearch() {
|
||||
this.showAssignee = false;
|
||||
}
|
||||
|
||||
assignTaskToUser(selectedUser: UserProcessModel) {
|
||||
this.taskListService.assignTask(this.taskDetails.id, selectedUser).subscribe(
|
||||
(res: any) => {
|
||||
this.logService.info('Task Assigned to ' + selectedUser.email);
|
||||
this.assignTask.emit();
|
||||
});
|
||||
this.showAssignee = false;
|
||||
}
|
||||
|
||||
getTaskHeaderViewClass(): string {
|
||||
if (this.showAssignee) {
|
||||
return 'assign-edit-view';
|
||||
} else {
|
||||
return 'default-view';
|
||||
}
|
||||
}
|
||||
|
||||
isReadOnlyComment(): boolean {
|
||||
return (this.taskDetails && this.taskDetails.isCompleted()) && (this.taskPeople && this.taskPeople.length === 0);
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
<div class="menu-container">
|
||||
<mat-list>
|
||||
<mat-list-item (click)="selectFilter(filter)" *ngFor="let filter of filters"
|
||||
class="adf-filters__entry" [class.active]="currentFilter === filter">
|
||||
<mat-icon *ngIf="hasIcon" matListIcon class="adf-filters__entry-icon">assignment</mat-icon>
|
||||
<span matLine [attr.data-automation-id]="filter.name + '_filter'">{{filter.name}}</span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
@@ -0,0 +1,31 @@
|
||||
@mixin adf-task-list-filters-task-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
|
||||
.adf {
|
||||
|
||||
&-filters__entry {
|
||||
cursor: pointer;
|
||||
font-size: 14px!important;
|
||||
font-weight: bold;
|
||||
opacity: .54;
|
||||
padding-left: 30px;
|
||||
|
||||
.mat-list-item-content {
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
&-filters__entry-icon {
|
||||
padding-right: 12px !important;
|
||||
padding-left: 0px !important;
|
||||
}
|
||||
|
||||
&-filters__entry {
|
||||
&.active, &:hover {
|
||||
color: mat-color($primary);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,320 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AppConfigService, AppsProcessService } from '@alfresco/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { FilterParamsModel, FilterRepresentationModel } from '../models/filter.model';
|
||||
import { TaskListService } from '../services/tasklist.service';
|
||||
import { TaskFilterService } from '../services/task-filter.service';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { TaskFiltersComponent } from './task-filters.component';
|
||||
|
||||
describe('TaskFiltersComponent', () => {
|
||||
|
||||
let taskListService: TaskListService;
|
||||
let taskFilterService: TaskFilterService;
|
||||
let appsProcessService: AppsProcessService;
|
||||
|
||||
let fakeGlobalFilter = [];
|
||||
fakeGlobalFilter.push(new FilterRepresentationModel({
|
||||
name: 'FakeInvolvedTasks',
|
||||
id: 10,
|
||||
filter: { state: 'open', assignment: 'fake-involved' }
|
||||
}));
|
||||
fakeGlobalFilter.push(new FilterRepresentationModel({
|
||||
name: 'FakeMyTasks1',
|
||||
id: 11,
|
||||
filter: { state: 'open', assignment: 'fake-assignee' }
|
||||
}));
|
||||
fakeGlobalFilter.push(new FilterRepresentationModel({
|
||||
name: 'FakeMyTasks2',
|
||||
id: 12,
|
||||
filter: { state: 'open', assignment: 'fake-assignee' }
|
||||
}));
|
||||
|
||||
let fakeGlobalFilterPromise = new Promise(function (resolve, reject) {
|
||||
resolve(fakeGlobalFilter);
|
||||
});
|
||||
|
||||
let fakeErrorFilterList = {
|
||||
error: 'wrong request'
|
||||
};
|
||||
|
||||
let fakeErrorFilterPromise = new Promise(function (resolve, reject) {
|
||||
reject(fakeErrorFilterList);
|
||||
});
|
||||
|
||||
let component: TaskFiltersComponent;
|
||||
let fixture: ComponentFixture<TaskFiltersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
TaskFiltersComponent
|
||||
],
|
||||
providers: [
|
||||
TaskListService,
|
||||
TaskFilterService
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
let appConfig: AppConfigService = TestBed.get(AppConfigService);
|
||||
appConfig.config.bpmHost = 'http://localhost:9876/bpm';
|
||||
|
||||
fixture = TestBed.createComponent(TaskFiltersComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
taskListService = TestBed.get(TaskListService);
|
||||
taskFilterService = TestBed.get(TaskFilterService);
|
||||
appsProcessService = TestBed.get(AppsProcessService);
|
||||
});
|
||||
|
||||
it('should emit an error with a bad response', (done) => {
|
||||
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeErrorFilterPromise));
|
||||
|
||||
const appId = '1';
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
component.ngOnChanges({ 'appId': change });
|
||||
|
||||
component.error.subscribe((err) => {
|
||||
expect(err).toBeDefined();
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should return the filter task list', (done) => {
|
||||
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
|
||||
const appId = '1';
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
component.ngOnChanges({ 'appId': change });
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.filters).toBeDefined();
|
||||
expect(component.filters.length).toEqual(3);
|
||||
expect(component.filters[0].name).toEqual('FakeInvolvedTasks');
|
||||
expect(component.filters[1].name).toEqual('FakeMyTasks1');
|
||||
expect(component.filters[2].name).toEqual('FakeMyTasks2');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnInit();
|
||||
});
|
||||
|
||||
it('should return the filter task list, filtered By Name', (done) => {
|
||||
|
||||
let fakeDeployedApplicationsPromise = new Promise(function (resolve, reject) {
|
||||
resolve({});
|
||||
});
|
||||
|
||||
spyOn(appsProcessService, 'getDeployedApplicationsByName').and.returnValue(Observable.fromPromise(fakeDeployedApplicationsPromise));
|
||||
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
|
||||
|
||||
let change = new SimpleChange(null, 'test', true);
|
||||
component.ngOnChanges({ 'appName': change });
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
let deployApp: any = appsProcessService.getDeployedApplicationsByName;
|
||||
expect(deployApp.calls.count()).toEqual(1);
|
||||
expect(res).toBeDefined();
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnInit();
|
||||
});
|
||||
|
||||
it('should select the first filter as default', (done) => {
|
||||
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
|
||||
|
||||
const appId = '1';
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({ 'appId': change });
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.currentFilter).toBeDefined();
|
||||
expect(component.currentFilter.name).toEqual('FakeInvolvedTasks');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should select the task filter based on the input by name param', (done) => {
|
||||
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
|
||||
|
||||
component.filterParam = new FilterParamsModel({name: 'FakeMyTasks1'});
|
||||
const appId = '1';
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({ 'appId': change });
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.currentFilter).toBeDefined();
|
||||
expect(component.currentFilter.name).toEqual('FakeMyTasks1');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should select the default task filter if filter input does not exist', (done) => {
|
||||
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
|
||||
|
||||
component.filterParam = new FilterParamsModel({name: 'UnexistableFilter'});
|
||||
|
||||
const appId = '1';
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({ 'appId': change });
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.currentFilter).toBeDefined();
|
||||
expect(component.currentFilter.name).toEqual('FakeInvolvedTasks');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should select the task filter based on the input by index param', (done) => {
|
||||
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
|
||||
|
||||
component.filterParam = new FilterParamsModel({index: 2});
|
||||
|
||||
const appId = '1';
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({ 'appId': change });
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.currentFilter).toBeDefined();
|
||||
expect(component.currentFilter.name).toEqual('FakeMyTasks2');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should select the task filter based on the input by id param', (done) => {
|
||||
spyOn(taskFilterService, 'getTaskListFilters').and.returnValue(Observable.fromPromise(fakeGlobalFilterPromise));
|
||||
|
||||
component.filterParam = new FilterParamsModel({id: 10});
|
||||
|
||||
const appId = '1';
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({ 'appId': change });
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.currentFilter).toBeDefined();
|
||||
expect(component.currentFilter.name).toEqual('FakeInvolvedTasks');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should emit an event when a filter is selected', (done) => {
|
||||
let currentFilter = new FilterRepresentationModel({ filter: { state: 'open', assignment: 'fake-involved' } });
|
||||
|
||||
component.filterClick.subscribe((filter: FilterRepresentationModel) => {
|
||||
expect(filter).toBeDefined();
|
||||
expect(filter).toEqual(currentFilter);
|
||||
expect(component.currentFilter).toEqual(currentFilter);
|
||||
done();
|
||||
});
|
||||
|
||||
component.selectFilter(currentFilter);
|
||||
});
|
||||
|
||||
it('should reload filters by appId on binding changes', () => {
|
||||
spyOn(component, 'getFiltersByAppId').and.stub();
|
||||
const appId = '1';
|
||||
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
component.ngOnChanges({ 'appId': change });
|
||||
|
||||
expect(component.getFiltersByAppId).toHaveBeenCalledWith(appId);
|
||||
});
|
||||
|
||||
it('should reload filters by appId null on binding changes', () => {
|
||||
spyOn(component, 'getFiltersByAppId').and.stub();
|
||||
const appId = null;
|
||||
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
component.ngOnChanges({ 'appId': change });
|
||||
|
||||
expect(component.getFiltersByAppId).toHaveBeenCalledWith(appId);
|
||||
});
|
||||
|
||||
it('should reload filters by app name on binding changes', () => {
|
||||
spyOn(component, 'getFiltersByAppName').and.stub();
|
||||
const appName = 'fake-app-name';
|
||||
|
||||
let change = new SimpleChange(null, appName, true);
|
||||
component.ngOnChanges({ 'appName': change });
|
||||
|
||||
expect(component.getFiltersByAppName).toHaveBeenCalledWith(appName);
|
||||
});
|
||||
|
||||
it('should return the current filter after one is selected', () => {
|
||||
let filter = new FilterRepresentationModel({
|
||||
name: 'FakeMyTasks',
|
||||
filter: { state: 'open', assignment: 'fake-assignee' }
|
||||
});
|
||||
expect(component.currentFilter).toBeUndefined();
|
||||
component.selectFilter(filter);
|
||||
expect(component.getCurrentFilter()).toBe(filter);
|
||||
});
|
||||
|
||||
it('should load Default list when no appid or taskid are provided', () => {
|
||||
spyOn(component, 'getFiltersByAppId').and.stub();
|
||||
|
||||
let change = new SimpleChange(null, null, true);
|
||||
component.ngOnChanges({ 'appName': change });
|
||||
|
||||
expect(component.getFiltersByAppId).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not change the current filter if no filter with taskid is found', async(() => {
|
||||
let filter = new FilterRepresentationModel({
|
||||
name: 'FakeMyTasks',
|
||||
filter: { state: 'open', assignment: 'fake-assignee' }
|
||||
});
|
||||
component.filters = fakeGlobalFilter;
|
||||
component.currentFilter = filter;
|
||||
spyOn(taskListService, 'isTaskRelatedToFilter').and.returnValue(Observable.of(null));
|
||||
component.selectFilterWithTask('111');
|
||||
|
||||
expect(component.currentFilter).toBe(filter);
|
||||
}));
|
||||
});
|
@@ -0,0 +1,230 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AppsProcessService } from '@alfresco/core';
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||
import { Observable, Observer } from 'rxjs/Rx';
|
||||
import { FilterParamsModel, FilterRepresentationModel } from '../models/filter.model';
|
||||
import { TaskFilterService } from './../services/task-filter.service';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-filters, taskListService-filters',
|
||||
templateUrl: './task-filters.component.html',
|
||||
styleUrls: ['task-filters.component.scss']
|
||||
})
|
||||
export class TaskFiltersComponent implements OnInit, OnChanges {
|
||||
|
||||
@Input()
|
||||
filterParam: FilterParamsModel;
|
||||
|
||||
@Output()
|
||||
filterClick: EventEmitter<FilterRepresentationModel> = new EventEmitter<FilterRepresentationModel>();
|
||||
|
||||
@Output()
|
||||
success: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Input()
|
||||
appId: number;
|
||||
|
||||
@Input()
|
||||
appName: string;
|
||||
|
||||
@Input()
|
||||
hasIcon: boolean = true;
|
||||
|
||||
private filterObserver: Observer<FilterRepresentationModel>;
|
||||
filter$: Observable<FilterRepresentationModel>;
|
||||
|
||||
currentFilter: FilterRepresentationModel;
|
||||
|
||||
filters: FilterRepresentationModel [] = [];
|
||||
|
||||
constructor(private taskFilterService: TaskFilterService, private taskListService: TaskListService, private appsProcessService: AppsProcessService) {
|
||||
this.filter$ = new Observable<FilterRepresentationModel>(observer => this.filterObserver = observer).share();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.filter$.subscribe((filter: FilterRepresentationModel) => {
|
||||
this.filters.push(filter);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
let appId = changes['appId'];
|
||||
if (appId && (appId.currentValue || appId.currentValue === null)) {
|
||||
this.getFiltersByAppId(appId.currentValue);
|
||||
return;
|
||||
}
|
||||
let appName = changes['appName'];
|
||||
if (appName && appName !== null && appName.currentValue) {
|
||||
this.getFiltersByAppName(appName.currentValue);
|
||||
return;
|
||||
}
|
||||
|
||||
this.getFiltersByAppId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the task list filtered by appId or by appName
|
||||
* @param appId
|
||||
* @param appName
|
||||
*/
|
||||
getFilters(appId?: number, appName?: string) {
|
||||
if (appName) {
|
||||
this.getFiltersByAppName(appName);
|
||||
} else {
|
||||
this.getFiltersByAppId(appId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filter list filtered by appId
|
||||
* @param appId - optional
|
||||
*/
|
||||
getFiltersByAppId(appId?: number) {
|
||||
this.taskFilterService.getTaskListFilters(appId).subscribe(
|
||||
(res: FilterRepresentationModel[]) => {
|
||||
if (res.length === 0 && this.isFilterListEmpty()) {
|
||||
this.taskFilterService.createDefaultFilters(appId).subscribe(
|
||||
(resDefault: FilterRepresentationModel[]) => {
|
||||
this.resetFilter();
|
||||
resDefault.forEach((filter) => {
|
||||
this.filterObserver.next(filter);
|
||||
});
|
||||
|
||||
this.selectTaskFilter(this.filterParam, this.filters);
|
||||
this.success.emit(resDefault);
|
||||
},
|
||||
(errDefault: any) => {
|
||||
this.error.emit(errDefault);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.resetFilter();
|
||||
res.forEach((filter) => {
|
||||
this.filterObserver.next(filter);
|
||||
});
|
||||
|
||||
this.selectTaskFilter(this.filterParam, this.filters);
|
||||
this.success.emit(res);
|
||||
}
|
||||
},
|
||||
(err: any) => {
|
||||
this.error.emit(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filter list filtered by appName
|
||||
* @param appName
|
||||
*/
|
||||
getFiltersByAppName(appName: string) {
|
||||
this.appsProcessService.getDeployedApplicationsByName(appName).subscribe(
|
||||
application => {
|
||||
this.getFiltersByAppId(application.id);
|
||||
this.selectTaskFilter(this.filterParam, this.filters);
|
||||
},
|
||||
(err) => {
|
||||
this.error.emit(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass the selected filter as next
|
||||
* @param filter
|
||||
*/
|
||||
public selectFilter(filter: FilterRepresentationModel) {
|
||||
this.currentFilter = filter;
|
||||
this.filterClick.emit(filter);
|
||||
}
|
||||
|
||||
public selectFilterWithTask(taskId: string) {
|
||||
let filteredFilterList: FilterRepresentationModel[] = [];
|
||||
this.taskListService.getFilterForTaskById(taskId, this.filters).subscribe(
|
||||
(filter: FilterRepresentationModel) => {
|
||||
filteredFilterList.push(filter);
|
||||
},
|
||||
(err) => {
|
||||
this.error.emit(err);
|
||||
},
|
||||
() => {
|
||||
if (filteredFilterList.length > 0) {
|
||||
this.selectTaskFilter(new FilterParamsModel({name: 'My Tasks'}), filteredFilterList);
|
||||
this.filterClick.emit(this.currentFilter);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the first filter of a list if present
|
||||
*/
|
||||
public selectTaskFilter(filterParam: FilterParamsModel, filteredFilterList: FilterRepresentationModel[]) {
|
||||
let findTaskFilter;
|
||||
if (filterParam) {
|
||||
filteredFilterList.filter((taskFilter: FilterRepresentationModel, index) => {
|
||||
if (filterParam.name && filterParam.name.toLowerCase() === taskFilter.name.toLowerCase() ||
|
||||
filterParam.id === taskFilter.id.toString()
|
||||
|| filterParam.index === index) {
|
||||
findTaskFilter = taskFilter;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (findTaskFilter) {
|
||||
this.currentFilter = findTaskFilter;
|
||||
} else {
|
||||
this.selectDefaultTaskFilter(filteredFilterList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select as default task filter the first in the list
|
||||
*/
|
||||
public selectDefaultTaskFilter(filteredFilterList: FilterRepresentationModel[]) {
|
||||
if (!this.isFilterListEmpty()) {
|
||||
this.currentFilter = this.filters[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current task
|
||||
* @returns {FilterRepresentationModel}
|
||||
*/
|
||||
getCurrentFilter(): FilterRepresentationModel {
|
||||
return this.currentFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the filter list is empty
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFilterListEmpty(): boolean {
|
||||
return this.filters === undefined || (this.filters && this.filters.length === 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the filters properties
|
||||
*/
|
||||
private resetFilter() {
|
||||
this.filters = [];
|
||||
this.currentFilter = undefined;
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
<mat-card *ngIf="taskDetails" class="adf-card-container">
|
||||
<mat-card-content>
|
||||
<adf-card-view [properties]="properties" [editable]="!isCompleted()"></adf-card-view>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions class="adf-controls">
|
||||
<button *ngIf="isTaskClaimedByCurrentUser()" mat-button data-automation-id="header-unclaim-button" id="unclaim-task" (click)="unclaimTask(taskDetails.id)" class="adf-claim-controls">{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}
|
||||
</button>
|
||||
<button *ngIf="isTaskClaimable()" mat-button data-automation-id="header-claim-button" id="claim-task" (click)="claimTask(taskDetails.id)" class="adf-claim-controls">{{ 'ADF_TASK_LIST.DETAILS.BUTTON.CLAIM' | translate }}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
@@ -0,0 +1,30 @@
|
||||
@mixin adf-task-list-header-theme($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
|
||||
.adf {
|
||||
&-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-edit-controls {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&-switch-to-edit-mode,
|
||||
&-save-edit-mode {
|
||||
color: mat-color($primary);
|
||||
}
|
||||
|
||||
&-cancel-edit-mode,
|
||||
&-claim-controls {
|
||||
color: rgb(131, 131, 131);
|
||||
}
|
||||
|
||||
&-card-container {
|
||||
font-family: inherit;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,276 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { CardViewUpdateService, UserProcessModel } from '@alfresco/core';
|
||||
import { BpmUserService } from '@alfresco/core';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import {
|
||||
completedTaskDetailsMock,
|
||||
taskDetailsMock,
|
||||
taskDetailsWithAssigneeMock,
|
||||
taskDetailsWithInvolvedGroupMock,
|
||||
taskDetailsWithInvolvedPeopleMock,
|
||||
taskDetailsWithOutAssigneeMock } from '../../mock';
|
||||
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
import { TaskHeaderComponent } from './task-header.component';
|
||||
|
||||
describe('TaskHeaderComponent', () => {
|
||||
|
||||
let service: TaskListService;
|
||||
let component: TaskHeaderComponent;
|
||||
let fixture: ComponentFixture<TaskHeaderComponent>;
|
||||
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({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
TaskHeaderComponent
|
||||
],
|
||||
providers: [
|
||||
TaskListService,
|
||||
BpmUserService,
|
||||
CardViewUpdateService
|
||||
]
|
||||
}).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);
|
||||
});
|
||||
|
||||
it('should render empty component if no task details provided', () => {
|
||||
component.taskDetails = undefined;
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.children.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should display assignee', () => {
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-property-value'));
|
||||
expect(formNameEl.nativeElement.innerText).toBe('Wilbur Adams');
|
||||
});
|
||||
|
||||
it('should display placeholder if no assignee', () => {
|
||||
component.taskDetails.assignee = null;
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-assignee"] .adf-property-value'));
|
||||
expect(valueEl.nativeElement.innerText).toBe('ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT');
|
||||
});
|
||||
|
||||
it('should display created-by', () => {
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let formNameEl = fixture.debugElement.query(By.css('[data-automation-id="header-created-by"] .adf-property-value'));
|
||||
expect(formNameEl.nativeElement.innerText).toBe('Wilbur Adams');
|
||||
});
|
||||
|
||||
it('should set editable to false if the task has already completed', () => {
|
||||
component.taskDetails.endDate = new Date('05/05/2002');
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
|
||||
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-dueDate"]`));
|
||||
expect(datePicker).toBeNull('Datepicker should NOT be in DOM');
|
||||
});
|
||||
|
||||
it('should set editable to true if the task has not completed yet', () => {
|
||||
component.taskDetails.endDate = undefined;
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
|
||||
let datePicker = fixture.debugElement.query(By.css(`[data-automation-id="datepicker-dueDate"]`));
|
||||
expect(datePicker).not.toBeNull('Datepicker should be in DOM');
|
||||
});
|
||||
|
||||
describe('Claiming', () => {
|
||||
|
||||
it('should display the claim button if no assignee', () => {
|
||||
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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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' });
|
||||
component.taskDetails.assignee = batman;
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let unclaimButton = fixture.debugElement.query(By.css('[data-automation-id="header-unclaim-button"]'));
|
||||
expect(unclaimButton).toBeNull();
|
||||
});
|
||||
|
||||
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 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(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', {});
|
||||
|
||||
expect(service.unclaimTask).toHaveBeenCalledWith('91');
|
||||
});
|
||||
|
||||
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', {});
|
||||
|
||||
expect(unclaimed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display due date', () => {
|
||||
component.taskDetails.dueDate = new Date('2016-11-03');
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-property-value'));
|
||||
expect(valueEl.nativeElement.innerText.trim()).toBe('Nov 03 2016');
|
||||
});
|
||||
|
||||
it('should display placeholder if no due date', () => {
|
||||
component.taskDetails.dueDate = null;
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-property-value'));
|
||||
expect(valueEl.nativeElement.innerText.trim()).toBe('ADF_TASK_LIST.PROPERTIES.DUE_DATE_DEFAULT');
|
||||
});
|
||||
|
||||
it('should display form name', () => {
|
||||
component.formName = 'test form';
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-formName"] .adf-property-value'));
|
||||
expect(valueEl.nativeElement.innerText).toBe('test form');
|
||||
});
|
||||
|
||||
it('should display the default parent value if is undefined', () => {
|
||||
component.taskDetails.processInstanceId = null;
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-parentName"] .adf-property-value'));
|
||||
expect(valueEl.nativeElement.innerText.trim()).toEqual('ADF_TASK_LIST.PROPERTIES.PARENT_NAME_DEFAULT');
|
||||
});
|
||||
|
||||
it('should display the Parent name value', () => {
|
||||
component.taskDetails.processInstanceId = '1';
|
||||
component.taskDetails.processDefinitionName = 'Parent Name';
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-parentName"] .adf-property-value'));
|
||||
expect(valueEl.nativeElement.innerText.trim()).toEqual('Parent Name');
|
||||
});
|
||||
|
||||
it('should not display form name if no form name provided', () => {
|
||||
component.ngOnChanges({});
|
||||
fixture.detectChanges();
|
||||
let valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-formName"] .adf-property-value'));
|
||||
expect(valueEl.nativeElement.innerText).toBe('ADF_TASK_LIST.PROPERTIES.FORM_NAME_DEFAULT');
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,266 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||
|
||||
import { BpmUserService, CardViewDateItemModel, CardViewItem, CardViewMapItemModel, CardViewTextItemModel, LogService } from '@alfresco/core';
|
||||
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-task-header',
|
||||
templateUrl: './task-header.component.html',
|
||||
styleUrls: ['./task-header.component.scss']
|
||||
})
|
||||
export class TaskHeaderComponent implements OnChanges, OnInit {
|
||||
|
||||
@Input()
|
||||
formName: string = null;
|
||||
|
||||
@Input()
|
||||
taskDetails: TaskDetailsModel;
|
||||
|
||||
@Output()
|
||||
claim: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
unclaim: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the card data
|
||||
*/
|
||||
refreshData() {
|
||||
if (this.taskDetails) {
|
||||
const parentInfoMap = this.getParentInfo();
|
||||
this.properties = [
|
||||
new CardViewTextItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.ASSIGNEE',
|
||||
value: this.taskDetails.getFullName(),
|
||||
key: 'assignee',
|
||||
default: 'ADF_TASK_LIST.PROPERTIES.ASSIGNEE_DEFAULT',
|
||||
clickable: !this.isCompleted()
|
||||
}
|
||||
),
|
||||
new CardViewTextItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.STATUS',
|
||||
value: this.getTaskStatus(),
|
||||
key: 'status'
|
||||
}
|
||||
),
|
||||
new CardViewTextItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.PRIORITY',
|
||||
value: this.taskDetails.priority,
|
||||
key: 'priority'
|
||||
}
|
||||
),
|
||||
new CardViewDateItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.DUE_DATE',
|
||||
value: this.taskDetails.dueDate,
|
||||
key: 'dueDate',
|
||||
default: 'ADF_TASK_LIST.PROPERTIES.DUE_DATE_DEFAULT',
|
||||
editable: true
|
||||
}
|
||||
),
|
||||
new CardViewTextItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.CATEGORY',
|
||||
value: this.taskDetails.category,
|
||||
key: 'category',
|
||||
default: 'ADF_TASK_LIST.PROPERTIES.CATEGORY_DEFAULT'
|
||||
}
|
||||
),
|
||||
new CardViewMapItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.PARENT_NAME',
|
||||
value: parentInfoMap, key: 'parentName',
|
||||
default: 'ADF_TASK_LIST.PROPERTIES.PARENT_NAME_DEFAULT',
|
||||
clickable: true
|
||||
}
|
||||
),
|
||||
new CardViewTextItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.CREATED_BY',
|
||||
value: this.taskDetails.getFullName(),
|
||||
key: 'created-by'
|
||||
}
|
||||
),
|
||||
new CardViewDateItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.CREATED',
|
||||
value: this.taskDetails.created,
|
||||
key: 'created'
|
||||
}
|
||||
),
|
||||
new CardViewTextItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.ID',
|
||||
value: this.taskDetails.id,
|
||||
key: 'id'
|
||||
}
|
||||
),
|
||||
new CardViewTextItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.DESCRIPTION',
|
||||
value: this.taskDetails.description,
|
||||
key: 'description',
|
||||
default: 'ADF_TASK_LIST.PROPERTIES.DESCRIPTION_DEFAULT',
|
||||
multiline: true,
|
||||
editable: true
|
||||
}
|
||||
),
|
||||
new CardViewTextItemModel(
|
||||
{
|
||||
label: 'ADF_TASK_LIST.PROPERTIES.FORM_NAME',
|
||||
value: this.formName,
|
||||
key: 'formName',
|
||||
default: 'ADF_TASK_LIST.PROPERTIES.FORM_NAME_DEFAULT'
|
||||
}
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bpmUser
|
||||
*/
|
||||
private getCurrentUserId(): void {
|
||||
this.bpmUserService.getCurrentUserInfo().subscribe((res) => {
|
||||
this.currentUserId = res ? +res.id : null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the process parent information
|
||||
*/
|
||||
getParentInfo() {
|
||||
if (this.taskDetails.processInstanceId && this.taskDetails.processDefinitionName) {
|
||||
return new Map([[this.taskDetails.processInstanceId, this.taskDetails.processDefinitionName]]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the task have an assignee
|
||||
*/
|
||||
public hasAssignee(): boolean {
|
||||
return !!this.taskDetails.assignee ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the task is assigne to logged in user
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns task's status
|
||||
*/
|
||||
getTaskStatus(): string {
|
||||
return (this.taskDetails && this.taskDetails.isCompleted()) ? 'Completed' : 'Running';
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim task
|
||||
*
|
||||
* @param taskId
|
||||
*/
|
||||
claimTask(taskId: string) {
|
||||
this.activitiTaskService.claimTask(taskId).subscribe(
|
||||
(res: any) => {
|
||||
this.logService.info('Task claimed');
|
||||
this.claim.emit(taskId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unclaim task
|
||||
*
|
||||
* @param taskId
|
||||
*/
|
||||
unclaimTask(taskId: string) {
|
||||
this.activitiTaskService.unclaimTask(taskId).subscribe(
|
||||
(res: any) => {
|
||||
this.logService.info('Task unclaimed');
|
||||
this.unclaim.emit(taskId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the task is completed
|
||||
*/
|
||||
isCompleted(): boolean {
|
||||
return this.taskDetails && !!this.taskDetails.endDate;
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
alfresco-datatable >>> .column-header {
|
||||
color: #232323;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
alfresco-datatable >>> .data-cell {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
alfresco-datatable >>> .cell-value{
|
||||
width: 250px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis
|
||||
}
|
||||
|
||||
.adf-task-list-loading-margin {
|
||||
margin-left: calc((100% - 100px) / 2);
|
||||
margin-right: calc((100% - 100px) / 2);
|
||||
}
|
||||
|
||||
.no-content-message {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
opacity: 0.54;
|
||||
color: #000;
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
<div *ngIf="!requestNode">{{ 'ADF_TASK_LIST.FILTERS.MESSAGES.NONE' | translate }}</div>
|
||||
<div *ngIf="requestNode">
|
||||
<div>
|
||||
<adf-datatable
|
||||
[data]="data"
|
||||
[loading]="isLoading"
|
||||
[multiselect]="multiselect"
|
||||
[selectionMode]="selectionMode"
|
||||
(row-select)="onRowSelect($event)"
|
||||
(row-unselect)="onRowUnselect($event)"
|
||||
(rowClick)="onRowClick($event)"
|
||||
(row-keyup)="onRowKeyUp($event)">
|
||||
<loading-content-template>
|
||||
<ng-template>
|
||||
<!--Add your custom loading template here-->
|
||||
<mat-progress-spinner
|
||||
class="adf-task-list-loading-margin"
|
||||
[color]="'primary'"
|
||||
[mode]="'indeterminate'">
|
||||
</mat-progress-spinner>
|
||||
</ng-template>
|
||||
</loading-content-template>
|
||||
<no-content-template>
|
||||
<!--Add your custom empty template here-->
|
||||
<ng-template>
|
||||
<div class="no-content-message">
|
||||
{{ 'ADF_TASK_LIST.LIST.MESSAGES.NONE' | translate }}
|
||||
</div>
|
||||
</ng-template>
|
||||
</no-content-template>
|
||||
</adf-datatable>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,632 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { AppConfigService } from '@alfresco/core';
|
||||
import { DataRowEvent, ObjectDataRow, ObjectDataTableAdapter } from '@alfresco/core';
|
||||
import { TaskListService } from '../services/tasklist.service';
|
||||
import { TaskListComponent } from './task-list.component';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('TaskListComponent', () => {
|
||||
|
||||
let fakeGlobalTask = {
|
||||
size: 2,
|
||||
start: 0,
|
||||
total: 2,
|
||||
data: [
|
||||
{
|
||||
id: 14, name: 'nameFake1',
|
||||
description: 'descriptionFake1',
|
||||
category: 'categoryFake1',
|
||||
assignee: {
|
||||
id: 2, firstName: 'firstNameFake1', lastName: 'lastNameFake1', email: 'emailFake1'
|
||||
},
|
||||
created: '2017-03-01T12:25:17.189+0000',
|
||||
dueDate: '2017-04-02T12:25:17.189+0000',
|
||||
endDate: '2017-05-03T12:25:31.129+0000',
|
||||
duration: 13940,
|
||||
priority: 50,
|
||||
parentTaskId: 1,
|
||||
parentTaskName: 'parentTaskNameFake',
|
||||
processInstanceId: 2511,
|
||||
processInstanceName: 'processInstanceNameFake',
|
||||
processDefinitionId: 'myprocess:1:4',
|
||||
processDefinitionName: 'processDefinitionNameFake',
|
||||
processDefinitionDescription: 'processDefinitionDescriptionFake',
|
||||
processDefinitionKey: 'myprocess',
|
||||
processDefinitionCategory: 'http://www.activiti.org/processdef',
|
||||
processDefinitionVersion: 1,
|
||||
processDefinitionDeploymentId: '1',
|
||||
formKey: 1,
|
||||
processInstanceStartUserId: null,
|
||||
initiatorCanCompleteTask: false,
|
||||
adhocTaskCanBeReassigned: false,
|
||||
taskDefinitionKey: 'sid-B6813AF5-8ACD-4481-A4D5-8BAAD1CB1416',
|
||||
executionId: 2511,
|
||||
memberOfCandidateGroup: false,
|
||||
memberOfCandidateUsers: false,
|
||||
managerOfCandidateGroup: false
|
||||
},
|
||||
|
||||
{
|
||||
id: 2, name: '', description: 'descriptionFake2', category: null,
|
||||
assignee: {
|
||||
id: 1, firstName: 'fistNameFake2', lastName: 'Administrator2', email: 'admin'
|
||||
},
|
||||
created: '2017-03-01T12:25:17.189+0000',
|
||||
dueDate: '2017-04-02T12:25:17.189+0000',
|
||||
endDate: null
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let fakeErrorTaskList = {
|
||||
error: 'wrong request'
|
||||
};
|
||||
|
||||
let fakeCutomSchema = [
|
||||
{
|
||||
'key': 'fakeName',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.FAKE',
|
||||
'sortable': true
|
||||
},
|
||||
{
|
||||
'key': 'fakeTaskName',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.TASK_FAKE',
|
||||
'sortable': true
|
||||
}
|
||||
];
|
||||
|
||||
let fakeColumnSchema = {
|
||||
'default': [
|
||||
{
|
||||
'key': 'name',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.NAME',
|
||||
'sortable': true
|
||||
},
|
||||
{
|
||||
'key': 'created',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.CREATED',
|
||||
'cssClass': 'hidden',
|
||||
'sortable': true
|
||||
},
|
||||
{
|
||||
'key': 'assignee',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.ASSIGNEE',
|
||||
'cssClass': 'hidden',
|
||||
'sortable': true
|
||||
}
|
||||
]
|
||||
, fakeCutomSchema };
|
||||
|
||||
let component: TaskListComponent;
|
||||
let fixture: ComponentFixture<TaskListComponent>;
|
||||
let taskListService: TaskListService;
|
||||
let appConfig: AppConfigService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
TaskListComponent
|
||||
],
|
||||
providers: [
|
||||
TaskListService
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
appConfig = TestBed.get(AppConfigService);
|
||||
appConfig.config.bpmHost = 'http://localhost:9876/bpm';
|
||||
|
||||
fixture = TestBed.createComponent(TaskListComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
taskListService = TestBed.get(TaskListService);
|
||||
appConfig.config = Object.assign(appConfig.config, {
|
||||
'adf-task-list': {
|
||||
'presets': {
|
||||
'fakeCutomSchema': [
|
||||
{
|
||||
'key': 'fakeName',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.FAKE',
|
||||
'sortable': true
|
||||
},
|
||||
{
|
||||
'key': 'fakeTaskName',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.TASK_FAKE',
|
||||
'sortable': true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should use the default schemaColumn as default', () => {
|
||||
component.ngAfterContentInit();
|
||||
expect(component.data.getColumns()).toBeDefined();
|
||||
expect(component.data.getColumns().length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should use the schemaColumn passed in input', () => {
|
||||
component.data = new ObjectDataTableAdapter(
|
||||
[],
|
||||
[
|
||||
{type: 'text', key: 'fake-id', title: 'Name'}
|
||||
]
|
||||
);
|
||||
|
||||
component.ngAfterContentInit();
|
||||
expect(component.data.getColumns()).toBeDefined();
|
||||
expect(component.data.getColumns().length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should use the custom schemaColumn from app.config.json', () => {
|
||||
component.ngAfterContentInit();
|
||||
fixture.detectChanges();
|
||||
expect(component.layoutPresets).toEqual(fakeColumnSchema);
|
||||
});
|
||||
|
||||
it('should fetch custom schemaColumn when the input presetColumn is defined', () => {
|
||||
component.presetColumn = 'fakeCutomColumns';
|
||||
fixture.detectChanges();
|
||||
expect(component.data.getColumns()).toBeDefined();
|
||||
expect(component.data.getColumns().length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should return an empty task list when no input parameters are passed', () => {
|
||||
component.ngAfterContentInit();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return the filtered task list when the input parameters are passed', (done) => {
|
||||
let state = new SimpleChange(null, 'open', true);
|
||||
let processDefinitionKey = new SimpleChange(null, null, true);
|
||||
let assignment = new SimpleChange(null, 'fake-assignee', true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[0].getValue('name')).toEqual('nameFake1');
|
||||
expect(component.data.getRows()[0].getValue('description')).toEqual('descriptionFake1');
|
||||
expect(component.data.getRows()[0].getValue('category')).toEqual('categoryFake1');
|
||||
expect(component.data.getRows()[0].getValue('assignee').id).toEqual(2);
|
||||
expect(component.data.getRows()[0].getValue('assignee').firstName).toEqual('firstNameFake1');
|
||||
expect(component.data.getRows()[0].getValue('assignee').lastName).toEqual('lastNameFake1');
|
||||
expect(component.data.getRows()[0].getValue('assignee').email).toEqual('emailFake1');
|
||||
expect(component.data.getRows()[0].getValue('created')).toEqual('2017-03-01T12:25:17.189+0000');
|
||||
expect(component.data.getRows()[0].getValue('dueDate')).toEqual('2017-04-02T12:25:17.189+0000');
|
||||
expect(component.data.getRows()[0].getValue('endDate')).toEqual('2017-05-03T12:25:31.129+0000');
|
||||
expect(component.data.getRows()[0].getValue('duration')).toEqual(13940);
|
||||
expect(component.data.getRows()[0].getValue('priority')).toEqual(50);
|
||||
expect(component.data.getRows()[0].getValue('parentTaskId')).toEqual(1);
|
||||
expect(component.data.getRows()[0].getValue('parentTaskName')).toEqual('parentTaskNameFake');
|
||||
expect(component.data.getRows()[0].getValue('processInstanceId')).toEqual(2511);
|
||||
expect(component.data.getRows()[0].getValue('processInstanceName')).toEqual('processInstanceNameFake');
|
||||
expect(component.data.getRows()[0].getValue('processDefinitionId')).toEqual('myprocess:1:4');
|
||||
expect(component.data.getRows()[0].getValue('processDefinitionName')).toEqual('processDefinitionNameFake');
|
||||
expect(component.data.getRows()[0].getValue('processDefinitionDescription')).toEqual('processDefinitionDescriptionFake');
|
||||
expect(component.data.getRows()[0].getValue('processDefinitionKey')).toEqual('myprocess');
|
||||
expect(component.data.getRows()[0].getValue('processDefinitionCategory')).toEqual('http://www.activiti.org/processdef');
|
||||
done();
|
||||
});
|
||||
component.ngAfterContentInit();
|
||||
component.ngOnChanges({'state': state, 'processDefinitionKey': processDefinitionKey, 'assignment': assignment});
|
||||
fixture.detectChanges();
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the filtered task list by processDefinitionKey', (done) => {
|
||||
let state = new SimpleChange(null, 'open', true);
|
||||
let processDefinitionKey = new SimpleChange(null, 'fakeprocess', true);
|
||||
let assignment = new SimpleChange(null, 'fake-assignee', true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[0].getValue('name')).toEqual('nameFake1');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngAfterContentInit();
|
||||
component.ngOnChanges({'state': state, 'processDefinitionKey': processDefinitionKey, 'assignment': assignment});
|
||||
fixture.detectChanges();
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the filtered task list by processInstanceId', (done) => {
|
||||
let state = new SimpleChange(null, 'open', true);
|
||||
let processInstanceId = new SimpleChange(null, 'fakeprocessId', true);
|
||||
let assignment = new SimpleChange(null, 'fake-assignee', true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[0].getValue('name')).toEqual('nameFake1');
|
||||
expect(component.data.getRows()[0].getValue('processInstanceId')).toEqual(2511);
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngAfterContentInit();
|
||||
component.ngOnChanges({'state': state, 'processInstanceId': processInstanceId, 'assignment': assignment});
|
||||
fixture.detectChanges();
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the filtered task list for all state', (done) => {
|
||||
let state = new SimpleChange(null, 'all', true);
|
||||
let processInstanceId = new SimpleChange(null, 'fakeprocessId', true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[0].getValue('name')).toEqual('nameFake1');
|
||||
expect(component.data.getRows()[0].getValue('processInstanceId')).toEqual(2511);
|
||||
expect(component.data.getRows()[0].getValue('endDate')).toBeDefined();
|
||||
expect(component.data.getRows()[1].getValue('name')).toEqual('No name');
|
||||
expect(component.data.getRows()[1].getValue('endDate')).toBeNull();
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngAfterContentInit();
|
||||
component.ngOnChanges({'state': state, 'processInstanceId': processInstanceId});
|
||||
fixture.detectChanges();
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a currentId null when the taskList is empty', () => {
|
||||
component.selectTask(null);
|
||||
expect(component.getCurrentId()).toBeNull();
|
||||
});
|
||||
|
||||
it('should return selected true for the selected task', () => {
|
||||
component.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{id: '999', name: 'Fake-name'},
|
||||
{id: '888', name: 'Fake-name-888'}
|
||||
],
|
||||
[
|
||||
{type: 'text', key: 'id', title: 'Id'},
|
||||
{type: 'text', key: 'name', title: 'Name'}
|
||||
]
|
||||
);
|
||||
component.selectTask('888');
|
||||
const dataRow = component.data.getRows();
|
||||
expect(dataRow).toBeDefined();
|
||||
expect(dataRow[0].getValue('id')).toEqual('999');
|
||||
expect(dataRow[0].isSelected).toEqual(false);
|
||||
expect(dataRow[1].getValue('id')).toEqual('888');
|
||||
expect(dataRow[1].isSelected).toEqual(true);
|
||||
});
|
||||
|
||||
xit('should throw an exception when the response is wrong', (done) => {
|
||||
let state = new SimpleChange(null, 'open', true);
|
||||
let assignment = new SimpleChange(null, 'fake-assignee', true);
|
||||
|
||||
component.error.subscribe((err) => {
|
||||
expect(err).toBeDefined();
|
||||
expect(err.error).toBe('wrong request');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngAfterContentInit();
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({'state': state, 'assignment': assignment});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 404,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeErrorTaskList)
|
||||
});
|
||||
});
|
||||
|
||||
it('should reload tasks when reload() is called', (done) => {
|
||||
component.state = 'open';
|
||||
component.assignment = 'fake-assignee';
|
||||
component.ngAfterContentInit();
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[0].getValue('name')).toEqual('nameFake1');
|
||||
done();
|
||||
});
|
||||
fixture.detectChanges();
|
||||
component.reload();
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit row click event', (done) => {
|
||||
let row = new ObjectDataRow({
|
||||
id: '999'
|
||||
});
|
||||
let rowEvent = new DataRowEvent(row, null);
|
||||
|
||||
component.rowClick.subscribe(taskId => {
|
||||
expect(taskId).toEqual('999');
|
||||
expect(component.getCurrentId()).toEqual('999');
|
||||
done();
|
||||
});
|
||||
|
||||
component.onRowClick(rowEvent);
|
||||
});
|
||||
|
||||
describe('component changes', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
component.data = new ObjectDataTableAdapter(
|
||||
[],
|
||||
[
|
||||
{type: 'text', key: 'fake-id', title: 'Name'}
|
||||
]
|
||||
);
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should NOT reload the tasks if the loadingTaskId is the same of the current task', () => {
|
||||
spyOn(component, 'reload').and.stub();
|
||||
component.currentInstanceId = '999';
|
||||
|
||||
component.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{id: '999', name: 'Fake-name'}
|
||||
],
|
||||
[
|
||||
{type: 'text', key: 'id', title: 'Id'},
|
||||
{type: 'text', key: 'name', title: 'Name'}
|
||||
]
|
||||
);
|
||||
|
||||
const landingTaskId = '999';
|
||||
let change = new SimpleChange(null, landingTaskId, true);
|
||||
component.ngOnChanges({'landingTaskId': change});
|
||||
expect(component.reload).not.toHaveBeenCalled();
|
||||
expect(component.data.getRows().length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should reload the tasks if the loadingTaskId is different from the current task', (done) => {
|
||||
component.currentInstanceId = '999';
|
||||
|
||||
component.data = new ObjectDataTableAdapter(
|
||||
[
|
||||
{id: '999', name: 'Fake-name'}
|
||||
],
|
||||
[
|
||||
{type: 'text', key: 'id', title: 'Id'},
|
||||
{type: 'text', key: 'name', title: 'Name'}
|
||||
]
|
||||
);
|
||||
|
||||
const landingTaskId = '888';
|
||||
let change = new SimpleChange(null, landingTaskId, true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({'landingTaskId': change});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT reload the process list when no parameters changed', () => {
|
||||
expect(component.isListEmpty()).toBeTruthy();
|
||||
component.ngOnChanges({});
|
||||
expect(component.isListEmpty()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should reload the list when the appId parameter changes', (done) => {
|
||||
const appId = '1';
|
||||
let change = new SimpleChange(null, appId, true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[1].getValue('name')).toEqual('No name');
|
||||
done();
|
||||
});
|
||||
component.ngOnChanges({'appId': change});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should reload the list when the processDefinitionKey parameter changes', (done) => {
|
||||
const processDefinitionKey = 'fakeprocess';
|
||||
let change = new SimpleChange(null, processDefinitionKey, true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[1].getValue('name')).toEqual('No name');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({'processDefinitionKey': change});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should reload the list when the state parameter changes', (done) => {
|
||||
const state = 'open';
|
||||
let change = new SimpleChange(null, state, true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[1].getValue('name')).toEqual('No name');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({'state': change});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should reload the list when the sort parameter changes', (done) => {
|
||||
const sort = 'desc';
|
||||
let change = new SimpleChange(null, sort, true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[1].getValue('name')).toEqual('No name');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({'sort': change});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should reload the process list when the name parameter changes', (done) => {
|
||||
const name = 'FakeTaskName';
|
||||
let change = new SimpleChange(null, name, true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[1].getValue('name')).toEqual('No name');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({'name': change});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
|
||||
it('should reload the list when the assignment parameter changes', (done) => {
|
||||
const assignment = 'assignee';
|
||||
let change = new SimpleChange(null, assignment, true);
|
||||
|
||||
component.success.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(component.data).toBeDefined();
|
||||
expect(component.isListEmpty()).not.toBeTruthy();
|
||||
expect(component.data.getRows().length).toEqual(2);
|
||||
expect(component.data.getRows()[1].getValue('name')).toEqual('No name');
|
||||
done();
|
||||
});
|
||||
|
||||
component.ngOnChanges({'assignment': change});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeGlobalTask)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
352
lib/process-services/task-list/components/task-list.component.ts
Normal file
352
lib/process-services/task-list/components/task-list.component.ts
Normal file
@@ -0,0 +1,352 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DataColumn, DataRowEvent, DataTableAdapter, ObjectDataColumn, ObjectDataRow, ObjectDataTableAdapter } from '@alfresco/core';
|
||||
import { AppConfigService, DataColumnListComponent } from '@alfresco/core';
|
||||
import { AfterContentInit, Component, ContentChild, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { TaskQueryRequestRepresentationModel } from '../models/filter.model';
|
||||
import { TaskListModel } from '../models/task-list.model';
|
||||
import { taskPresetsDefaultModel } from '../models/task-preset.model';
|
||||
import { TaskListService } from './../services/tasklist.service';
|
||||
|
||||
const DEFAULT_SIZE = 5;
|
||||
@Component({
|
||||
selector: 'adf-tasklist',
|
||||
templateUrl: './task-list.component.html',
|
||||
styleUrls: ['./task-list.component.css']
|
||||
})
|
||||
export class TaskListComponent implements OnChanges, OnInit, AfterContentInit {
|
||||
|
||||
requestNode: TaskQueryRequestRepresentationModel;
|
||||
|
||||
@ContentChild(DataColumnListComponent) columnList: DataColumnListComponent;
|
||||
|
||||
@Input()
|
||||
appId: number;
|
||||
|
||||
@Input()
|
||||
processInstanceId: string;
|
||||
|
||||
@Input()
|
||||
processDefinitionKey: string;
|
||||
|
||||
@Input()
|
||||
state: string;
|
||||
|
||||
@Input()
|
||||
assignment: string;
|
||||
|
||||
@Input()
|
||||
sort: string;
|
||||
|
||||
@Input()
|
||||
name: string;
|
||||
|
||||
@Input()
|
||||
landingTaskId: string;
|
||||
|
||||
@Input()
|
||||
data: DataTableAdapter;
|
||||
|
||||
@Input()
|
||||
selectionMode: string = 'single'; // none|single|multiple
|
||||
|
||||
@Input()
|
||||
presetColumn: string;
|
||||
|
||||
@Input()
|
||||
multiselect: boolean = false;
|
||||
|
||||
@Output()
|
||||
rowClick: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
@Output()
|
||||
rowsSelected: EventEmitter<any[]> = new EventEmitter<any[]>();
|
||||
|
||||
@Output()
|
||||
success: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
currentInstanceId: string;
|
||||
selectedInstances: any[];
|
||||
layoutPresets = {};
|
||||
|
||||
@Input()
|
||||
page: number = 0;
|
||||
|
||||
@Input()
|
||||
size: number = DEFAULT_SIZE;
|
||||
|
||||
isLoading: boolean = true;
|
||||
|
||||
/**
|
||||
* Toggles custom data source mode.
|
||||
* When enabled the component reloads data from it's current source instead of the server side.
|
||||
* This allows generating and displaying custom data sets (i.e. filtered out content).
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberOf TaskListComponent
|
||||
*/
|
||||
hasCustomDataSource: boolean = false;
|
||||
isStreamLoaded = false;
|
||||
|
||||
constructor(private taskListService: TaskListService,
|
||||
private appConfig: AppConfigService) {
|
||||
}
|
||||
|
||||
initStream() {
|
||||
if (!this.isStreamLoaded) {
|
||||
this.isStreamLoaded = true;
|
||||
this.taskListService.tasksList$.subscribe(
|
||||
(tasks) => {
|
||||
let instancesRow = this.createDataRow(tasks.data);
|
||||
this.renderInstances(instancesRow);
|
||||
this.selectTask(this.landingTaskId);
|
||||
this.success.emit(tasks);
|
||||
this.isLoading = false;
|
||||
}, (error) => {
|
||||
this.error.emit(error);
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.data === undefined) {
|
||||
this.data = new ObjectDataTableAdapter();
|
||||
}
|
||||
this.initStream();
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this.loadLayoutPresets();
|
||||
this.setupSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup html-based (html definitions) or code behind (data adapter) schema.
|
||||
* If component is assigned with an empty data adater the default schema settings applied.
|
||||
*/
|
||||
setupSchema(): void {
|
||||
let schema: DataColumn[] = [];
|
||||
|
||||
if (this.columnList && this.columnList.columns && this.columnList.columns.length > 0) {
|
||||
schema = this.columnList.columns.map(c => <DataColumn> c);
|
||||
}
|
||||
|
||||
if (!this.data) {
|
||||
this.data = new ObjectDataTableAdapter([], schema.length > 0 ? schema : this.presetColumn ? this.getLayoutPreset(this.presetColumn) : this.getLayoutPreset());
|
||||
|
||||
} else {
|
||||
if (schema && schema.length > 0) {
|
||||
this.data.setColumns(schema);
|
||||
} else if (this.data.getColumns().length === 0) {
|
||||
this.presetColumn ? this.setupDefaultColumns(this.presetColumn) : this.setupDefaultColumns();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.initStream();
|
||||
if (this.isPropertyChanged(changes)) {
|
||||
this.reload();
|
||||
}
|
||||
}
|
||||
|
||||
setCustomDataSource(rows: ObjectDataRow[]): void {
|
||||
if (this.data) {
|
||||
this.data.setRows(rows);
|
||||
this.hasCustomDataSource = true;
|
||||
}
|
||||
}
|
||||
|
||||
private isPropertyChanged(changes: SimpleChanges): boolean {
|
||||
let changed: boolean = true;
|
||||
|
||||
let landingTaskId = changes['landingTaskId'];
|
||||
if (landingTaskId && landingTaskId.currentValue && this.isEqualToCurrentId(landingTaskId.currentValue)) {
|
||||
changed = false;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
reload(): void {
|
||||
if (!this.hasCustomDataSource) {
|
||||
this.requestNode = this.createRequestNode();
|
||||
this.load(this.requestNode);
|
||||
}
|
||||
}
|
||||
|
||||
private load(requestNode: TaskQueryRequestRepresentationModel) {
|
||||
this.isLoading = true;
|
||||
this.loadTasksByState().subscribe();
|
||||
}
|
||||
|
||||
private loadTasksByState(): Observable<TaskListModel> {
|
||||
return this.requestNode.state === 'all'
|
||||
? this.taskListService.findAllTasksWithoutState(this.requestNode)
|
||||
: this.taskListService.findTasksByState(this.requestNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of ObjectDataRow
|
||||
* @param instances
|
||||
* @returns {ObjectDataRow[]}
|
||||
*/
|
||||
private createDataRow(instances: any[]): ObjectDataRow[] {
|
||||
let instancesRows: ObjectDataRow[] = [];
|
||||
instances.forEach((row) => {
|
||||
instancesRows.push(new ObjectDataRow(row));
|
||||
});
|
||||
return instancesRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the instances list
|
||||
*
|
||||
* @param instances
|
||||
*/
|
||||
private renderInstances(instances: any[]) {
|
||||
instances = this.optimizeNames(instances);
|
||||
this.data.setRows(instances);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the task given in input if present
|
||||
*/
|
||||
selectTask(taskIdToSelect: string): void {
|
||||
if (!this.isListEmpty()) {
|
||||
let rows = this.data.getRows();
|
||||
if (rows.length > 0) {
|
||||
let dataRow = rows.find(row => row.getValue('id') === taskIdToSelect) || rows[0];
|
||||
this.data.selectedRow = dataRow;
|
||||
dataRow.isSelected = true;
|
||||
this.currentInstanceId = dataRow.getValue('id');
|
||||
}
|
||||
} else {
|
||||
if (this.data) {
|
||||
this.data.selectedRow = null;
|
||||
}
|
||||
this.currentInstanceId = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current id
|
||||
* @returns {string}
|
||||
*/
|
||||
getCurrentId(): string {
|
||||
return this.currentInstanceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the taskId is the same of the selected task
|
||||
* @param taskId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isEqualToCurrentId(taskId: string) {
|
||||
return this.currentInstanceId === taskId ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the list is empty
|
||||
* @returns {ObjectDataTableAdapter|boolean}
|
||||
*/
|
||||
isListEmpty(): boolean {
|
||||
return this.data === undefined ||
|
||||
(this.data && this.data.getRows() && this.data.getRows().length === 0);
|
||||
}
|
||||
|
||||
onRowClick(item: DataRowEvent) {
|
||||
this.currentInstanceId = item.value.getValue('id');
|
||||
this.rowClick.emit(this.currentInstanceId);
|
||||
}
|
||||
|
||||
onRowSelect(event: CustomEvent) {
|
||||
this.selectedInstances = [...event.detail.selection];
|
||||
this.rowsSelected.emit(this.selectedInstances);
|
||||
}
|
||||
|
||||
onRowUnselect(event: CustomEvent) {
|
||||
this.selectedInstances = [...event.detail.selection];
|
||||
this.rowsSelected.emit(this.selectedInstances);
|
||||
}
|
||||
|
||||
onRowKeyUp(event: CustomEvent) {
|
||||
if (event.detail.keyboardEvent.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
this.currentInstanceId = event.detail.row.getValue('id');
|
||||
this.rowClick.emit(this.currentInstanceId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize name field
|
||||
* @param istances
|
||||
* @returns {any[]}
|
||||
*/
|
||||
private optimizeNames(istances: any[]) {
|
||||
istances = istances.map(t => {
|
||||
t.obj.name = t.obj.name || 'No name';
|
||||
return t;
|
||||
});
|
||||
return istances;
|
||||
}
|
||||
|
||||
private createRequestNode() {
|
||||
|
||||
let requestNode = {
|
||||
appDefinitionId: this.appId,
|
||||
processInstanceId: this.processInstanceId,
|
||||
processDefinitionKey: this.processDefinitionKey,
|
||||
text: this.name,
|
||||
assignment: this.assignment,
|
||||
state: this.state,
|
||||
sort: this.sort,
|
||||
landingTaskId: this.landingTaskId,
|
||||
page: this.page,
|
||||
size: this.size,
|
||||
start: 0
|
||||
};
|
||||
return new TaskQueryRequestRepresentationModel(requestNode);
|
||||
}
|
||||
|
||||
setupDefaultColumns(preset: string = 'default'): void {
|
||||
if (this.data) {
|
||||
const columns = this.getLayoutPreset(preset);
|
||||
this.data.setColumns(columns);
|
||||
}
|
||||
}
|
||||
|
||||
private loadLayoutPresets(): void {
|
||||
const externalSettings = this.appConfig.get('adf-task-list.presets', null);
|
||||
|
||||
if (externalSettings) {
|
||||
this.layoutPresets = Object.assign({}, taskPresetsDefaultModel, externalSettings);
|
||||
} else {
|
||||
this.layoutPresets = taskPresetsDefaultModel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private getLayoutPreset(name: string = 'default'): DataColumn[] {
|
||||
return (this.layoutPresets[name] || this.layoutPresets['default']).map(col => new ObjectDataColumn(col));
|
||||
}
|
||||
}
|
18
lib/process-services/task-list/index.ts
Normal file
18
lib/process-services/task-list/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
export * from './public-api';
|
136
lib/process-services/task-list/models/filter.model.ts
Normal file
136
lib/process-services/task-list/models/filter.model.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TaskQueryRequestRepresentation, UserTaskFilterRepresentation } from 'alfresco-js-api';
|
||||
|
||||
export class AppDefinitionRepresentationModel {
|
||||
defaultAppId: string;
|
||||
deploymentId: string;
|
||||
name: string;
|
||||
description: string;
|
||||
theme: string;
|
||||
icon: string;
|
||||
id: number;
|
||||
modelId: number;
|
||||
tenantId: number;
|
||||
|
||||
constructor(obj?: any) {
|
||||
if (obj) {
|
||||
this.defaultAppId = obj.defaultAppId ? obj.defaultAppId : null;
|
||||
this.deploymentId = obj.deploymentId ? obj.deploymentId : null;
|
||||
this.name = obj.name ? obj.name : null;
|
||||
this.description = obj.description ? obj.description : null;
|
||||
this.theme = obj.theme ? obj.theme : null;
|
||||
this.icon = obj.icon ? obj.icon : null;
|
||||
this.id = obj.id ? obj.id : null;
|
||||
this.modelId = obj.modelId ? obj.modelId : null;
|
||||
this.tenantId = obj.tenantId ? obj.tenantId : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FilterParamsModel {
|
||||
id: string;
|
||||
name: string;
|
||||
index: number;
|
||||
|
||||
constructor(obj?: any) {
|
||||
if (obj) {
|
||||
this.id = obj.id || null;
|
||||
this.name = obj.name || null;
|
||||
this.index = obj.index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FilterRepresentationModel implements UserTaskFilterRepresentation {
|
||||
id: number;
|
||||
appId: number;
|
||||
name: string;
|
||||
recent: boolean;
|
||||
icon: string;
|
||||
filter: FilterParamRepresentationModel;
|
||||
index: number;
|
||||
|
||||
constructor(obj?: any) {
|
||||
if (obj) {
|
||||
this.id = obj.id || null;
|
||||
this.appId = obj.appId || null;
|
||||
this.name = obj.name || null;
|
||||
this.recent = !!obj.recent;
|
||||
this.icon = obj.icon || null;
|
||||
this.filter = new FilterParamRepresentationModel(obj.filter);
|
||||
this.index = obj.index;
|
||||
}
|
||||
}
|
||||
|
||||
hasFilter() {
|
||||
return this.filter ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
export class FilterParamRepresentationModel {
|
||||
processDefinitionId: string;
|
||||
processDefinitionKey: string;
|
||||
name: string;
|
||||
state: string;
|
||||
sort: string;
|
||||
assignment: string;
|
||||
dueAfter: Date;
|
||||
dueBefore: Date;
|
||||
|
||||
constructor(obj?: any) {
|
||||
if (obj) {
|
||||
this.processDefinitionId = obj.processDefinitionId || null;
|
||||
this.processDefinitionKey = obj.processDefinitionKey || null;
|
||||
this.name = obj.name || null;
|
||||
this.state = obj.state || null;
|
||||
this.sort = obj.sort || null;
|
||||
this.assignment = obj.assignment || null;
|
||||
this.dueAfter = obj.dueAfter || null;
|
||||
this.dueBefore = obj.dueBefore || null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TaskQueryRequestRepresentationModel implements TaskQueryRequestRepresentation {
|
||||
appDefinitionId: string;
|
||||
processInstanceId: string;
|
||||
processDefinitionId: string;
|
||||
text: string;
|
||||
assignment: string;
|
||||
state: string;
|
||||
start: string;
|
||||
sort: string;
|
||||
page: number;
|
||||
size: number;
|
||||
|
||||
constructor(obj?: any) {
|
||||
if (obj) {
|
||||
this.appDefinitionId = obj.appDefinitionId || null;
|
||||
this.processInstanceId = obj.processInstanceId || null;
|
||||
this.processDefinitionId = obj.processDefinitionId || null;
|
||||
this.text = obj.text || null;
|
||||
this.assignment = obj.assignment || null;
|
||||
this.state = obj.state || null;
|
||||
this.start = obj.start || null;
|
||||
this.sort = obj.sort || null;
|
||||
this.page = obj.page || 0;
|
||||
this.size = obj.size || 25;
|
||||
}
|
||||
}
|
||||
}
|
34
lib/process-services/task-list/models/form.model.ts
Normal file
34
lib/process-services/task-list/models/form.model.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*!
|
||||
* @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 of the Form.
|
||||
*
|
||||
*
|
||||
* @returns {Form} .
|
||||
*/
|
||||
export class Form {
|
||||
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
constructor(id: number, name: string) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
}
|
||||
}
|
44
lib/process-services/task-list/models/start-task.model.ts
Normal file
44
lib/process-services/task-list/models/start-task.model.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*!
|
||||
* @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 of the StartTaskModel.
|
||||
*
|
||||
*
|
||||
* @returns {StartTaskModel} .
|
||||
*/
|
||||
import { UserProcessModel } from '@alfresco/core';
|
||||
|
||||
export class StartTaskModel {
|
||||
|
||||
name: string;
|
||||
description: string;
|
||||
assignee: UserProcessModel;
|
||||
dueDate: any;
|
||||
formKey: any;
|
||||
category: string;
|
||||
|
||||
constructor(obj?: any) {
|
||||
this.name = obj && obj.name || null;
|
||||
this.description = obj && obj.description || null;
|
||||
this.assignee = obj && obj.assignee ? new UserProcessModel(obj.assignee) : null;
|
||||
this.dueDate = obj && obj.dueDate || null;
|
||||
this.formKey = obj && obj.formKey || null;
|
||||
this.category = obj && obj.category || null;
|
||||
}
|
||||
}
|
40
lib/process-services/task-list/models/task-details.event.ts
Normal file
40
lib/process-services/task-list/models/task-details.event.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TaskDetailsModel } from './task-details.model';
|
||||
|
||||
export class TaskDetailsEvent {
|
||||
|
||||
private _value: TaskDetailsModel;
|
||||
private _defaultPrevented: boolean = false;
|
||||
|
||||
get value(): TaskDetailsModel {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
get defaultPrevented() {
|
||||
return this._defaultPrevented;
|
||||
}
|
||||
|
||||
constructor(value: TaskDetailsModel) {
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
preventDefault() {
|
||||
this._defaultPrevented = true;
|
||||
}
|
||||
}
|
115
lib/process-services/task-list/models/task-details.model.ts
Normal file
115
lib/process-services/task-list/models/task-details.model.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/*!
|
||||
* @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 details of a task.
|
||||
*
|
||||
*
|
||||
* @returns {TaskDetailsModel} .
|
||||
*/
|
||||
import { UserProcessModel } from '@alfresco/core';
|
||||
import { TaskRepresentation } from 'alfresco-js-api';
|
||||
import { UserGroupModel } from './user-group.model';
|
||||
|
||||
export class TaskDetailsModel implements TaskRepresentation {
|
||||
id: string;
|
||||
name: string;
|
||||
assignee: UserProcessModel;
|
||||
priority: number;
|
||||
adhocTaskCanBeReassigned: boolean;
|
||||
category: string;
|
||||
created: Date;
|
||||
description: string;
|
||||
dueDate: Date;
|
||||
duration: number;
|
||||
endDate: Date;
|
||||
executionId: string;
|
||||
formKey: string;
|
||||
initiatorCanCompleteTask: boolean;
|
||||
managerOfCandidateGroup: boolean;
|
||||
memberOfCandidateGroup: boolean;
|
||||
memberOfCandidateUsers: boolean;
|
||||
involvedGroups: UserGroupModel [];
|
||||
involvedPeople: UserProcessModel [];
|
||||
parentTaskId: string;
|
||||
parentTaskName: string;
|
||||
processDefinitionCategory: string;
|
||||
processDefinitionDeploymentId: string;
|
||||
processDefinitionDescription: string;
|
||||
processDefinitionId: string;
|
||||
processDefinitionKey: string;
|
||||
processDefinitionName: string;
|
||||
processDefinitionVersion: number = 0;
|
||||
processInstanceId: string;
|
||||
processInstanceName: string;
|
||||
processInstanceStartUserId: string;
|
||||
taskDefinitionKey: string;
|
||||
|
||||
constructor(obj?: any) {
|
||||
if (obj) {
|
||||
this.id = obj.id || null;
|
||||
this.name = obj.name || null;
|
||||
this.priority = obj.priority;
|
||||
this.assignee = obj.assignee ? new UserProcessModel(obj.assignee) : null;
|
||||
this.adhocTaskCanBeReassigned = obj.adhocTaskCanBeReassigned;
|
||||
this.category = obj.category || null;
|
||||
this.created = obj.created || null;
|
||||
this.description = obj.description || null;
|
||||
this.dueDate = obj.dueDate || null;
|
||||
this.duration = obj.duration || null;
|
||||
this.endDate = obj.endDate || null;
|
||||
this.executionId = obj.executionId || null;
|
||||
this.formKey = obj.formKey || null;
|
||||
this.initiatorCanCompleteTask = !!obj.initiatorCanCompleteTask;
|
||||
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;
|
||||
this.processDefinitionCategory = obj.processDefinitionCategory || null;
|
||||
this.processDefinitionDeploymentId = obj.processDefinitionDeploymentId || null;
|
||||
this.processDefinitionDescription = obj.processDefinitionDescription || null;
|
||||
this.processDefinitionId = obj.processDefinitionId || null;
|
||||
this.processDefinitionKey = obj.processDefinitionKey || null;
|
||||
this.processDefinitionName = obj.processDefinitionName || null;
|
||||
this.processDefinitionVersion = obj.processDefinitionVersion || 0;
|
||||
this.processInstanceId = obj.processInstanceId || null;
|
||||
this.processInstanceName = obj.processInstanceName || null;
|
||||
this.processInstanceStartUserId = obj.processInstanceStartUserId || null;
|
||||
this.taskDefinitionKey = obj.taskDefinitionKey || null;
|
||||
}
|
||||
}
|
||||
|
||||
getFullName(): string {
|
||||
let fullName: string = '';
|
||||
|
||||
if (this.assignee) {
|
||||
let firstName: string = this.assignee.firstName ? this.assignee.firstName : '';
|
||||
let lastName: string = this.assignee.lastName ? this.assignee.lastName : '';
|
||||
fullName = `${firstName} ${lastName}`;
|
||||
}
|
||||
|
||||
return fullName.trim();
|
||||
}
|
||||
|
||||
isCompleted(): boolean {
|
||||
return !!this.endDate;
|
||||
}
|
||||
}
|
27
lib/process-services/task-list/models/task-list.model.ts
Normal file
27
lib/process-services/task-list/models/task-list.model.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TaskDetailsModel } from './task-details.model';
|
||||
|
||||
export interface TaskListModel {
|
||||
size: number;
|
||||
total: number;
|
||||
start: number;
|
||||
length: number;
|
||||
data: TaskDetailsModel [];
|
||||
|
||||
}
|
41
lib/process-services/task-list/models/task-preset.model.ts
Normal file
41
lib/process-services/task-list/models/task-preset.model.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
export let taskPresetsDefaultModel = {
|
||||
'default': [
|
||||
{
|
||||
'key': 'name',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.NAME',
|
||||
'sortable': true
|
||||
},
|
||||
{
|
||||
'key': 'created',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.CREATED',
|
||||
'cssClass': 'hidden',
|
||||
'sortable': true
|
||||
},
|
||||
{
|
||||
'key': 'assignee',
|
||||
'type': 'text',
|
||||
'title': 'ADF_TASK_LIST.PROPERTIES.ASSIGNEE',
|
||||
'cssClass': 'hidden',
|
||||
'sortable': true
|
||||
}
|
||||
]
|
||||
};
|
33
lib/process-services/task-list/models/user-event.model.ts
Normal file
33
lib/process-services/task-list/models/user-event.model.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*!
|
||||
* @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 User Event.
|
||||
*
|
||||
*
|
||||
* @returns {UserEventModel} .
|
||||
*/
|
||||
export class UserEventModel {
|
||||
type: string = '';
|
||||
value: any = {};
|
||||
|
||||
constructor(obj?: any) {
|
||||
this.type = obj && obj.type;
|
||||
this.value = obj && obj.value || {};
|
||||
}
|
||||
}
|
36
lib/process-services/task-list/models/user-group.model.ts
Normal file
36
lib/process-services/task-list/models/user-group.model.ts
Normal file
@@ -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;
|
||||
}
|
||||
}
|
37
lib/process-services/task-list/public-api.ts
Normal file
37
lib/process-services/task-list/public-api.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*!
|
||||
* @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.
|
||||
*/
|
||||
|
||||
export * from './components/task-list.component';
|
||||
export * from './components/checklist.component';
|
||||
export * from './components/task-header.component';
|
||||
export * from './components/no-task-detail-template.directive';
|
||||
export * from './components/task-filters.component';
|
||||
export * from './components/task-details.component';
|
||||
export * from './components/task-audit.directive';
|
||||
export * from './components/start-task.component';
|
||||
|
||||
export * from './services/tasklist.service';
|
||||
export * from './services/process-upload.service';
|
||||
export * from './services/task-filter.service';
|
||||
|
||||
export * from './models/filter.model';
|
||||
export * from './models/task-details.model';
|
||||
export * from './models/task-details.event';
|
||||
export * from './models/user-event.model';
|
||||
export * from './models/start-task.model';
|
||||
|
||||
export * from './task-list.module';
|
@@ -0,0 +1,44 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AlfrescoApiService, AppConfigService, UploadService } from '@alfresco/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
@Injectable()
|
||||
export class ProcessUploadService extends UploadService {
|
||||
|
||||
instanceApi: AlfrescoApiService;
|
||||
|
||||
constructor(apiService: AlfrescoApiService, appConfigService: AppConfigService) {
|
||||
super(apiService, appConfigService);
|
||||
this.instanceApi = apiService;
|
||||
}
|
||||
|
||||
getUploadPromise(file: any): any {
|
||||
let opts = {
|
||||
isRelatedContent: true
|
||||
};
|
||||
let taskId = file.options.parentId;
|
||||
return this.instanceApi.getInstance().activiti.contentApi.createRelatedContentOnTask(taskId, file.file, opts).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
return Observable.throw(error || 'Server error');
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,217 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
fakeAppFilter,
|
||||
fakeAppPromise,
|
||||
fakeFilters
|
||||
} from '../../mock';
|
||||
import { FilterRepresentationModel } from '../models/filter.model';
|
||||
import { TaskFilterService } from './task-filter.service';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('Activiti Tas filter Service', () => {
|
||||
|
||||
let service: TaskFilterService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
|
||||
providers: [
|
||||
TaskFilterService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.get(TaskFilterService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
describe('Content tests', () => {
|
||||
|
||||
it('should return the task list filters', (done) => {
|
||||
service.getTaskListFilters().subscribe(
|
||||
(res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toEqual(2);
|
||||
expect(res[0].name).toEqual('FakeInvolvedTasks');
|
||||
expect(res[1].name).toEqual('FakeMyTasks');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeFilters)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the task filter by id', (done) => {
|
||||
service.getTaskFilterById(2).subscribe(
|
||||
(taskFilter: FilterRepresentationModel) => {
|
||||
expect(taskFilter).toBeDefined();
|
||||
expect(taskFilter.id).toEqual(2);
|
||||
expect(taskFilter.name).toEqual('FakeMyTasks');
|
||||
expect(taskFilter.filter.sort).toEqual('created-desc');
|
||||
expect(taskFilter.filter.state).toEqual('open');
|
||||
expect(taskFilter.filter.assignment).toEqual('fake-assignee');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeFilters)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the task filter by name', (done) => {
|
||||
service.getTaskFilterByName('FakeMyTasks').subscribe(
|
||||
(res: FilterRepresentationModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).toEqual(2);
|
||||
expect(res.name).toEqual('FakeMyTasks');
|
||||
expect(res.filter.sort).toEqual('created-desc');
|
||||
expect(res.filter.state).toEqual('open');
|
||||
expect(res.filter.assignment).toEqual('fake-assignee');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeFilters)
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the api withthe appId', (done) => {
|
||||
spyOn(service, 'callApiTaskFilters').and.returnValue((fakeAppPromise));
|
||||
|
||||
let appId = 1;
|
||||
service.getTaskListFilters(appId).subscribe(
|
||||
(res) => {
|
||||
expect(service.callApiTaskFilters).toHaveBeenCalledWith(appId);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the app filter by id', (done) => {
|
||||
let appId = 1;
|
||||
service.getTaskListFilters(appId).subscribe(
|
||||
(res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toEqual(1);
|
||||
expect(res[0].name).toEqual('FakeInvolvedTasks');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeAppFilter)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the default filters', (done) => {
|
||||
service.createDefaultFilters(1234).subscribe(
|
||||
(res: FilterRepresentationModel []) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toEqual(4);
|
||||
expect(res[0].name).toEqual('Involved Tasks');
|
||||
expect(res[1].name).toEqual('My Tasks');
|
||||
expect(res[2].name).toEqual('Queued Tasks');
|
||||
expect(res[3].name).toEqual('Completed Tasks');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.at(0).respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
id: '111', name: 'Involved Tasks', filter: { assignment: 'fake-involved' }
|
||||
})
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.at(1).respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
id: '222', name: 'My Tasks', filter: { assignment: 'fake-assignee' }
|
||||
})
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.at(2).respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
id: '333', name: 'Queued Tasks', filter: { assignment: 'fake-candidate' }
|
||||
})
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.at(3).respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
id: '444', name: 'Completed Tasks', filter: { assignment: 'fake-involved' }
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a filter ', (done) => {
|
||||
let filterFake = new FilterRepresentationModel({
|
||||
name: 'FakeNameFilter',
|
||||
assignment: 'fake-assignement'
|
||||
});
|
||||
|
||||
service.addFilter(filterFake).subscribe(
|
||||
(res: FilterRepresentationModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).not.toEqual(null);
|
||||
expect(res.name).toEqual('FakeNameFilter');
|
||||
expect(res.filter.assignment).toEqual('fake-assignement');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
id: '2233', name: 'FakeNameFilter', filter: { assignment: 'fake-assignement' }
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
210
lib/process-services/task-list/services/task-filter.service.ts
Normal file
210
lib/process-services/task-list/services/task-filter.service.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AlfrescoApiService, LogService } from '@alfresco/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs/Rx';
|
||||
import { FilterRepresentationModel } from '../models/filter.model';
|
||||
import { TaskListModel } from '../models/task-list.model';
|
||||
|
||||
@Injectable()
|
||||
export class TaskFilterService {
|
||||
private tasksListSubject = new Subject<TaskListModel>();
|
||||
|
||||
public tasksList$: Observable<TaskListModel>;
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService) {
|
||||
this.tasksList$ = this.tasksListSubject.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return the default filters
|
||||
* @param appId
|
||||
* @returns {FilterRepresentationModel[]}
|
||||
*/
|
||||
public createDefaultFilters(appId: number): Observable<FilterRepresentationModel[]> {
|
||||
let involvedTasksFilter = this.getInvolvedTasksFilterInstance(appId);
|
||||
let involvedObservable = this.addFilter(involvedTasksFilter);
|
||||
|
||||
let myTasksFilter = this.getMyTasksFilterInstance(appId);
|
||||
let myTaskObservable = this.addFilter(myTasksFilter);
|
||||
|
||||
let queuedTasksFilter = this.getQueuedTasksFilterInstance(appId);
|
||||
let queuedObservable = this.addFilter(queuedTasksFilter);
|
||||
|
||||
let completedTasksFilter = this.getCompletedTasksFilterInstance(appId);
|
||||
let completeObservable = this.addFilter(completedTasksFilter);
|
||||
|
||||
return Observable.create(observer => {
|
||||
Observable.forkJoin(
|
||||
involvedObservable,
|
||||
myTaskObservable,
|
||||
queuedObservable,
|
||||
completeObservable
|
||||
).subscribe(
|
||||
(res) => {
|
||||
let filters: FilterRepresentationModel[] = [];
|
||||
res.forEach((filter) => {
|
||||
if (filter.name === involvedTasksFilter.name) {
|
||||
filters.push(involvedTasksFilter);
|
||||
} else if (filter.name === myTasksFilter.name) {
|
||||
filters.push(myTasksFilter);
|
||||
} else if (filter.name === queuedTasksFilter.name) {
|
||||
filters.push(queuedTasksFilter);
|
||||
} else if (filter.name === completedTasksFilter.name) {
|
||||
filters.push(completedTasksFilter);
|
||||
}
|
||||
});
|
||||
observer.next(filters);
|
||||
observer.complete();
|
||||
},
|
||||
(err: any) => {
|
||||
this.logService.error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the Tasks filters
|
||||
* @returns {Observable<any>}
|
||||
*/
|
||||
getTaskListFilters(appId?: number): Observable<any> {
|
||||
return Observable.fromPromise(this.callApiTaskFilters(appId))
|
||||
.map((response: any) => {
|
||||
let filters: FilterRepresentationModel[] = [];
|
||||
response.data.forEach((filter: FilterRepresentationModel) => {
|
||||
let filterModel = new FilterRepresentationModel(filter);
|
||||
filters.push(filterModel);
|
||||
});
|
||||
return filters;
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Tasks filter by id
|
||||
* @param filterId - number - The id of the filter
|
||||
* @param appId - string - optional - The id of app
|
||||
* @returns {Observable<FilterRepresentationModel>}
|
||||
*/
|
||||
getTaskFilterById(filterId: number, appId?: number): Observable<FilterRepresentationModel> {
|
||||
return Observable.fromPromise(this.callApiTaskFilters(appId))
|
||||
.map((response: any) => {
|
||||
return response.data.find(filter => filter.id === filterId);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Tasks filter by name
|
||||
* @param taskName - string - The name of the filter
|
||||
* @returns {Observable<FilterRepresentationModel>}
|
||||
*/
|
||||
getTaskFilterByName(taskName: string, appId?: number): Observable<FilterRepresentationModel> {
|
||||
return Observable.fromPromise(this.callApiTaskFilters(appId))
|
||||
.map((response: any) => {
|
||||
return response.data.find(filter => filter.name === taskName);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a filter
|
||||
* @param filter - FilterRepresentationModel
|
||||
* @returns {FilterRepresentationModel}
|
||||
*/
|
||||
addFilter(filter: FilterRepresentationModel): Observable<FilterRepresentationModel> {
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.userFiltersApi.createUserTaskFilter(filter))
|
||||
.map(res => res)
|
||||
.map((response: FilterRepresentationModel) => {
|
||||
return response;
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
callApiTaskFilters(appId?: number) {
|
||||
if (appId) {
|
||||
return this.apiService.getInstance().activiti.userFiltersApi.getUserTaskFilters({appId: appId});
|
||||
} else {
|
||||
return this.apiService.getInstance().activiti.userFiltersApi.getUserTaskFilters();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a static Involved filter instance
|
||||
* @param appId
|
||||
* @returns {FilterRepresentationModel}
|
||||
*/
|
||||
getInvolvedTasksFilterInstance(appId: number): FilterRepresentationModel {
|
||||
return new FilterRepresentationModel({
|
||||
'name': 'Involved Tasks',
|
||||
'appId': appId,
|
||||
'recent': false,
|
||||
'icon': 'glyphicon-align-left',
|
||||
'filter': {'sort': 'created-desc', 'name': '', 'state': 'open', 'assignment': 'involved'}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a static My task filter instance
|
||||
* @param appId
|
||||
* @returns {FilterRepresentationModel}
|
||||
*/
|
||||
getMyTasksFilterInstance(appId: number): FilterRepresentationModel {
|
||||
return new FilterRepresentationModel({
|
||||
'name': 'My Tasks',
|
||||
'appId': appId,
|
||||
'recent': false,
|
||||
'icon': 'glyphicon-inbox',
|
||||
'filter': {'sort': 'created-desc', 'name': '', 'state': 'open', 'assignment': 'assignee'}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a static Queued filter instance
|
||||
* @param appId
|
||||
* @returns {FilterRepresentationModel}
|
||||
*/
|
||||
getQueuedTasksFilterInstance(appId: number): FilterRepresentationModel {
|
||||
return new FilterRepresentationModel({
|
||||
'name': 'Queued Tasks',
|
||||
'appId': appId,
|
||||
'recent': false,
|
||||
'icon': 'glyphicon-record',
|
||||
'filter': {'sort': 'created-desc', 'name': '', 'state': 'open', 'assignment': 'candidate'}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a static Completed filter instance
|
||||
* @param appId
|
||||
* @returns {FilterRepresentationModel}
|
||||
*/
|
||||
getCompletedTasksFilterInstance(appId: number): FilterRepresentationModel {
|
||||
return new FilterRepresentationModel({
|
||||
'name': 'Completed Tasks',
|
||||
'appId': appId,
|
||||
'recent': true,
|
||||
'icon': 'glyphicon-ok-sign',
|
||||
'filter': {'sort': 'created-desc', 'name': '', 'state': 'completed', 'assignment': 'involved'}
|
||||
});
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
this.tasksListSubject.error(error);
|
||||
return Observable.throw(error || 'Server error');
|
||||
}
|
||||
|
||||
}
|
639
lib/process-services/task-list/services/tasklist.service.spec.ts
Normal file
639
lib/process-services/task-list/services/tasklist.service.spec.ts
Normal file
@@ -0,0 +1,639 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { async, TestBed } from '@angular/core/testing';
|
||||
import { UserProcessModel } from '@alfresco/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import {
|
||||
fakeCompletedTaskList,
|
||||
fakeErrorTaskList,
|
||||
fakeFilter,
|
||||
fakeFormList,
|
||||
fakeOpenTaskList,
|
||||
fakeRepresentationFilter1,
|
||||
fakeRepresentationFilter2,
|
||||
fakeTaskDetails,
|
||||
fakeTaskList,
|
||||
fakeTaskListDifferentProcessDefinitionKey,
|
||||
fakeTasksChecklist,
|
||||
fakeUser1,
|
||||
fakeUser2,
|
||||
secondFakeTaskList
|
||||
} from '../../mock';
|
||||
import { FilterRepresentationModel, TaskQueryRequestRepresentationModel } from '../models/filter.model';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { TaskListService } from './tasklist.service';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
xdescribe('Activiti TaskList Service', () => {
|
||||
|
||||
let service: TaskListService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
|
||||
providers: [
|
||||
TaskListService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
service = TestBed.get(TaskListService);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
describe('Content tests', () => {
|
||||
|
||||
it('should return the task list filtered', (done) => {
|
||||
service.getTasks(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.size).toEqual(1);
|
||||
expect(res.start).toEqual(0);
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(1);
|
||||
expect(res.data[0].name).toEqual('FakeNameTask');
|
||||
expect(res.data[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.data[0].assignee.firstName).toEqual('firstName');
|
||||
expect(res.data[0].assignee.lastName).toEqual('lastName');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTaskList)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the task list filtered by processDefinitionKey', (done) => {
|
||||
service.getTasks(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.size).toEqual(2);
|
||||
expect(res.start).toEqual(0);
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(2);
|
||||
expect(res.data[0].name).toEqual('FakeNameTask');
|
||||
expect(res.data[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.data[0].assignee.firstName).toEqual('firstName');
|
||||
expect(res.data[0].assignee.lastName).toEqual('lastName');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTaskListDifferentProcessDefinitionKey)
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an exception when the response is wrong', () => {
|
||||
service.getTasks(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
|
||||
(res) => {
|
||||
},
|
||||
(err: any) => {
|
||||
expect(err).toBeDefined();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 404,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeErrorTaskList)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the task list with all tasks filtered by state', (done) => {
|
||||
spyOn(service, 'getTasks').and.returnValue(Observable.of(fakeTaskList));
|
||||
spyOn(service, 'getTotalTasks').and.returnValue(Observable.of(fakeTaskList));
|
||||
|
||||
service.findAllTaskByState(<TaskQueryRequestRepresentationModel> fakeFilter, 'open').subscribe(
|
||||
res => {
|
||||
|
||||
expect(res).toBeDefined();
|
||||
expect(res.size).toEqual(1);
|
||||
expect(res.start).toEqual(0);
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(1);
|
||||
expect(res.data[0].name).toEqual('FakeNameTask');
|
||||
expect(res.data[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.data[0].assignee.firstName).toEqual('firstName');
|
||||
expect(res.data[0].assignee.lastName).toEqual('lastName');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the task list with all tasks filtered', (done) => {
|
||||
spyOn(service, 'getTasks').and.returnValue(Observable.of(fakeTaskList));
|
||||
spyOn(service, 'getTotalTasks').and.returnValue(Observable.of(fakeTaskList));
|
||||
|
||||
service.findAllTaskByState(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.size).toEqual(1);
|
||||
expect(res.start).toEqual(0);
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(1);
|
||||
expect(res.data[0].name).toEqual('FakeNameTask');
|
||||
expect(res.data[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.data[0].assignee.firstName).toEqual('firstName');
|
||||
expect(res.data[0].assignee.lastName).toEqual('lastName');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the task list filtered by state', (done) => {
|
||||
service.findTasksByState(<TaskQueryRequestRepresentationModel> fakeFilter, 'open').subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.size).toEqual(1);
|
||||
expect(res.start).toEqual(0);
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(1);
|
||||
expect(res.data[0].name).toEqual('FakeNameTask');
|
||||
expect(res.data[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.data[0].assignee.firstName).toEqual('firstName');
|
||||
expect(res.data[0].assignee.lastName).toEqual('lastName');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTaskList)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the task list filtered', (done) => {
|
||||
service.findTasksByState(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
|
||||
res => {
|
||||
expect(res.size).toEqual(1);
|
||||
expect(res.start).toEqual(0);
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(1);
|
||||
expect(res.data[0].name).toEqual('FakeNameTask');
|
||||
expect(res.data[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.data[0].assignee.firstName).toEqual('firstName');
|
||||
expect(res.data[0].assignee.lastName).toEqual('lastName');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTaskList)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the task list with all tasks filtered without state', (done) => {
|
||||
spyOn(service, 'getTasks').and.returnValue(Observable.of(fakeTaskList));
|
||||
spyOn(service, 'getTotalTasks').and.returnValue(Observable.of(fakeTaskList));
|
||||
|
||||
service.findAllTasksWithoutState(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(2);
|
||||
expect(res.data[0].name).toEqual('FakeNameTask');
|
||||
expect(res.data[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.data[0].assignee.firstName).toEqual('firstName');
|
||||
expect(res.data[0].assignee.lastName).toEqual('lastName');
|
||||
|
||||
expect(res.data[1].name).toEqual('FakeNameTask');
|
||||
expect(res.data[1].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.data[1].assignee.firstName).toEqual('firstName');
|
||||
expect(res.data[1].assignee.lastName).toEqual('lastName');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('Should return both open and completed task', (done) => {
|
||||
spyOn(service, 'findTasksByState').and.returnValue(Observable.of(fakeOpenTaskList));
|
||||
spyOn(service, 'findAllTaskByState').and.returnValue(Observable.of(fakeCompletedTaskList));
|
||||
service.findAllTasksWithoutState(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(4);
|
||||
expect(res.data[0].name).toEqual('FakeOpenTask1');
|
||||
expect(res.data[1].assignee.email).toEqual('fake-open-email@dom.com');
|
||||
expect(res.data[2].name).toEqual('FakeCompletedTaskName1');
|
||||
expect(res.data[2].assignee.email).toEqual('fake-completed-email@dom.com');
|
||||
expect(res.data[3].name).toEqual('FakeCompletedTaskName2');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should add the task list to the tasklistSubject with all tasks filtered without state', (done) => {
|
||||
spyOn(service, 'getTasks').and.returnValue(Observable.of(fakeTaskList));
|
||||
spyOn(service, 'getTotalTasks').and.returnValue(Observable.of(fakeTaskList));
|
||||
|
||||
service.tasksList$.subscribe(
|
||||
(res) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(2);
|
||||
expect(res.data[0].name).toEqual('FakeNameTask');
|
||||
expect(res.data[1].assignee.email).toEqual('fake-email@dom.com');
|
||||
});
|
||||
service.findAllTasksWithoutState(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.data).toBeDefined();
|
||||
expect(res.data.length).toEqual(2);
|
||||
expect(res.data[0].name).toEqual('FakeNameTask');
|
||||
expect(res.data[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.data[1].name).toEqual('FakeNameTask');
|
||||
expect(res.data[1].assignee.email).toEqual('fake-email@dom.com');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the task details ', (done) => {
|
||||
service.getTaskDetails('999').subscribe(
|
||||
(res: TaskDetailsModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).toEqual('999');
|
||||
expect(res.name).toEqual('fake-task-name');
|
||||
expect(res.formKey).toEqual('99');
|
||||
expect(res.assignee).toBeDefined();
|
||||
expect(res.assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res.assignee.firstName).toEqual('firstName');
|
||||
expect(res.assignee.lastName).toEqual('lastName');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTaskDetails)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the tasks checklists ', (done) => {
|
||||
service.getTaskChecklist('999').subscribe(
|
||||
(res: TaskDetailsModel[]) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toEqual(2);
|
||||
expect(res[0].name).toEqual('FakeCheckTask1');
|
||||
expect(res[0].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res[0].assignee.firstName).toEqual('firstName');
|
||||
expect(res[0].assignee.lastName).toEqual('lastName');
|
||||
expect(res[1].name).toEqual('FakeCheckTask2');
|
||||
expect(res[1].assignee.email).toEqual('fake-email@dom.com');
|
||||
expect(res[1].assignee.firstName).toEqual('firstName');
|
||||
expect(res[0].assignee.lastName).toEqual('lastName');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTasksChecklist)
|
||||
});
|
||||
});
|
||||
|
||||
it('should add a task ', (done) => {
|
||||
let taskFake = new TaskDetailsModel({
|
||||
id: 123,
|
||||
parentTaskId: 456,
|
||||
name: 'FakeNameTask',
|
||||
description: null,
|
||||
category: null,
|
||||
assignee: fakeUser1,
|
||||
created: ''
|
||||
});
|
||||
|
||||
service.addTask(taskFake).subscribe(
|
||||
(res: TaskDetailsModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).not.toEqual('');
|
||||
expect(res.name).toEqual('FakeNameTask');
|
||||
expect(res.created).not.toEqual(null);
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
id: '777', name: 'FakeNameTask', description: null, category: null,
|
||||
assignee: fakeUser1,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove a checklist task ', (done) => {
|
||||
service.deleteTask('999').subscribe(
|
||||
() => {
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json'
|
||||
});
|
||||
});
|
||||
|
||||
it('should complete the task ', (done) => {
|
||||
service.completeTask('999').subscribe(
|
||||
(res: any) => {
|
||||
expect(res).toBeDefined();
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the total number of tasks', (done) => {
|
||||
service.getTotalTasks(<TaskQueryRequestRepresentationModel> fakeFilter).subscribe(
|
||||
res => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.size).toEqual(1);
|
||||
expect(res.total).toEqual(1);
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTaskList)
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new standalone task ', (done) => {
|
||||
let taskFake = new TaskDetailsModel({
|
||||
name: 'FakeNameTask',
|
||||
description: 'FakeDescription',
|
||||
category: '3'
|
||||
});
|
||||
|
||||
service.createNewTask(taskFake).subscribe(
|
||||
(res: TaskDetailsModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).not.toEqual('');
|
||||
expect(res.name).toEqual('FakeNameTask');
|
||||
expect(res.description).toEqual('FakeDescription');
|
||||
expect(res.category).toEqual('3');
|
||||
expect(res.created).not.toEqual(null);
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
id: '777',
|
||||
name: 'FakeNameTask',
|
||||
description: 'FakeDescription',
|
||||
category: '3',
|
||||
assignee: fakeUser1,
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should assign task to a user', (done) => {
|
||||
let testTaskId = '8888';
|
||||
service.assignTask(testTaskId, fakeUser2).subscribe(
|
||||
(res: TaskDetailsModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).toEqual(testTaskId);
|
||||
expect(res.name).toEqual('FakeNameTask');
|
||||
expect(res.description).toEqual('FakeDescription');
|
||||
expect(res.category).toEqual('3');
|
||||
expect(res.created).not.toEqual(null);
|
||||
expect(res.adhocTaskCanBeReassigned).toBe(true);
|
||||
expect(res.assignee).toEqual(new UserProcessModel(fakeUser2));
|
||||
expect(res.involvedPeople[0].email).toEqual(fakeUser1.email);
|
||||
expect(res.involvedPeople[0].firstName).toEqual(fakeUser1.firstName);
|
||||
expect(res.involvedPeople[0].lastName).toEqual(fakeUser1.lastName);
|
||||
expect(res.involvedPeople[0].id).toEqual(fakeUser1.id);
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
id: testTaskId,
|
||||
name: 'FakeNameTask',
|
||||
description: 'FakeDescription',
|
||||
adhocTaskCanBeReassigned: true,
|
||||
category: '3',
|
||||
assignee: fakeUser2,
|
||||
involvedPeople: [fakeUser1],
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should assign task to a userId', (done) => {
|
||||
let testTaskId = '8888';
|
||||
service.assignTaskByUserId(testTaskId, fakeUser2.id).subscribe(
|
||||
(res: TaskDetailsModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.id).toEqual(testTaskId);
|
||||
expect(res.name).toEqual('FakeNameTask');
|
||||
expect(res.description).toEqual('FakeDescription');
|
||||
expect(res.category).toEqual('3');
|
||||
expect(res.created).not.toEqual(null);
|
||||
expect(res.adhocTaskCanBeReassigned).toBe(true);
|
||||
expect(res.assignee).toEqual(new UserProcessModel(fakeUser2));
|
||||
expect(res.involvedPeople[0].email).toEqual(fakeUser1.email);
|
||||
expect(res.involvedPeople[0].firstName).toEqual(fakeUser1.firstName);
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({
|
||||
id: testTaskId,
|
||||
name: 'FakeNameTask',
|
||||
description: 'FakeDescription',
|
||||
adhocTaskCanBeReassigned: true,
|
||||
category: '3',
|
||||
assignee: fakeUser2,
|
||||
involvedPeople: [fakeUser1],
|
||||
created: '2016-07-15T11:19:17.440+0000'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('should claim a task', (done) => {
|
||||
let taskId = '111';
|
||||
|
||||
service.claimTask(taskId).subscribe(
|
||||
(res: any) => {
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('should unclaim a task', (done) => {
|
||||
let taskId = '111';
|
||||
|
||||
service.unclaimTask(taskId).subscribe(
|
||||
(res: any) => {
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('should update a task', (done) => {
|
||||
let taskId = '111';
|
||||
|
||||
service.updateTask(taskId, {property: 'value'}).subscribe(
|
||||
(res: any) => {
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify({})
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the filter if it contains task id', (done) => {
|
||||
let taskId = '1';
|
||||
let filterFake = new FilterRepresentationModel({
|
||||
name: 'FakeNameFilter',
|
||||
assignment: 'fake-assignement',
|
||||
filter: {
|
||||
processDefinitionKey: '1',
|
||||
assignment: 'fake',
|
||||
state: 'none',
|
||||
sort: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
service.isTaskRelatedToFilter(taskId, filterFake).subscribe(
|
||||
(res: any) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res).not.toBeNull();
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTaskList)
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the filters if it contains task id', async(() => {
|
||||
let taskId = '1';
|
||||
|
||||
let fakeFilterList: FilterRepresentationModel[] = [];
|
||||
fakeFilterList.push(fakeRepresentationFilter1, fakeRepresentationFilter2);
|
||||
let resultFilter: FilterRepresentationModel = null;
|
||||
service.getFilterForTaskById(taskId, fakeFilterList).subscribe(
|
||||
(res: FilterRepresentationModel) => {
|
||||
resultFilter = res;
|
||||
},
|
||||
() => {
|
||||
},
|
||||
() => {
|
||||
expect(resultFilter).toBeDefined();
|
||||
expect(resultFilter).not.toBeNull();
|
||||
expect(resultFilter.name).toBe('CONTAIN FILTER');
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.at(0).respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeTaskList)
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.at(1).respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(secondFakeTaskList)
|
||||
});
|
||||
}));
|
||||
|
||||
it('should get possibile form list', (done) => {
|
||||
service.getFormList().subscribe(
|
||||
(res: any) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toBe(2);
|
||||
expect(res[0].id).toBe(1);
|
||||
expect(res[0].name).toBe('form with all widgets');
|
||||
expect(res[1].id).toBe(2);
|
||||
expect(res[1].name).toBe('uppy');
|
||||
done();
|
||||
}
|
||||
);
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'application/json',
|
||||
responseText: JSON.stringify(fakeFormList)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
354
lib/process-services/task-list/services/tasklist.service.ts
Normal file
354
lib/process-services/task-list/services/tasklist.service.ts
Normal file
@@ -0,0 +1,354 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AlfrescoApiService, LogService } from '@alfresco/core';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs/Rx';
|
||||
import { FilterRepresentationModel, TaskQueryRequestRepresentationModel } from '../models/filter.model';
|
||||
import { Form } from '../models/form.model';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { TaskListModel } from '../models/task-list.model';
|
||||
|
||||
@Injectable()
|
||||
export class TaskListService {
|
||||
private tasksListSubject = new Subject<TaskListModel>();
|
||||
|
||||
public tasksList$: Observable<TaskListModel>;
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService) {
|
||||
this.tasksList$ = this.tasksListSubject.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the filters in the list where the task id belong
|
||||
* @param taskId - string
|
||||
* @param filter - FilterRepresentationModel []
|
||||
* @returns {FilterRepresentationModel}
|
||||
*/
|
||||
getFilterForTaskById(taskId: string, filterList: FilterRepresentationModel[]): Observable<FilterRepresentationModel> {
|
||||
return Observable.from(filterList)
|
||||
.flatMap((filter: FilterRepresentationModel) => this.isTaskRelatedToFilter(taskId, filter))
|
||||
.filter((filter: FilterRepresentationModel) => filter != null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the search node for query task based on the given filter
|
||||
* @param filter - FilterRepresentationModel
|
||||
* @returns {TaskQueryRequestRepresentationModel}
|
||||
*/
|
||||
private generateTaskRequestNodeFromFilter(filter: FilterRepresentationModel): TaskQueryRequestRepresentationModel {
|
||||
let requestNode = {
|
||||
appDefinitionId: filter.appId,
|
||||
assignment: filter.filter.assignment,
|
||||
state: filter.filter.state,
|
||||
sort: filter.filter.sort
|
||||
};
|
||||
return new TaskQueryRequestRepresentationModel(requestNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a taskId is filtered with the given filter
|
||||
* @param taskId - string
|
||||
* @param filter - FilterRepresentationModel
|
||||
* @returns {FilterRepresentationModel}
|
||||
*/
|
||||
isTaskRelatedToFilter(taskId: string, filter: FilterRepresentationModel): Observable<FilterRepresentationModel> {
|
||||
let requestNodeForFilter = this.generateTaskRequestNodeFromFilter(filter);
|
||||
return Observable.fromPromise(this.callApiTasksFiltered(requestNodeForFilter))
|
||||
.map((res: any) => {
|
||||
return res.data.find(element => element.id === taskId) ? filter : null;
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the tasks filtered by filterModel
|
||||
* @param filter - TaskFilterRepresentationModel
|
||||
* @returns {any}
|
||||
*/
|
||||
getTasks(requestNode: TaskQueryRequestRepresentationModel): Observable<TaskListModel> {
|
||||
return Observable.fromPromise(this.callApiTasksFiltered(requestNode))
|
||||
.map((res: any) => {
|
||||
this.tasksListSubject.next(res);
|
||||
return res;
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve tasks filtered by filterModel and state
|
||||
* @param filter - TaskFilterRepresentationModel
|
||||
* @returns {any}
|
||||
*/
|
||||
findTasksByState(requestNode: TaskQueryRequestRepresentationModel, state?: string): Observable<TaskListModel> {
|
||||
if (state) {
|
||||
requestNode.state = state;
|
||||
}
|
||||
return this.getTasks(requestNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all tasks filtered by filterModel and state
|
||||
* @param filter - TaskFilterRepresentationModel
|
||||
* @returns {any}
|
||||
*/
|
||||
findAllTaskByState(requestNode: TaskQueryRequestRepresentationModel, state?: string): Observable<TaskListModel> {
|
||||
if (state) {
|
||||
requestNode.state = state;
|
||||
}
|
||||
return this.getTotalTasks(requestNode).switchMap((res: any) => {
|
||||
requestNode.size = res.total;
|
||||
return this.getTasks(requestNode);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all tasks filtered by filterModel irrespective of state
|
||||
* @param filter - TaskFilterRepresentationModel
|
||||
* @returns {any}
|
||||
*/
|
||||
findAllTasksWithoutState(requestNode: TaskQueryRequestRepresentationModel): Observable<TaskListModel> {
|
||||
return Observable.forkJoin(
|
||||
this.findTasksByState(requestNode, 'open'),
|
||||
this.findAllTaskByState(requestNode, 'completed'),
|
||||
(activeTasks: TaskListModel, completedTasks: TaskListModel) => {
|
||||
const tasks = Object.assign({}, activeTasks);
|
||||
tasks.total += completedTasks.total;
|
||||
tasks.data = tasks.data.concat(completedTasks.data);
|
||||
this.tasksListSubject.next(tasks);
|
||||
return tasks;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the task details
|
||||
* @param id - taskId
|
||||
* @returns {<TaskDetailsModel>}
|
||||
*/
|
||||
getTaskDetails(taskId: string): Observable<TaskDetailsModel> {
|
||||
return Observable.fromPromise(this.callApiTaskDetails(taskId))
|
||||
.map(res => res)
|
||||
.map((details: any) => {
|
||||
return new TaskDetailsModel(details);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the task's checklist
|
||||
* @param id - taskId
|
||||
* @returns {TaskDetailsModel}
|
||||
*/
|
||||
getTaskChecklist(id: string): Observable<TaskDetailsModel[]> {
|
||||
return Observable.fromPromise(this.callApiTaskChecklist(id))
|
||||
.map(res => res)
|
||||
.map((response: any) => {
|
||||
let checklists: TaskDetailsModel[] = [];
|
||||
response.data.forEach((checklist) => {
|
||||
checklists.push(new TaskDetailsModel(checklist));
|
||||
});
|
||||
return checklists;
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all the form shared with this user
|
||||
* @returns {TaskDetailsModel}
|
||||
*/
|
||||
getFormList(): Observable<Form []> {
|
||||
let opts = {
|
||||
'filter': 'myReusableForms', // String | filter
|
||||
'sort': 'modifiedDesc', // String | sort
|
||||
'modelType': 2 // Integer | modelType
|
||||
};
|
||||
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.modelsApi.getModels(opts)).map(res => res)
|
||||
.map((response: any) => {
|
||||
let forms: Form[] = [];
|
||||
response.data.forEach((form) => {
|
||||
forms.push(new Form(form.id, form.name));
|
||||
});
|
||||
return forms;
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
attachFormToATask(taskId: string, formId: number): Observable<any> {
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.attachForm(taskId, {'formId': formId})).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task
|
||||
* @param task - TaskDetailsModel
|
||||
* @returns {TaskDetailsModel}
|
||||
*/
|
||||
addTask(task: TaskDetailsModel): Observable<TaskDetailsModel> {
|
||||
return Observable.fromPromise(this.callApiAddTask(task))
|
||||
.map(res => res)
|
||||
.map((response: TaskDetailsModel) => {
|
||||
return new TaskDetailsModel(response);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a task
|
||||
* @param taskId - string
|
||||
*/
|
||||
deleteTask(taskId: string): Observable<TaskDetailsModel> {
|
||||
return Observable.fromPromise(this.callApiDeleteTask(taskId))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the task completed
|
||||
* @param id - taskId
|
||||
* @returns {TaskDetailsModel}
|
||||
*/
|
||||
completeTask(taskId: string) {
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.completeTask(taskId))
|
||||
.map(res => res)
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total number of the tasks by filter
|
||||
* @param requestNode - TaskFilterRepresentationModel
|
||||
* @returns {any}
|
||||
*/
|
||||
public getTotalTasks(requestNode: TaskQueryRequestRepresentationModel): Observable<any> {
|
||||
requestNode.size = 0;
|
||||
return Observable.fromPromise(this.callApiTasksFiltered(requestNode))
|
||||
.map((res: any) => {
|
||||
return res;
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new standalone task
|
||||
* @param task - TaskDetailsModel
|
||||
* @returns {TaskDetailsModel}
|
||||
*/
|
||||
createNewTask(task: TaskDetailsModel): Observable<TaskDetailsModel> {
|
||||
return Observable.fromPromise(this.callApiCreateTask(task))
|
||||
.map(res => res)
|
||||
.map((response: TaskDetailsModel) => {
|
||||
return new TaskDetailsModel(response);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign task to user/group
|
||||
* @param taskId - string
|
||||
* @param requestNode - any
|
||||
* @returns {TaskDetailsModel}
|
||||
*/
|
||||
assignTask(taskId: string, requestNode: any): Observable<TaskDetailsModel> {
|
||||
let assignee = {assignee: requestNode.id};
|
||||
return Observable.fromPromise(this.callApiAssignTask(taskId, assignee))
|
||||
.map(res => res)
|
||||
.map((response: TaskDetailsModel) => {
|
||||
return new TaskDetailsModel(response);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
assignTaskByUserId(taskId: string, userId: number): Observable<TaskDetailsModel> {
|
||||
let assignee = {assignee: userId};
|
||||
return Observable.fromPromise(this.callApiAssignTask(taskId, assignee))
|
||||
.map(res => res)
|
||||
.map((response: TaskDetailsModel) => {
|
||||
return new TaskDetailsModel(response);
|
||||
}).catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a task
|
||||
* @param id - taskId
|
||||
*/
|
||||
claimTask(taskId: string): Observable<TaskDetailsModel> {
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.claimTask(taskId))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unclaim a task
|
||||
* @param id - taskId
|
||||
*/
|
||||
unclaimTask(taskId: string): Observable<TaskDetailsModel> {
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.unclaimTask(taskId))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update due date
|
||||
* @param dueDate - the new due date
|
||||
*/
|
||||
updateTask(taskId: any, updated): Observable<TaskDetailsModel> {
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.updateTask(taskId, updated))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the Task Audit information as a pdf
|
||||
* @param taskId - the task id
|
||||
*/
|
||||
fetchTaskAuditPdfById(taskId: string): Observable<Blob> {
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.getTaskAuditPdf(taskId))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch the Task Audit information in a json format
|
||||
* @param taskId - the task id
|
||||
*/
|
||||
fetchTaskAuditJsonById(taskId: string): Observable<any> {
|
||||
return Observable.fromPromise(this.apiService.getInstance().activiti.taskApi.getTaskAuditJson(taskId))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
private callApiTasksFiltered(requestNode: TaskQueryRequestRepresentationModel) {
|
||||
return this.apiService.getInstance().activiti.taskApi.listTasks(requestNode);
|
||||
}
|
||||
|
||||
private callApiTaskDetails(taskId: string) {
|
||||
return this.apiService.getInstance().activiti.taskApi.getTask(taskId);
|
||||
}
|
||||
|
||||
private callApiAddTask(task: TaskDetailsModel) {
|
||||
return this.apiService.getInstance().activiti.taskApi.addSubtask(task.parentTaskId, task);
|
||||
}
|
||||
|
||||
private callApiDeleteTask(taskId: string) {
|
||||
return this.apiService.getInstance().activiti.taskApi.deleteTask(taskId);
|
||||
}
|
||||
|
||||
private callApiTaskChecklist(taskId: string) {
|
||||
return this.apiService.getInstance().activiti.taskApi.getChecklist(taskId);
|
||||
}
|
||||
|
||||
private callApiCreateTask(task: TaskDetailsModel) {
|
||||
return this.apiService.getInstance().activiti.taskApi.createNewTask(task);
|
||||
}
|
||||
|
||||
private callApiAssignTask(taskId: string, requestNode: any) {
|
||||
return this.apiService.getInstance().activiti.taskApi.assignTask(taskId, requestNode);
|
||||
}
|
||||
|
||||
private handleError(error: any) {
|
||||
this.logService.error(error);
|
||||
this.tasksListSubject.error(error);
|
||||
return Observable.throw(error || 'Server error');
|
||||
}
|
||||
|
||||
}
|
87
lib/process-services/task-list/task-list.module.ts
Normal file
87
lib/process-services/task-list/task-list.module.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommonModule, DatePipe } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FormModule } from '@alfresco/core';
|
||||
|
||||
import { CardViewModule, DataColumnModule, DataTableModule, DirectiveModule, InfoDrawerModule } from '@alfresco/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { MaterialModule } from '../material.module';
|
||||
import { PeopleModule } from '../people';
|
||||
import { CommentsModule } from '../comments';
|
||||
import { ProcessUploadService } from './services/process-upload.service';
|
||||
import { TaskListService } from './services/tasklist.service';
|
||||
import { TaskFilterService } from './services/task-filter.service';
|
||||
|
||||
import { ChecklistComponent } from './components/checklist.component';
|
||||
import { NoTaskDetailsTemplateDirective } from './components/no-task-detail-template.directive';
|
||||
import { StartTaskComponent } from './components/start-task.component';
|
||||
import { TaskAuditDirective } from './components/task-audit.directive';
|
||||
import { TaskDetailsComponent } from './components/task-details.component';
|
||||
import { TaskFiltersComponent } from './components/task-filters.component';
|
||||
import { TaskHeaderComponent } from './components/task-header.component';
|
||||
import { TaskListComponent } from './components/task-list.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
CardViewModule,
|
||||
DataTableModule,
|
||||
DataColumnModule,
|
||||
DirectiveModule,
|
||||
FormModule,
|
||||
FlexLayoutModule,
|
||||
InfoDrawerModule,
|
||||
MaterialModule,
|
||||
TranslateModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
PeopleModule,
|
||||
CommentsModule
|
||||
],
|
||||
declarations: [
|
||||
NoTaskDetailsTemplateDirective,
|
||||
TaskFiltersComponent,
|
||||
TaskListComponent,
|
||||
TaskDetailsComponent,
|
||||
TaskAuditDirective,
|
||||
ChecklistComponent,
|
||||
TaskHeaderComponent,
|
||||
StartTaskComponent
|
||||
],
|
||||
providers: [
|
||||
TaskListService,
|
||||
TaskFilterService,
|
||||
ProcessUploadService,
|
||||
DatePipe
|
||||
],
|
||||
exports: [
|
||||
NoTaskDetailsTemplateDirective,
|
||||
TaskFiltersComponent,
|
||||
TaskListComponent,
|
||||
TaskDetailsComponent,
|
||||
TaskAuditDirective,
|
||||
ChecklistComponent,
|
||||
TaskHeaderComponent,
|
||||
StartTaskComponent
|
||||
]
|
||||
})
|
||||
export class TaskListModule {}
|
Reference in New Issue
Block a user