New packages org (#2639)

New packages org
This commit is contained in:
Eugenio Romano
2017-11-16 14:12:52 +00:00
committed by GitHub
parent 6a24c6ef75
commit a52bb5600a
1984 changed files with 17179 additions and 40423 deletions

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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' }
});
});
});
});

View 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 = '';
}
}

View File

@@ -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);
});
});

View 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.
*/
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;
}
}

View File

@@ -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>

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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();
});
});

View File

@@ -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;
}
}
}
}

View File

@@ -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) {
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}
}

View File

@@ -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();
});
});
});

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}));
});

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;
}
}
}

View File

@@ -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');
});
});

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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)
});
});
});
});

View 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));
}
}

View 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';

View 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;
}
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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 [];
}

View 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
}
]
};

View 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 || {};
}
}

View 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;
}
}

View 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';

View 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.
*/
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');
}
}

View File

@@ -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' }
})
});
});
});
});

View 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');
}
}

View 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)
});
});
});
});

View 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');
}
}

View 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 {}