Merge branch 'development' into dev-hashraf-automation-ids

* development:
  Apply PR changes
  Fix xAxes labels #788
  #843 fixed validation summary reset for Login
  Disabled action on readonly task
  Added test for checklist - fixed test for people
  Added start task test
  Added some improvements and clear user list
  add test for people search component
  Added test for people component
  Added task id as input
  Changed activiti people to manage the new search user window
  Added new people search to the index.ts
  Added component search for users
  Added remove involved user to people service
  Fix dialog for firefox and safari
  Fix process definition api
This commit is contained in:
Hussain Ashraf 2016-11-04 10:52:29 +00:00
commit c2966c9d00
29 changed files with 1262 additions and 101 deletions

View File

@ -69,6 +69,29 @@ export var reportDefParamProcessDef = {
'definition': '{ "parameters" :[{"id":"processDefinitionId","name":null,"nameKey":null,"type":"processDefinition","value":null,"dependsOn":null}]}'
};
export var reportDefParamProcessDefOptionsNoApp = [
{
'id': 'FakeProcessTest 1:1:1',
'name': 'Fake Process Test 1 Name ',
'version': 1
},
{
'id': 'FakeProcessTest 1:2:1',
'name': 'Fake Process Test 1 Name ',
'version': 2
},
{
'id': 'FakeProcessTest 2:1:1',
'name': 'Fake Process Test 2 Name ',
'version': 1
},
{
'id': 'FakeProcessTest 3:1:1',
'name': 'Fake Process Test 3 Name ',
'version': 1
}
];
export var reportDefParamProcessDefOptions = {
'size': 4, 'total': 4, 'start': 0, 'data': [
{

View File

@ -293,7 +293,7 @@ describe('Test ng2-analytics-report-parameters Report Parameters ', () => {
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: analyticParamsMock.reportDefParamProcessDefOptions
responseText: analyticParamsMock.reportDefParamProcessDefOptionsNoApp
});
});

View File

@ -182,7 +182,7 @@ export class AnalyticsReportParametersComponent implements OnInit, OnChanges {
}
public convertNumber(value: string): number {
return parseInt(value, 10);
return value != null ? parseInt(value, 10) : 0;
}
convertFormValuesToReportParamQuery(values: any): ReportQuery {

View File

@ -76,6 +76,8 @@ export class BarChart extends Chart {
labels: string[] = [];
datasets: any[] = [];
data: any[] = [];
xAxisType: string;
yAxisType: string;
options: any = {
scales: {
yAxes: [{
@ -83,6 +85,9 @@ export class BarChart extends Chart {
beginAtZero: true,
stepSize: 1
}
}],
xAxes: [{
ticks: {}
}]
}
};
@ -91,6 +96,9 @@ export class BarChart extends Chart {
super(obj);
this.title = obj && obj.title || null;
this.titleKey = obj && obj.titleKey || null;
this.xAxisType = obj && obj.xAxisType || null;
this.yAxisType = obj && obj.yAxisType || null;
this.options.scales.xAxes[0].ticks.callback = this.xAxisTickFormatFunction(this.xAxisType);
obj.values.forEach((params: any) => {
let dataValue = [];
params.values.forEach((info: any) => {
@ -108,6 +116,18 @@ export class BarChart extends Chart {
});
}
xAxisTickFormatFunction = function (xAxisType) {
return function (value) {
if ('date_day' === xAxisType) {
return moment(new Date(value)).format('DD');
} else if ('date_month' === xAxisType) {
return moment(new Date(value)).format('MMMM');
} else if ('date_year' === xAxisType) {
return moment(new Date(value)).format('YYYY');
}
};
};
hasDatasets() {
return this.datasets && this.datasets.length > 0 ? true : false;
}

View File

@ -68,7 +68,11 @@ export class AnalyticsService {
if (type === 'status') {
return this.getProcessStatusValues();
} else if (type === 'processDefinition') {
return this.getProcessDefinitionsValues(appId);
if (appId === null || appId === undefined) {
return this.getProcessDefinitionsValuesNoApp();
} else {
return this.getProcessDefinitionsValues(appId);
}
} else if (type === 'dateInterval') {
return this.getDateIntervalValues();
} else if (type === 'task') {
@ -122,13 +126,26 @@ export class AnalyticsService {
});
}
getProcessDefinitionsValuesNoApp(): Observable<any> {
let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/reporting/process-definitions`;
let options = this.getRequestOptions();
return this.http
.get(url, options)
.map((res: any) => {
let paramOptions: ParameterValueModel[] = [];
let body = res.json();
body.forEach((opt) => {
paramOptions.push(new ParameterValueModel(opt));
});
return paramOptions;
}).catch(this.handleError);
}
getProcessDefinitionsValues(appId: string): Observable<any> {
let url = `${this.alfrescoSettingsService.getBPMApiBaseUrl()}/app/rest/process-definitions`;
let params: URLSearchParams;
if (appId) {
params = new URLSearchParams();
params.set('appDefinitionId', appId);
}
params = new URLSearchParams();
params.set('appDefinitionId', appId);
let options = this.getRequestOptions(params);
return this.http
.get(url, options)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,6 +33,9 @@ export class ActivitiComments implements OnInit, OnChanges {
@Input()
taskId: string;
@Input()
readOnly: boolean = false;
@ViewChild('dialog')
dialog: any;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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."
}
}
}
}

View File

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

View File

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

View File

@ -272,6 +272,7 @@ export class AlfrescoLoginComponent implements OnInit {
*/
private disableError() {
this.error = false;
this.initFormError();
}
/**