diff --git a/ng2-components/ng2-activiti-analytics/src/assets/analyticsParamsReportComponent.mock.ts b/ng2-components/ng2-activiti-analytics/src/assets/analyticsParamsReportComponent.mock.ts index 626996af3b..07d44ade6a 100644 --- a/ng2-components/ng2-activiti-analytics/src/assets/analyticsParamsReportComponent.mock.ts +++ b/ng2-components/ng2-activiti-analytics/src/assets/analyticsParamsReportComponent.mock.ts @@ -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': [ { diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.spec.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.spec.ts index 9302b32f01..bb435e61cc 100644 --- a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.spec.ts +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.spec.ts @@ -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 }); }); diff --git a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.ts b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.ts index f31fcb0f18..f1768acc42 100644 --- a/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.ts +++ b/ng2-components/ng2-activiti-analytics/src/components/analytics-report-parameters.component.ts @@ -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 { diff --git a/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts b/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts index bba431596e..ff3f45f590 100644 --- a/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts +++ b/ng2-components/ng2-activiti-analytics/src/models/chart.model.ts @@ -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; } diff --git a/ng2-components/ng2-activiti-analytics/src/services/analytics.service.ts b/ng2-components/ng2-activiti-analytics/src/services/analytics.service.ts index 867fe2c47d..ea7eb28496 100644 --- a/ng2-components/ng2-activiti-analytics/src/services/analytics.service.ts +++ b/ng2-components/ng2-activiti-analytics/src/services/analytics.service.ts @@ -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 { + 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 { 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) diff --git a/ng2-components/ng2-activiti-tasklist/index.ts b/ng2-components/ng2-activiti-tasklist/index.ts index b6c3b1cf53..3e4263e937 100644 --- a/ng2-components/ng2-activiti-tasklist/index.ts +++ b/ng2-components/ng2-activiti-tasklist/index.ts @@ -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({ diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.html index 9bfdf6e968..1f74274e2b 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.html @@ -1,33 +1,33 @@ -{{ 'TASK_DETAILS.LABELS.CHECKLIST' | translate }} -
add
-
+
add
+
Add a checklist
-
+
{{ 'TASK_DETAILS.CHECKLIST.NONE' | translate }}
- -

New Task

+ +

New Check

- - + +
- - + +
-
\ No newline at end of file +
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.spec.ts new file mode 100644 index 0000000000..35ff285f11 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.spec.ts @@ -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; + 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 = element.querySelector('#add-checklist'); + closeCheckDialogButton = 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 = 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 = 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'); + }); + })); + }); + +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.ts index dab4a8825b..fd8574a92f 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-checklist.component.ts @@ -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) { diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.html index 50fb144e73..26d71179d4 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.html @@ -1,7 +1,7 @@ {{ 'TASK_DETAILS.LABELS.COMMENTS' |translate }} -
add
-
+
add
+
Add a comment
@@ -32,4 +32,4 @@
- \ No newline at end of file + diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.ts index a2be327c5b..10f5a7b1a4 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-comments.component.ts @@ -33,6 +33,9 @@ export class ActivitiComments implements OnInit, OnChanges { @Input() taskId: string; + @Input() + readOnly: boolean = false; + @ViewChild('dialog') dialog: any; diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css new file mode 100644 index 0000000000..11c70fd077 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.css @@ -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; +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html new file mode 100644 index 0000000000..b6e13ddeb3 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.html @@ -0,0 +1,17 @@ +
+ + +
+
    +
  • + + face + {{getDisplayUser(user)}} + +
  • +
    + No user found to involve +
    +
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts new file mode 100644 index 0000000000..0c4c3bb9fe --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.spec.ts @@ -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; + 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 = element.querySelector('#user-1'); + userToSelect.click(); + }); + })); + + it('should remove clicked user', async(() => { + activitiPeopleSearchComponent.results = Observable.of(userArray); + activitiPeopleSearchComponent.ngOnInit(); + fixture.detectChanges(); + let userToSelect = element.querySelector('#user-1'); + userToSelect.click(); + + fixture.detectChanges(); + fixture.whenStable() + .then(() => { + expect(element.querySelector('#user-1')).toBeNull(); + }); + })); +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.ts new file mode 100644 index 0000000000..52bd3284cf --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people-search.component.ts @@ -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; + + @Output() + onSearch: EventEmitter = new EventEmitter(); + + @Output() + onRowClicked: EventEmitter = 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; + } +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css index 7e9e64291f..8caacb8c93 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.css @@ -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; +} diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html index 5430f9b172..616afee42d 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.html @@ -1,33 +1,36 @@ -{{ 'TASK_DETAILS.LABELS.PEOPLE' | translate }} -
add
-
+
add
+
Add a person
-
+
{{ 'TASK_DETAILS.PEOPLE.NONE' | translate }}
- -

