mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-31 17:38:48 +00:00
Merge pull request #993 from Alfresco/dev-valbano-893
Fix People add on Task details
This commit is contained in:
@@ -19,6 +19,8 @@ import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { CoreModule } from 'ng2-alfresco-core';
|
||||
import { DataTableModule } from 'ng2-alfresco-datatable';
|
||||
import { ActivitiFormModule } from 'ng2-activiti-form';
|
||||
import { ActivitiPeopleService } from './src/services/activiti-people.service';
|
||||
import { ActivitiTaskListService } from './src/services/activiti-tasklist.service';
|
||||
|
||||
import {
|
||||
ActivitiApps,
|
||||
@@ -30,11 +32,10 @@ import {
|
||||
ActivitiComments,
|
||||
ActivitiPeople,
|
||||
ActivitiTaskHeader,
|
||||
ActivitiStartProcessButton
|
||||
ActivitiStartTaskButton,
|
||||
ActivitiPeopleSearch
|
||||
} from './src/components/index';
|
||||
|
||||
import { ActivitiTaskListService } from './src/services/activiti-tasklist.service';
|
||||
|
||||
export * from './src/components/index';
|
||||
export * from './src/services/activiti-tasklist.service';
|
||||
export * from './src/models/filter.model';
|
||||
@@ -49,11 +50,13 @@ export const ACTIVITI_TASKLIST_DIRECTIVES: any[] = [
|
||||
ActivitiComments,
|
||||
ActivitiPeople,
|
||||
ActivitiTaskHeader,
|
||||
ActivitiStartProcessButton
|
||||
ActivitiStartTaskButton,
|
||||
ActivitiPeopleSearch
|
||||
];
|
||||
|
||||
export const ACTIVITI_TASKLIST_PROVIDERS: any[] = [
|
||||
ActivitiTaskListService
|
||||
ActivitiTaskListService,
|
||||
ActivitiPeopleService
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@@ -1,33 +1,33 @@
|
||||
<span class="activiti-label mdl-badge"
|
||||
<span class="activiti-label mdl-badge" id="checklist-label"
|
||||
[attr.data-badge]="checklist?.length">{{ 'TASK_DETAILS.LABELS.CHECKLIST' | translate }}</span>
|
||||
<div id="addChecklist" (click)="showDialog()" class="icon material-icons">add</div>
|
||||
<div class="mdl-tooltip" for="addChecklist">
|
||||
<div *ngIf="!readOnly" id="addChecklist" (click)="showDialog()" id="add-checklist" class="icon material-icons">add</div>
|
||||
<div *ngIf="!readOnly" class="mdl-tooltip" for="add-checklist">
|
||||
Add a checklist
|
||||
</div>
|
||||
<div class="menu-container" *ngIf="checklist?.length > 0">
|
||||
<ul class='mdl-list'>
|
||||
<li class="mdl-list__item" *ngFor="let check of checklist">
|
||||
<span class="mdl-list__item-primary-content">
|
||||
<span class="mdl-list__item-primary-content" id="check-{{check.id}}">
|
||||
<i class="material-icons mdl-list__item-icon">done</i>
|
||||
{{check.name}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="checklist?.length === 0">
|
||||
<div *ngIf="checklist?.length === 0" id="checklist-none-message">
|
||||
{{ 'TASK_DETAILS.CHECKLIST.NONE' | translate }}
|
||||
</div>
|
||||
|
||||
<dialog class="mdl-dialog" #dialog>
|
||||
<h4 class="mdl-dialog__title">New Task</h4>
|
||||
<dialog class="mdl-dialog" id="checklist-dialog" #dialog>
|
||||
<h4 class="mdl-dialog__title" id="add-checklist-title">New Check</h4>
|
||||
<div class="mdl-dialog__content">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
|
||||
<input class="mdl-textfield__input" type="text" [(ngModel)]="taskName" id="task" />
|
||||
<label class="mdl-textfield__label" for="task">Name</label>
|
||||
<input class="mdl-textfield__input" type="text" [(ngModel)]="taskName" id="checklist-name"/>
|
||||
<label class="mdl-textfield__label" for="checklist-name">Name</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-dialog__actions">
|
||||
<button type="button" (click)="add()" class="mdl-button">Add Checklist</button>
|
||||
<button type="button" (click)="cancel()" class="mdl-button close">Cancel</button>
|
||||
<button type="button" id="add-check" (click)="add()" class="mdl-button">Add Checklist</button>
|
||||
<button type="button" id="close-check-dialog" (click)="cancel()" class="mdl-button close">Cancel</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</dialog>
|
||||
|
@@ -0,0 +1,172 @@
|
||||
/*!
|
||||
* @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 {
|
||||
CoreModule,
|
||||
AlfrescoTranslationService
|
||||
} from 'ng2-alfresco-core';
|
||||
import { SimpleChange } from '@angular/core';
|
||||
import { ActivitiTaskListService } from '../services/activiti-tasklist.service';
|
||||
import { ActivitiChecklist } from './activiti-checklist.component';
|
||||
import { TranslationMock } from '../assets/translation.service.mock';
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const fakeTaskDetail = new TaskDetailsModel({
|
||||
id: 'fake-check-id',
|
||||
name: 'fake-check-name'
|
||||
});
|
||||
|
||||
describe('Activiti Checklist Component', () => {
|
||||
|
||||
let checklistComponent: ActivitiChecklist;
|
||||
let fixture: ComponentFixture<ActivitiChecklist>;
|
||||
let element: HTMLElement;
|
||||
let showChecklistDialog, closeCheckDialogButton;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreModule],
|
||||
declarations: [ActivitiChecklist],
|
||||
providers: [
|
||||
{provide: AlfrescoTranslationService, useClass: TranslationMock},
|
||||
ActivitiTaskListService]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(ActivitiChecklist);
|
||||
checklistComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show people 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('TASK_DETAILS.CHECKLIST.NONE');
|
||||
});
|
||||
|
||||
describe('when interact with people 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(element.querySelector('#checklist-dialog')).not.toBeNull();
|
||||
expect(element.querySelector('#add-checklist-title')).not.toBeNull();
|
||||
expect(element.querySelector('#add-checklist-title').textContent).toContain('New Check');
|
||||
});
|
||||
|
||||
it('should close dialog when clicked on cancel', () => {
|
||||
showChecklistDialog.click();
|
||||
expect(element.querySelector('#checklist-dialog').getAttribute('open')).not.toBeNull();
|
||||
closeCheckDialogButton.click();
|
||||
expect(element.querySelector('#checklist-dialog').getAttribute('open')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
it('should add checklist', async(() => {
|
||||
showChecklistDialog.click();
|
||||
let addButtonDialog = <HTMLElement> element.querySelector('#add-check');
|
||||
addButtonDialog.click();
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {id: 'fake-check-added-id', name: 'fake-check-added-name'}
|
||||
});
|
||||
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 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');
|
||||
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');
|
||||
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('TASK_DETAILS.CHECKLIST.NONE');
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit, ViewChild, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { AlfrescoTranslationService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
|
||||
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
|
||||
import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { Observer, Observable } from 'rxjs/Rx';
|
||||
@@ -33,6 +33,9 @@ export class ActivitiChecklist implements OnInit, OnChanges {
|
||||
@Input()
|
||||
taskId: string;
|
||||
|
||||
@Input()
|
||||
readOnly: boolean = false;
|
||||
|
||||
@ViewChild('dialog')
|
||||
dialog: any;
|
||||
|
||||
@@ -48,8 +51,7 @@ export class ActivitiChecklist implements OnInit, OnChanges {
|
||||
* @param auth
|
||||
* @param translate
|
||||
*/
|
||||
constructor(private auth: AlfrescoAuthenticationService,
|
||||
private translate: AlfrescoTranslationService,
|
||||
constructor(private translate: AlfrescoTranslationService,
|
||||
private activitiTaskList: ActivitiTaskListService) {
|
||||
|
||||
if (translate) {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<span class="activiti-label mdl-badge"
|
||||
[attr.data-badge]="comments?.length">{{ 'TASK_DETAILS.LABELS.COMMENTS' |translate }}</span>
|
||||
<div id="addComment" (click)="showDialog()" class="icon material-icons">add</div>
|
||||
<div class="mdl-tooltip" for="addComment">
|
||||
<div *ngIf="!readOnly" id="addComment" (click)="showDialog()" class="icon material-icons">add</div>
|
||||
<div *ngIf="!readOnly" class="mdl-tooltip" for="addComment">
|
||||
Add a comment
|
||||
</div>
|
||||
|
||||
@@ -32,4 +32,4 @@
|
||||
<button type="button" (click)="add()" class="mdl-button">Add Comment</button>
|
||||
<button type="button" (click)="cancel()" class="mdl-button close">Cancel</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</dialog>
|
||||
|
@@ -33,6 +33,9 @@ export class ActivitiComments implements OnInit, OnChanges {
|
||||
@Input()
|
||||
taskId: string;
|
||||
|
||||
@Input()
|
||||
readOnly: boolean = false;
|
||||
|
||||
@ViewChild('dialog')
|
||||
dialog: any;
|
||||
|
||||
|
@@ -0,0 +1,18 @@
|
||||
:host {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.activiti-label {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.material-icons.people-search__icon:hover {
|
||||
color: rgb(255, 152, 0);
|
||||
}
|
||||
|
||||
.fix-element-user-list{
|
||||
padding-top: 0px;
|
||||
padding-right: 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-left: 0px;
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
|
||||
<input class="mdl-textfield__input" type="text" id="userSearchText" [value]=""
|
||||
[formControl]="searchUser"/>
|
||||
<label class="mdl-textfield__label" for="userSearchText">Search user</label>
|
||||
</div>
|
||||
<ul class='mdl-list'>
|
||||
<li class="mdl-list__item fix-element-user-list" *ngFor="let user of userList">
|
||||
<span class="mdl-list__item-primary-content mdl-button mdl-js-button mdl-js-ripple-effect"
|
||||
(click)="onRowClick(user)" id="user-{{user.id}}">
|
||||
<i class="material-icons md-light people-search__icon">face</i>
|
||||
{{getDisplayUser(user)}}
|
||||
</span>
|
||||
</li>
|
||||
<div *ngIf="userList?.length === 0" id="no-user-found">
|
||||
No user found to involve
|
||||
</div>
|
||||
</ul>
|
@@ -0,0 +1,137 @@
|
||||
/*!
|
||||
* @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 {
|
||||
CoreModule,
|
||||
AlfrescoTranslationService
|
||||
} from 'ng2-alfresco-core';
|
||||
import { ActivitiPeopleSearch } from './activiti-people-search.component';
|
||||
import { TranslationMock } from '../assets/translation.service.mock';
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { User } from '../models/user.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const fakeUser: User = new User({
|
||||
id: '1',
|
||||
firstName: 'fake-name',
|
||||
lastName: 'fake-last',
|
||||
email: 'fake@mail.com'
|
||||
});
|
||||
|
||||
const fakeSecondUser: User = new User({
|
||||
id: '2',
|
||||
firstName: 'fake-involve-name',
|
||||
lastName: 'fake-involve-last',
|
||||
email: 'fake-involve@mail.com'
|
||||
});
|
||||
|
||||
describe('Activiti People Search', () => {
|
||||
|
||||
let activitiPeopleSearchComponent: ActivitiPeopleSearch;
|
||||
let fixture: ComponentFixture<ActivitiPeopleSearch>;
|
||||
let element: HTMLElement;
|
||||
let componentHandler;
|
||||
let userArray = [fakeUser, fakeSecondUser];
|
||||
let searchInput;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreModule],
|
||||
declarations: [ActivitiPeopleSearch],
|
||||
providers: [
|
||||
{provide: AlfrescoTranslationService, useClass: TranslationMock}]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(ActivitiPeopleSearch);
|
||||
activitiPeopleSearchComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', [
|
||||
'upgradeAllRegistered'
|
||||
]);
|
||||
|
||||
window['componentHandler'] = componentHandler;
|
||||
activitiPeopleSearchComponent.results = Observable.of([]);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show input search text', () => {
|
||||
expect(element.querySelector('#userSearchText')).toBeDefined();
|
||||
expect(element.querySelector('#userSearchText')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show no user found to involve message', () => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#no-user-found')).not.toBeNull();
|
||||
expect(element.querySelector('#no-user-found').textContent).toContain('No user found to involve');
|
||||
});
|
||||
});
|
||||
|
||||
it('should show user which can be involved ', (done) => {
|
||||
activitiPeopleSearchComponent.onSearch.subscribe(() => {
|
||||
activitiPeopleSearchComponent.results = Observable.of(userArray);
|
||||
activitiPeopleSearchComponent.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#user-1')).not.toBeNull();
|
||||
expect(element.querySelector('#user-1').textContent)
|
||||
.toContain('fake-name - fake-last');
|
||||
expect(element.querySelector('#user-2')).not.toBeNull();
|
||||
expect(element.querySelector('#user-2').textContent)
|
||||
.toContain('fake-involve-name - fake-involve-last');
|
||||
done();
|
||||
});
|
||||
});
|
||||
searchInput = element.querySelector('#userSearchText');
|
||||
searchInput.value = 'fake-search';
|
||||
activitiPeopleSearchComponent.searchUser.markAsDirty();
|
||||
searchInput.dispatchEvent(new Event('input'));
|
||||
});
|
||||
|
||||
it('should send an event when an user is clicked', async(() => {
|
||||
activitiPeopleSearchComponent.onRowClicked.subscribe((user) => {
|
||||
expect(user).toBeDefined();
|
||||
expect(user.firstName).toBe('fake-name');
|
||||
});
|
||||
activitiPeopleSearchComponent.results = Observable.of(userArray);
|
||||
activitiPeopleSearchComponent.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
let userToSelect = <HTMLElement> element.querySelector('#user-1');
|
||||
userToSelect.click();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should remove clicked user', async(() => {
|
||||
activitiPeopleSearchComponent.results = Observable.of(userArray);
|
||||
activitiPeopleSearchComponent.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
let userToSelect = <HTMLElement> element.querySelector('#user-1');
|
||||
userToSelect.click();
|
||||
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#user-1')).toBeNull();
|
||||
});
|
||||
}));
|
||||
});
|
@@ -0,0 +1,93 @@
|
||||
/*!
|
||||
* @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, Input, Output, EventEmitter, OnInit, AfterViewInit } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { User } from '../models/user.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
|
||||
|
||||
declare let componentHandler: any;
|
||||
|
||||
@Component({
|
||||
selector: 'activiti-people-search',
|
||||
moduleId: module.id,
|
||||
templateUrl: './activiti-people-search.component.html',
|
||||
styleUrls: ['./activiti-people-search.component.css']
|
||||
})
|
||||
|
||||
export class ActivitiPeopleSearch implements OnInit, AfterViewInit {
|
||||
|
||||
@Input()
|
||||
results: Observable<User[]>;
|
||||
|
||||
@Output()
|
||||
onSearch: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
onRowClicked: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
searchUser: FormControl = new FormControl();
|
||||
|
||||
userList: User[] = [];
|
||||
|
||||
constructor(private translate: AlfrescoTranslationService) {
|
||||
if (translate) {
|
||||
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
|
||||
}
|
||||
|
||||
this.searchUser
|
||||
.valueChanges
|
||||
.debounceTime(200)
|
||||
.subscribe((event) => {
|
||||
this.onSearch.emit(event);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.results.subscribe((list) => {
|
||||
this.userList = list;
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.setupMaterialComponents(componentHandler);
|
||||
}
|
||||
|
||||
setupMaterialComponents(handler?: any): boolean {
|
||||
// workaround for MDL issues with dynamic components
|
||||
let isUpgraded: boolean = false;
|
||||
if (handler) {
|
||||
handler.upgradeAllRegistered();
|
||||
isUpgraded = true;
|
||||
}
|
||||
return isUpgraded;
|
||||
}
|
||||
|
||||
onRowClick(userClicked: User) {
|
||||
this.onRowClicked.emit(userClicked);
|
||||
this.userList = this.userList.filter((user) => {
|
||||
return user.id !== userClicked.id;
|
||||
});
|
||||
}
|
||||
|
||||
getDisplayUser(user: User): string {
|
||||
let firstName = user.firstName && user.firstName !== 'null' ? user.firstName : 'N/A';
|
||||
let lastName = user.lastName && user.lastName !== 'null' ? user.lastName : 'N/A';
|
||||
return firstName + ' - ' + lastName;
|
||||
}
|
||||
}
|
@@ -6,6 +6,10 @@
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.material-icons:hover {
|
||||
.material-icons.people__icon:hover {
|
||||
color: rgb(255, 152, 0);
|
||||
}
|
||||
|
||||
.add-people-dialog__content {
|
||||
padding: 20px 24px 2px;
|
||||
}
|
||||
|
@@ -1,33 +1,36 @@
|
||||
<span class="activiti-label mdl-badge"
|
||||
<span class="activiti-label mdl-badge" id="people-title"
|
||||
[attr.data-badge]="people?.length">{{ 'TASK_DETAILS.LABELS.PEOPLE' | translate }}</span>
|
||||
<div id="addPeople" (click)="showDialog()" class="icon material-icons">add</div>
|
||||
<div class="mdl-tooltip" for="addPeople">
|
||||
<div *ngIf="!readOnly" id="addPeople" (click)="showDialog()" class="icon material-icons people__icon">add</div>
|
||||
<div *ngIf="!readOnly" class="mdl-tooltip" data-mdl-for="addPeople">
|
||||
Add a person
|
||||
</div>
|
||||
<div class="menu-container" *ngIf="people?.length > 0">
|
||||
<ul class='mdl-list'>
|
||||
<li class="mdl-list__item" *ngFor="let user of people">
|
||||
<span class="mdl-list__item-primary-content">
|
||||
<i class="material-icons mdl-list__item-icon">face</i>
|
||||
{{user.firstName}}
|
||||
<i class="material-icons mdl-list__item-icon">face</i>
|
||||
<span id="user-{{user.id}}">{{getDisplayUser(user)}}</span>
|
||||
</span>
|
||||
<a *ngIf="!readOnly" class="mdl-list__item-secondary-action">
|
||||
<i id="remove" class="material-icons people__icon"
|
||||
(click)="removeInvolvedUser(user)">delete</i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="people?.length === 0">
|
||||
<div *ngIf="people?.length === 0" id="no-people-label">
|
||||
{{ 'TASK_DETAILS.PEOPLE.NONE' | translate }}
|
||||
</div>
|
||||
|
||||
<dialog class="mdl-dialog" #dialog>
|
||||
<h4 class="mdl-dialog__title">New User</h4>
|
||||
<div class="mdl-dialog__content">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
|
||||
<input class="mdl-textfield__input" type="text" id="people" />
|
||||
<label class="mdl-textfield__label" for="people">Name</label>
|
||||
</div>
|
||||
<dialog class="mdl-dialog" id="add-people-dialog" #dialog>
|
||||
<h4 class="mdl-dialog__title" id="add-people-dialog-title">Involve User</h4>
|
||||
<div class="mdl-dialog__content add-people-dialog__content">
|
||||
<activiti-people-search (onSearch)="searchUser($event)"
|
||||
(onRowClicked)="involveUser($event)"
|
||||
[results]="peopleSearch$">
|
||||
</activiti-people-search>
|
||||
</div>
|
||||
<div class="mdl-dialog__actions">
|
||||
<button type="button" (click)="add()" class="mdl-button">Add User</button>
|
||||
<button type="button" (click)="cancel()" class="mdl-button close">Cancel</button>
|
||||
<button type="button" id="close-people-dialog" (click)="cancel()" class="mdl-button close">Cancel</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
@@ -0,0 +1,252 @@
|
||||
/*!
|
||||
* @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 {
|
||||
CoreModule,
|
||||
AlfrescoTranslationService
|
||||
} from 'ng2-alfresco-core';
|
||||
import { ActivitiPeopleService } from '../services/activiti-people.service';
|
||||
import { ActivitiPeople } from './activiti-people.component';
|
||||
import { ActivitiPeopleSearch } from './activiti-people-search.component';
|
||||
import { TranslationMock } from '../assets/translation.service.mock';
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { User } from '../models/user.model';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const fakeUser: User = new User({
|
||||
id: 'fake-id',
|
||||
firstName: 'fake-name',
|
||||
lastName: 'fake-last',
|
||||
email: 'fake@mail.com'
|
||||
});
|
||||
|
||||
const fakeUserToInvolve: User = new User({
|
||||
id: 'fake-involve-id',
|
||||
firstName: 'fake-involve-name',
|
||||
lastName: 'fake-involve-last',
|
||||
email: 'fake-involve@mail.com'
|
||||
});
|
||||
|
||||
describe('Activiti People Component', () => {
|
||||
|
||||
let activitiPeopleComponent: ActivitiPeople;
|
||||
let fixture: ComponentFixture<ActivitiPeople>;
|
||||
let element: HTMLElement;
|
||||
let componentHandler;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreModule],
|
||||
declarations: [ActivitiPeople, ActivitiPeopleSearch],
|
||||
providers: [
|
||||
{provide: AlfrescoTranslationService, useClass: TranslationMock},
|
||||
ActivitiPeopleService]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(ActivitiPeople);
|
||||
activitiPeopleComponent = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
componentHandler = jasmine.createSpyObj('componentHandler', [
|
||||
'upgradeAllRegistered'
|
||||
]);
|
||||
|
||||
window['componentHandler'] = componentHandler;
|
||||
});
|
||||
}));
|
||||
|
||||
it('should show people component title', () => {
|
||||
expect(element.querySelector('#people-title')).toBeDefined();
|
||||
expect(element.querySelector('#people-title')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should show no people involved message', () => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
expect(element.querySelector('#no-people-label')).not.toBeNull();
|
||||
expect(element.querySelector('#no-people-label').textContent).toContain('TASK_DETAILS.PEOPLE.NONE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when interact with people dialog', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
activitiPeopleComponent.taskId = 'fake-task-id';
|
||||
activitiPeopleComponent.people = [];
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should show dialog when clicked on add', () => {
|
||||
expect(element.querySelector('#addPeople')).not.toBeNull();
|
||||
activitiPeopleComponent.showDialog();
|
||||
|
||||
expect(element.querySelector('#add-people-dialog')).not.toBeNull();
|
||||
expect(element.querySelector('#add-people-dialog-title')).not.toBeNull();
|
||||
expect(element.querySelector('#add-people-dialog-title').textContent).toContain('Involve User');
|
||||
});
|
||||
|
||||
it('should close dialog when clicked on cancel', () => {
|
||||
activitiPeopleComponent.showDialog();
|
||||
expect(element.querySelector('#addPeople')).not.toBeNull();
|
||||
activitiPeopleComponent.cancel();
|
||||
let dialogWindow = <HTMLElement> element.querySelector('#add-people-dialog');
|
||||
expect(dialogWindow.getAttribute('open')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are involved people', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
activitiPeopleComponent.taskId = 'fake-task-id';
|
||||
activitiPeopleComponent.people.push(fakeUser);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should show people involved', () => {
|
||||
expect(element.querySelector('#user-fake-id')).not.toBeNull();
|
||||
expect(element.querySelector('#user-fake-id').textContent).toContain('fake-name');
|
||||
expect(element.querySelector('#user-fake-id').textContent).toContain('fake-last');
|
||||
});
|
||||
|
||||
it('should remove pepole involved', async(() => {
|
||||
activitiPeopleComponent.removeInvolvedUser(fakeUser);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200
|
||||
});
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#user-fake-id')).toBeNull();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should involve pepole', async(() => {
|
||||
activitiPeopleComponent.involveUser(fakeUserToInvolve);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200
|
||||
});
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#user-fake-involve-id')).not.toBeNull();
|
||||
expect(element.querySelector('#user-fake-involve-id').textContent)
|
||||
.toBe('fake-involve-name fake-involve-last');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should return an observable with user search results', (done) => {
|
||||
activitiPeopleComponent.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();
|
||||
});
|
||||
activitiPeopleComponent.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) => {
|
||||
activitiPeopleComponent.peopleSearch$.subscribe((users) => {
|
||||
expect(users.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
activitiPeopleComponent.searchUser('fake-search-word');
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are errors on service call', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should log error message when search fails', async(() => {
|
||||
console.log = jasmine.createSpy('log');
|
||||
activitiPeopleComponent.peopleSearch$.subscribe(() => {
|
||||
expect(console.log).toHaveBeenCalledWith('Could not load users');
|
||||
});
|
||||
activitiPeopleComponent.searchUser('fake-search');
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not remove user if remove involved user fail', async(() => {
|
||||
activitiPeopleComponent.people.push(fakeUser);
|
||||
fixture.detectChanges();
|
||||
activitiPeopleComponent.removeInvolvedUser(fakeUser);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#user-fake-id')).not.toBeNull();
|
||||
expect(element.querySelector('#user-fake-id').textContent)
|
||||
.toBe('fake-name fake-last');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should not involve user if involve user fail', async(() => {
|
||||
activitiPeopleComponent.involveUser(fakeUserToInvolve);
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
fixture.whenStable()
|
||||
.then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#user-fake-id')).toBeNull();
|
||||
expect(element.querySelector('#no-people-label').textContent).toContain('TASK_DETAILS.PEOPLE.NONE');
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
@@ -15,10 +15,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { AlfrescoTranslationService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
|
||||
import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
|
||||
import { User } from '../models/user.model';
|
||||
import { Observer, Observable } from 'rxjs/Rx';
|
||||
import { ActivitiPeopleService } from '../services/activiti-people.service';
|
||||
|
||||
@Component({
|
||||
selector: 'activiti-people',
|
||||
@@ -26,56 +27,79 @@ import { Observer, Observable } from 'rxjs/Rx';
|
||||
templateUrl: './activiti-people.component.html',
|
||||
styleUrls: ['./activiti-people.component.css']
|
||||
})
|
||||
export class ActivitiPeople implements OnInit {
|
||||
export class ActivitiPeople {
|
||||
|
||||
@Input()
|
||||
people: User [] = [];
|
||||
|
||||
@Input()
|
||||
taskId: string = '';
|
||||
|
||||
@Input()
|
||||
readOnly: boolean = false;
|
||||
|
||||
@ViewChild('dialog')
|
||||
dialog: any;
|
||||
|
||||
private peopleObserver: Observer<User>;
|
||||
people$: Observable<User>;
|
||||
private peopleSearchObserver: Observer<User[]>;
|
||||
peopleSearch$: Observable<User[]>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param auth
|
||||
* @param translate
|
||||
* @param people service
|
||||
*/
|
||||
constructor(private auth: AlfrescoAuthenticationService,
|
||||
private translate: AlfrescoTranslationService) {
|
||||
|
||||
constructor(private translate: AlfrescoTranslationService,
|
||||
private peopleService: ActivitiPeopleService) {
|
||||
if (translate) {
|
||||
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
|
||||
}
|
||||
this.people$ = new Observable<User>(observer => this.peopleObserver = observer).share();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.people$.subscribe((user: User) => {
|
||||
this.people.push(user);
|
||||
});
|
||||
this.peopleSearch$ = new Observable<User[]>(observer => this.peopleSearchObserver = observer).share();
|
||||
}
|
||||
|
||||
public showDialog() {
|
||||
if (!this.dialog.nativeElement.showModal) {
|
||||
dialogPolyfill.registerDialog(this.dialog.nativeElement);
|
||||
}
|
||||
if (this.dialog) {
|
||||
if (!this.dialog.nativeElement.showModal) {
|
||||
dialogPolyfill.registerDialog(this.dialog.nativeElement);
|
||||
}
|
||||
this.dialog.nativeElement.showModal();
|
||||
}
|
||||
}
|
||||
|
||||
public add() {
|
||||
alert('add people');
|
||||
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
if (this.dialog) {
|
||||
this.dialog.nativeElement.close();
|
||||
this.peopleSearchObserver.next([]);
|
||||
}
|
||||
}
|
||||
|
||||
searchUser(searchedWord: string) {
|
||||
this.peopleService.getWorkflowUsers(this.taskId, searchedWord)
|
||||
.subscribe((users) => {
|
||||
this.peopleSearchObserver.next(users);
|
||||
}, error => console.log('Could not load users'));
|
||||
}
|
||||
|
||||
involveUser(user: User) {
|
||||
this.peopleService.involveUserWithTask(this.taskId, user.id.toString())
|
||||
.subscribe(() => {
|
||||
this.people.push(user);
|
||||
}, error => console.error('Impossible to involve user with task'));
|
||||
}
|
||||
|
||||
removeInvolvedUser(user: User) {
|
||||
this.peopleService.removeInvolvedUser(this.taskId, user.id.toString())
|
||||
.subscribe(() => {
|
||||
this.people = this.people.filter((involvedUser) => {
|
||||
return involvedUser.id !== user.id;
|
||||
});
|
||||
}, error => console.error('Impossible to remove involved user from task'));
|
||||
}
|
||||
|
||||
getDisplayUser(user: User): string {
|
||||
let firstName = user.firstName && user.firstName !== 'null' ? user.firstName : 'N/A';
|
||||
let lastName = user.lastName && user.lastName !== 'null' ? user.lastName : 'N/A';
|
||||
return firstName + ' ' + lastName;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,19 +1,27 @@
|
||||
<button type="button" (click)="showDialog()" class="mdl-button">{{'START_TASK.BUTTON'|translate}}</button>
|
||||
<button type="button" (click)="showDialog()" class="mdl-button" id="start-task-button">
|
||||
{{'START_TASK.BUTTON'|translate}}
|
||||
</button>
|
||||
|
||||
<dialog class="mdl-dialog" #dialog>
|
||||
<h4 class="mdl-dialog__title">{{'START_TASK.DIALOG.TITLE'|translate}}</h4>
|
||||
<dialog class="mdl-dialog" id="start-task-dialog" #dialog>
|
||||
<h4 class="mdl-dialog__title" id="start-task-dialog-title">{{'START_TASK.DIALOG.TITLE'|translate}}</h4>
|
||||
<div class="mdl-dialog__content">
|
||||
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
|
||||
<input class="mdl-textfield__input" type="text" [(ngModel)]="name" id="taskName" />
|
||||
<input class="mdl-textfield__input" type="text" [(ngModel)]="name" id="taskName"/>
|
||||
<label class="mdl-textfield__label" for="taskName">{{'START_TASK.DIALOG.LABEL.NAME'|translate}}</label>
|
||||
</div>
|
||||
<div class="mdl-textfield mdl-js-textfield">
|
||||
<textarea class="mdl-textfield__input" type="text" [(ngModel)]="description" rows="3" id="taskDescription"></textarea>
|
||||
<label class="mdl-textfield__label" for="taskDescription">{{'START_TASK.DIALOG.LABEL.DESCRIPTION'|translate}}</label>
|
||||
<textarea class="mdl-textfield__input" type="text" [(ngModel)]="description" rows="3"
|
||||
id="taskDescription"></textarea>
|
||||
<label class="mdl-textfield__label" id="task-description-label"
|
||||
for="taskDescription">{{'START_TASK.DIALOG.LABEL.DESCRIPTION'|translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdl-dialog__actions">
|
||||
<button type="button" (click)="start()" class="mdl-button">{{'START_TASK.DIALOG.ACTION.START'|translate}}</button>
|
||||
<button type="button" (click)="cancel()" class="mdl-button close">{{'START_TASK.DIALOG.ACTION.CANCEL'|translate}}</button>
|
||||
<button type="button" id="button-start" (click)="start()" class="mdl-button">
|
||||
{{'START_TASK.DIALOG.ACTION.START'|translate}}
|
||||
</button>
|
||||
<button type="button" id="button-cancel" (click)="cancel()" class="mdl-button close">
|
||||
{{'START_TASK.DIALOG.ACTION.CANCEL'|translate}}
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
@@ -0,0 +1,124 @@
|
||||
/*!
|
||||
* @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 {
|
||||
CoreModule,
|
||||
AlfrescoTranslationService
|
||||
} from 'ng2-alfresco-core';
|
||||
import { ActivitiTaskListService } from '../services/activiti-tasklist.service';
|
||||
import { ActivitiStartTaskButton } from './activiti-start-task.component';
|
||||
import { TranslationMock } from '../assets/translation.service.mock';
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
describe('Activiti Start Task Component', () => {
|
||||
|
||||
let activitiStartTaskButton: ActivitiStartTaskButton;
|
||||
let fixture: ComponentFixture<ActivitiStartTaskButton>;
|
||||
let element: HTMLElement;
|
||||
let startTaskButton: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CoreModule],
|
||||
declarations: [ActivitiStartTaskButton],
|
||||
providers: [
|
||||
{provide: AlfrescoTranslationService, useClass: TranslationMock},
|
||||
ActivitiTaskListService]
|
||||
}).compileComponents().then(() => {
|
||||
fixture = TestBed.createComponent(ActivitiStartTaskButton);
|
||||
activitiStartTaskButton = fixture.componentInstance;
|
||||
element = fixture.nativeElement;
|
||||
fixture.detectChanges();
|
||||
startTaskButton = <HTMLElement> element.querySelector('#start-task-button');
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should show start task button', () => {
|
||||
expect(element.querySelector('#start-task-button')).toBeDefined();
|
||||
expect(element.querySelector('#start-task-button')).not.toBeNull();
|
||||
expect(element.querySelector('#start-task-button').textContent).toContain('START_TASK.BUTTON');
|
||||
});
|
||||
|
||||
it('should show start dialog on press button', () => {
|
||||
startTaskButton.click();
|
||||
expect(element.querySelector('#start-task-dialog')).not.toBeNull();
|
||||
expect(element.querySelector('#start-task-dialog').getAttribute('open')).not.toBeNull();
|
||||
expect(element.querySelector('#start-task-dialog-title')).not.toBeNull();
|
||||
expect(element.querySelector('#start-task-dialog-title').textContent).toContain('START_TASK.DIALOG.TITLE');
|
||||
});
|
||||
|
||||
it('should close start dialog on cancel button', () => {
|
||||
startTaskButton.click();
|
||||
expect(element.querySelector('#start-task-dialog')).not.toBeNull();
|
||||
expect(element.querySelector('#start-task-dialog').getAttribute('open')).not.toBeNull();
|
||||
let cancelButton = <HTMLElement> element.querySelector('#button-cancel');
|
||||
cancelButton.click();
|
||||
expect(element.querySelector('#start-task-dialog').getAttribute('open')).toBeNull();
|
||||
});
|
||||
|
||||
it('should create new task when start is clicked', () => {
|
||||
activitiStartTaskButton.onSuccess.subscribe(() => {
|
||||
expect(element.querySelector('#start-task-dialog').getAttribute('open')).toBeNull();
|
||||
});
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
startTaskButton.click();
|
||||
activitiStartTaskButton.name = 'fake-name';
|
||||
createTaskButton.click();
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200
|
||||
});
|
||||
});
|
||||
|
||||
it('alert message is showed on start error', () => {
|
||||
spyOn(window, 'alert');
|
||||
activitiStartTaskButton.onSuccess.subscribe(() => {
|
||||
expect(window.alert).toHaveBeenCalledWith('An error occurred while trying to add the task');
|
||||
});
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
startTaskButton.click();
|
||||
activitiStartTaskButton.name = 'fake-name';
|
||||
createTaskButton.click();
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 403
|
||||
});
|
||||
});
|
||||
|
||||
it('should send on success event when the task is started', () => {
|
||||
activitiStartTaskButton.onSuccess.subscribe((res) => {
|
||||
expect(res).toBeDefined();
|
||||
});
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
startTaskButton.click();
|
||||
activitiStartTaskButton.name = 'fake-name';
|
||||
createTaskButton.click();
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
'status': 200,
|
||||
contentType: 'json',
|
||||
responseText: {}
|
||||
});
|
||||
});
|
||||
});
|
@@ -15,8 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { AlfrescoTranslationService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
|
||||
import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
|
||||
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
import { ActivitiTaskListService } from './../services/activiti-tasklist.service';
|
||||
|
||||
@@ -29,7 +29,7 @@ declare let dialogPolyfill: any;
|
||||
templateUrl: './activiti-start-task.component.html',
|
||||
styleUrls: ['./activiti-start-task.component.css']
|
||||
})
|
||||
export class ActivitiStartProcessButton implements OnInit {
|
||||
export class ActivitiStartTaskButton {
|
||||
|
||||
@Input()
|
||||
appId: string;
|
||||
@@ -49,8 +49,7 @@ export class ActivitiStartProcessButton implements OnInit {
|
||||
* @param translate
|
||||
* @param taskService
|
||||
*/
|
||||
constructor(private auth: AlfrescoAuthenticationService,
|
||||
private translate: AlfrescoTranslationService,
|
||||
constructor(private translate: AlfrescoTranslationService,
|
||||
private taskService: ActivitiTaskListService) {
|
||||
|
||||
if (translate) {
|
||||
@@ -58,9 +57,6 @@ export class ActivitiStartProcessButton implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
public start() {
|
||||
if (this.name) {
|
||||
this.taskService.createNewTask(new TaskDetailsModel({
|
||||
|
@@ -12,13 +12,16 @@
|
||||
<activiti-task-header [taskDetails]="taskDetails" #activitiheader></activiti-task-header>
|
||||
<div class="mdl-grid">
|
||||
<div class="mdl-cell mdl-cell--4-col">
|
||||
<activiti-people [people]="taskPeople"></activiti-people>
|
||||
<activiti-people [people]="taskPeople" [readOnly]="readOnlyForm"
|
||||
[taskId]="taskDetails.id"></activiti-people>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--4-col">
|
||||
<activiti-comments [taskId]="taskDetails.id" #activiticomments></activiti-comments>
|
||||
<activiti-comments [readOnly]="readOnlyForm" [taskId]="taskDetails.id"
|
||||
#activiticomments></activiti-comments>
|
||||
</div>
|
||||
<div class="mdl-cell mdl-cell--4-col">
|
||||
<activiti-checklist [taskId]="taskDetails.id" #activitichecklist></activiti-checklist>
|
||||
<activiti-checklist [readOnly]="readOnlyForm" [taskId]="taskDetails.id"
|
||||
#activitichecklist></activiti-checklist>
|
||||
</div>
|
||||
</div>
|
||||
<activiti-form *ngIf="hasFormKey()" [taskId]="taskDetails.id"
|
||||
|
@@ -15,8 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { AlfrescoTranslationService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
|
||||
import { TaskDetailsModel } from '../models/task-details.model';
|
||||
|
||||
declare let componentHandler: any;
|
||||
@@ -27,7 +27,7 @@ declare let componentHandler: any;
|
||||
templateUrl: './activiti-task-header.component.html',
|
||||
styleUrls: ['./activiti-task-header.component.css']
|
||||
})
|
||||
export class ActivitiTaskHeader implements OnInit {
|
||||
export class ActivitiTaskHeader {
|
||||
|
||||
@Input()
|
||||
taskDetails: TaskDetailsModel;
|
||||
@@ -37,16 +37,11 @@ export class ActivitiTaskHeader implements OnInit {
|
||||
* @param auth
|
||||
* @param translate
|
||||
*/
|
||||
constructor(private auth: AlfrescoAuthenticationService,
|
||||
private translate: AlfrescoTranslationService) {
|
||||
constructor(private translate: AlfrescoTranslationService) {
|
||||
|
||||
if (translate) {
|
||||
translate.addTranslationFolder('node_modules/ng2-activiti-tasklist/src');
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -25,3 +25,4 @@ export * from './no-task-detail-template.component';
|
||||
export * from './activiti-filters.component';
|
||||
export * from './activiti-task-details.component';
|
||||
export * from './activiti-start-task.component';
|
||||
export * from './activiti-people-search.component';
|
||||
|
@@ -8,7 +8,10 @@
|
||||
"LABELS": {
|
||||
"ASSIGNEE": "Assegnatario",
|
||||
"DUE": "Scadenza",
|
||||
"FORM": "Form"
|
||||
"FORM": "Form",
|
||||
"PEOPLE": "Persone",
|
||||
"COMMENTS": "Commenti",
|
||||
"CHECKLIST": "Checklist"
|
||||
},
|
||||
"MESSAGES": {
|
||||
"NONE": "Nessun dettaglio task trovato."
|
||||
@@ -25,4 +28,4 @@
|
||||
"NONE": "Nessun filtro task selezionato."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,169 @@
|
||||
/*!
|
||||
* @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 { ReflectiveInjector } from '@angular/core';
|
||||
import {
|
||||
AlfrescoAuthenticationService,
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService
|
||||
} from 'ng2-alfresco-core';
|
||||
import { User } from '../models/user.model';
|
||||
import { ActivitiPeopleService } from './activiti-people.service';
|
||||
|
||||
declare let jasmine: any;
|
||||
|
||||
const firstInvolvedUser: User = new User({
|
||||
id: '1',
|
||||
email: 'fake-user1@fake.com',
|
||||
firstName: 'fakeName1',
|
||||
lastName: 'fakeLast1'
|
||||
});
|
||||
|
||||
const secondInvolvedUser: User = new User({
|
||||
id: '2',
|
||||
email: 'fake-user2@fake.com',
|
||||
firstName: 'fakeName2',
|
||||
lastName: 'fakeLast2'
|
||||
});
|
||||
|
||||
const fakeInvolveUserList: User[] = [firstInvolvedUser, secondInvolvedUser];
|
||||
|
||||
describe('Activiti People Search Service', () => {
|
||||
|
||||
let service, injector, apiService;
|
||||
|
||||
beforeEach(() => {
|
||||
injector = ReflectiveInjector.resolveAndCreate([
|
||||
AlfrescoSettingsService,
|
||||
AlfrescoApiService,
|
||||
AlfrescoAuthenticationService,
|
||||
ActivitiPeopleService
|
||||
]);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = injector.get(ActivitiPeopleService);
|
||||
apiService = injector.get(AlfrescoApiService);
|
||||
});
|
||||
|
||||
it('can instantiate service with authorization', () => {
|
||||
expect(apiService).not.toBeNull('authorization should be provided');
|
||||
let serviceApi = new ActivitiPeopleService(null, apiService);
|
||||
|
||||
expect(serviceApi instanceof ActivitiPeopleService).toBe(true, 'new service should be ok');
|
||||
});
|
||||
|
||||
describe('when user is logged in', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jasmine.Ajax.install();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jasmine.Ajax.uninstall();
|
||||
});
|
||||
|
||||
it('should be able to retrieve people to involve in the task', (done) => {
|
||||
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe(
|
||||
(users: User[]) => {
|
||||
expect(users).toBeDefined();
|
||||
expect(users.length).toBe(2);
|
||||
expect(users[0].id).toEqual('1');
|
||||
expect(users[0].email).toEqual('fake-user1@fake.com');
|
||||
expect(users[0].firstName).toEqual('fakeName1');
|
||||
expect(users[0].lastName).toEqual('fakeLast1');
|
||||
done();
|
||||
});
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {data: fakeInvolveUserList}
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty list when there are no users to involve', (done) => {
|
||||
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe(
|
||||
(users: User[]) => {
|
||||
expect(users).toBeDefined();
|
||||
expect(users.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200,
|
||||
contentType: 'json',
|
||||
responseText: {}
|
||||
});
|
||||
});
|
||||
|
||||
it('getWorkflowUsers catch errors call', (done) => {
|
||||
service.getWorkflowUsers('fake-task-id', 'fake-filter').subscribe(() => {
|
||||
}, () => {
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to involve people in the task', (done) => {
|
||||
service.involveUserWithTask('fake-task-id', 'fake-user-id').subscribe(
|
||||
() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('PUT');
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toContain('tasks/fake-task-id/action/involve');
|
||||
done();
|
||||
});
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200
|
||||
});
|
||||
});
|
||||
|
||||
it('involveUserWithTask catch errors call', (done) => {
|
||||
service.involveUserWithTask('fake-task-id', 'fake-user-id').subscribe(() => {
|
||||
}, () => {
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to remove involved people from task', (done) => {
|
||||
service.removeInvolvedUser('fake-task-id', 'fake-user-id').subscribe(
|
||||
() => {
|
||||
expect(jasmine.Ajax.requests.mostRecent().method).toBe('PUT');
|
||||
expect(jasmine.Ajax.requests.mostRecent().url).toContain('tasks/fake-task-id/action/remove-involved');
|
||||
done();
|
||||
});
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 200
|
||||
});
|
||||
});
|
||||
|
||||
it('removeInvolvedUser catch errors call', (done) => {
|
||||
service.removeInvolvedUser('fake-task-id', 'fake-user-id').subscribe(() => {
|
||||
}, () => {
|
||||
done();
|
||||
});
|
||||
|
||||
jasmine.Ajax.requests.mostRecent().respondWith({
|
||||
status: 403
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,73 @@
|
||||
/*!
|
||||
* @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 { Injectable } from '@angular/core';
|
||||
import { AlfrescoApiService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { Response } from '@angular/http';
|
||||
import { User } from '../models/user.model';
|
||||
|
||||
@Injectable()
|
||||
export class ActivitiPeopleService {
|
||||
|
||||
constructor(private authService: AlfrescoAuthenticationService,
|
||||
private alfrescoJsApi: AlfrescoApiService) {
|
||||
}
|
||||
|
||||
getWorkflowUsers(taskId: string, searchWord: string): Observable<User[]> {
|
||||
let option = {excludeTaskId: taskId, filter: searchWord};
|
||||
return Observable.fromPromise(this.getWorkflowUserApi(option))
|
||||
.map((response: any) => <User[]> response.data || [])
|
||||
.catch(this.handleError);
|
||||
}
|
||||
|
||||
involveUserWithTask(taskId: string, idToInvolve: string): Observable<User[]> {
|
||||
let node = {userId: idToInvolve};
|
||||
return Observable.fromPromise(this.involveUserToTaskApi(taskId, node))
|
||||
.catch(this.handleError);
|
||||
}
|
||||
|
||||
removeInvolvedUser(taskId: string, idToRemove: string): Observable<User[]> {
|
||||
let node = {userId: idToRemove};
|
||||
return Observable.fromPromise(this.removeInvolvedUserFromTaskApi(taskId, node))
|
||||
.catch(this.handleError);
|
||||
}
|
||||
|
||||
private getWorkflowUserApi(options: any) {
|
||||
return this.alfrescoJsApi.getInstance().activiti.usersWorkflowApi.getUsers(options);
|
||||
}
|
||||
|
||||
private involveUserToTaskApi(taskId: string, node: any) {
|
||||
return this.alfrescoJsApi.getInstance().activiti.taskActionsApi.involveUser(taskId, node);
|
||||
}
|
||||
|
||||
private removeInvolvedUserFromTaskApi(taskId: string, node: any) {
|
||||
return this.alfrescoJsApi.getInstance().activiti.taskActionsApi.removeInvolvedUser(taskId, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw the error
|
||||
* @param error
|
||||
* @returns {ErrorObservable}
|
||||
*/
|
||||
private handleError(error: Response) {
|
||||
// in a real world app, we may send the error to some remote logging infrastructure
|
||||
// instead of just logging it to the console
|
||||
console.error(error);
|
||||
return Observable.throw(error || 'Server error');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user