New User

-
-
- - -
+ +

Involve User

+
+ +
- - +
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts new file mode 100644 index 0000000000..7ede77c23e --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.spec.ts @@ -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; + 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 = 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'); + }); + })); + }); +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts index 7ce00fc1a3..e4fa29919e 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-people.component.ts @@ -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; - people$: Observable; + private peopleSearchObserver: Observer; + peopleSearch$: Observable; /** * 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(observer => this.peopleObserver = observer).share(); - } - - ngOnInit() { - this.people$.subscribe((user: User) => { - this.people.push(user); - }); + this.peopleSearch$ = new Observable(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; + } + } diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.html index 837e1730fb..fb369d4477 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.html @@ -1,19 +1,27 @@ - + - -

{{'START_TASK.DIALOG.TITLE'|translate}}

+ +

{{'START_TASK.DIALOG.TITLE'|translate}}

- +
- - + +
- - + +
diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.spec.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.spec.ts new file mode 100644 index 0000000000..509a898e10 --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.spec.ts @@ -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; + 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 = 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 = 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 = 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 = 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 = element.querySelector('#button-start'); + startTaskButton.click(); + activitiStartTaskButton.name = 'fake-name'; + createTaskButton.click(); + jasmine.Ajax.requests.mostRecent().respondWith({ + 'status': 200, + contentType: 'json', + responseText: {} + }); + }); +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.ts b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.ts index 15da8c356d..f31b0e3ede 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.ts +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-start-task.component.ts @@ -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({ diff --git a/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.html b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.html index 6658cf65c5..15c862721e 100644 --- a/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.html +++ b/ng2-components/ng2-activiti-tasklist/src/components/activiti-task-details.component.html @@ -12,13 +12,16 @@
- +
- +
- +
{ + + 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 + }); + }); + }); +}); diff --git a/ng2-components/ng2-activiti-tasklist/src/services/activiti-people.service.ts b/ng2-components/ng2-activiti-tasklist/src/services/activiti-people.service.ts new file mode 100644 index 0000000000..1e5e99433d --- /dev/null +++ b/ng2-components/ng2-activiti-tasklist/src/services/activiti-people.service.ts @@ -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 { + let option = {excludeTaskId: taskId, filter: searchWord}; + return Observable.fromPromise(this.getWorkflowUserApi(option)) + .map((response: any) => response.data || []) + .catch(this.handleError); + } + + involveUserWithTask(taskId: string, idToInvolve: string): Observable { + let node = {userId: idToInvolve}; + return Observable.fromPromise(this.involveUserToTaskApi(taskId, node)) + .catch(this.handleError); + } + + removeInvolvedUser(taskId: string, idToRemove: string): Observable { + 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'); + } +} diff --git a/ng2-components/ng2-alfresco-login/src/components/alfresco-login.component.ts b/ng2-components/ng2-alfresco-login/src/components/alfresco-login.component.ts index 7606dd05f0..cfdcfa6541 100644 --- a/ng2-components/ng2-alfresco-login/src/components/alfresco-login.component.ts +++ b/ng2-components/ng2-alfresco-login/src/components/alfresco-login.component.ts @@ -272,6 +272,7 @@ export class AlfrescoLoginComponent implements OnInit { */ private disableError() { this.error = false; + this.initFormError(); } /**