+
{{ 'ADF_CLOUD_START_TASK.ERROR.MESSAGE' | translate }}
warning
diff --git a/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.spec.ts
index d99ce8282a..50baa578c6 100644
--- a/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.spec.ts
+++ b/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.spec.ts
@@ -20,7 +20,7 @@ import { By } from '@angular/platform-browser';
import { PeopleCloudComponent } from './people-cloud.component';
import { StartTaskCloudTestingModule } from '../../testing/start-task-cloud.testing.module';
import { LogService, setupTestBed, IdentityUserService, IdentityUserModel } from '@alfresco/adf-core';
-import { mockUsers, mockRoles } from '../../mock/user-cloud.mock';
+import { mockUsers } from '../../mock/user-cloud.mock';
import { of } from 'rxjs';
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
@@ -29,8 +29,8 @@ describe('PeopleCloudComponent', () => {
let fixture: ComponentFixture
;
let element: HTMLElement;
let identityService: IdentityUserService;
- let getRolesByUserIdSpy: jasmine.Spy;
- let getUserSpy: jasmine.Spy;
+ let findUsersSpy: jasmine.Spy;
+ let checkUserHasAccessSpy: jasmine.Spy;
setupTestBed({
imports: [ProcessServiceCloudTestingModule, StartTaskCloudTestingModule],
@@ -42,40 +42,17 @@ describe('PeopleCloudComponent', () => {
component = fixture.componentInstance;
element = fixture.nativeElement;
identityService = TestBed.get(IdentityUserService);
- getRolesByUserIdSpy = spyOn(identityService, 'getUserRoles').and.returnValue(of(mockRoles));
- getUserSpy = spyOn(identityService, 'getUsers').and.returnValue(of(mockUsers));
+ findUsersSpy = spyOn(identityService, 'findUsersByName').and.returnValue(of(mockUsers));
+ checkUserHasAccessSpy = spyOn(identityService, 'checkUserHasClientApp').and.returnValue(of(true));
+ spyOn(identityService, 'getClientIdByApplicationName').and.returnValue(of('mock-client-id'));
});
it('should create PeopleCloudComponent', () => {
expect(component instanceof PeopleCloudComponent).toBeTruthy();
});
- it('should able to fetch users', () => {
- fixture.detectChanges();
- expect(getUserSpy).toHaveBeenCalled();
- });
-
- it('should able to fetch roles by user id', async(() => {
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- expect(getRolesByUserIdSpy).toHaveBeenCalled();
- });
- }));
-
- it('should not list the current logged in user when showCurrentUser is false', async(() => {
- spyOn(identityService, 'getCurrentUserInfo').and.returnValue(mockUsers[1]);
- component.showCurrentUser = false;
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- const currentUser = component.users.find((user) => {
- return user.username === mockUsers[1].username;
- });
- expect(currentUser).toBeUndefined();
- });
- }));
-
it('should show the users if the typed result match', async(() => {
- component.users$ = of( mockUsers);
+ component.searchUsers$ = of( mockUsers);
fixture.detectChanges();
let inputHTMLElement: HTMLInputElement = element.querySelector('input');
inputHTMLElement.focus();
@@ -90,7 +67,7 @@ describe('PeopleCloudComponent', () => {
});
}));
- it('should hide result list if input is empty', () => {
+ it('should hide result list if input is empty', async(() => {
fixture.detectChanges();
let inputHTMLElement: HTMLInputElement = element.querySelector('input');
inputHTMLElement.focus();
@@ -99,40 +76,176 @@ describe('PeopleCloudComponent', () => {
inputHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
- fixture.detectChanges();
expect(fixture.debugElement.query(By.css('mat-option'))).toBeNull();
expect(fixture.debugElement.query(By.css('#adf-people-cloud-user-0'))).toBeNull();
});
- });
+ }));
- it('should emit selectedUser if option is valid', async() => {
+ it('should emit selectedUser if option is valid', async(() => {
fixture.detectChanges();
- let selectEmitSpy = spyOn(component.selectedUser, 'emit');
+ let selectEmitSpy = spyOn(component.selectUser, 'emit');
component.onSelect(new IdentityUserModel({ username: 'username'}));
fixture.whenStable().then(() => {
- fixture.detectChanges();
expect(selectEmitSpy).toHaveBeenCalled();
});
- });
+ }));
it('should show an error message if the user is invalid', async(() => {
- getUserSpy.and.returnValue(of([]));
- getRolesByUserIdSpy.and.returnValue(of([]));
- component.dataError = true;
+ checkUserHasAccessSpy.and.returnValue(of(false));
+ findUsersSpy.and.returnValue(of([]));
fixture.detectChanges();
- let inputHTMLElement: HTMLInputElement = element.querySelector('input');
+ const inputHTMLElement: HTMLInputElement = element.querySelector('input');
inputHTMLElement.focus();
- inputHTMLElement.dispatchEvent(new Event('input'));
- inputHTMLElement.dispatchEvent(new Event('keyup'));
- inputHTMLElement.dispatchEvent(new Event('keydown'));
inputHTMLElement.value = 'ZZZ';
+ inputHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
+ inputHTMLElement.blur();
fixture.detectChanges();
const errorMessage = element.querySelector('.adf-start-task-cloud-error-message');
- expect(element.querySelector('.adf-start-task-cloud-error')).not.toBeNull();
+ expect(errorMessage).not.toBeNull();
expect(errorMessage.textContent).toContain('ADF_CLOUD_START_TASK.ERROR.MESSAGE');
});
}));
+ it('should show chip list when mode=multiple', async(() => {
+ component.mode = 'multiple';
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ const chip = element.querySelector('mat-chip-list');
+ expect(chip).toBeDefined();
+ });
+ }));
+
+ it('should not show chip list when mode=single', async(() => {
+ component.mode = 'single';
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ const chip = element.querySelector('mat-chip-list');
+ expect(chip).toBeNull();
+ });
+ }));
+
+ it('should pre-select all preSelectUsers when mode=multiple', async(() => {
+ spyOn(identityService, 'getUsersByRolesWithCurrentUser').and.returnValue(Promise.resolve(mockUsers));
+ component.mode = 'multiple';
+ component.preSelectUsers = [{id: mockUsers[1].id}, {id: mockUsers[2].id}];
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
+ expect(chips.length).toBe(2);
+ });
+ }));
+
+ it('should not pre-select any user when preSelectUsers is empty and mode=multiple', async(() => {
+ spyOn(identityService, 'getUsersByRolesWithCurrentUser').and.returnValue(Promise.resolve(mockUsers));
+ component.mode = 'multiple';
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ const chip = fixture.debugElement.query(By.css('mat-chip'));
+ expect(chip).toBeNull();
+ });
+ }));
+
+ it('should pre-select preSelectUsers[0] when mode=single', async(() => {
+ spyOn(identityService, 'getUsersByRolesWithCurrentUser').and.returnValue(Promise.resolve(mockUsers));
+ component.mode = 'single';
+ component.preSelectUsers = [{id: mockUsers[1].id}, {id: mockUsers[2].id}];
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ const selectedUser = component.searchUserCtrl.value;
+ expect(selectedUser.id).toBe(mockUsers[1].id);
+ });
+ }));
+
+ it('should not pre-select any user when preSelectUsers is empty and mode=single', async(() => {
+ spyOn(identityService, 'getUsersByRolesWithCurrentUser').and.returnValue(Promise.resolve(mockUsers));
+ component.mode = 'single';
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ const selectedUser = component.searchUserCtrl.value;
+ expect(selectedUser).toBeNull();
+ });
+ }));
+
+ it('should emit removeUser when a selected user is removed if mode=multiple', async(() => {
+ spyOn(identityService, 'getUsersByRolesWithCurrentUser').and.returnValue(Promise.resolve(mockUsers));
+ let removeUserSpy = spyOn(component.removeUser, 'emit');
+
+ component.mode = 'multiple';
+ component.preSelectUsers = [{id: mockUsers[1].id}, {id: mockUsers[2].id}];
+ fixture.detectChanges();
+
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ const removeIcon = fixture.debugElement.query(By.css('mat-chip mat-icon'));
+ removeIcon.nativeElement.click();
+
+ expect(removeUserSpy).toHaveBeenCalledWith({ id: mockUsers[1].id });
+ });
+
+ }));
+
+ it('should list users who have access to the app when appName is specified', async(() => {
+ component.appName = 'sample-app';
+ fixture.detectChanges();
+ let inputHTMLElement: HTMLInputElement = element.querySelector('input');
+ inputHTMLElement.focus();
+ inputHTMLElement.value = 'M';
+ inputHTMLElement.dispatchEvent(new Event('input'));
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ const usersList = fixture.debugElement.queryAll(By.css('mat-option'));
+ expect(usersList.length).toBe(mockUsers.length);
+ });
+ }));
+
+ it('should not list users who do not have access to the app when appName is specified', async(() => {
+ checkUserHasAccessSpy.and.returnValue(of(false));
+ component.appName = 'sample-app';
+
+ fixture.detectChanges();
+ let inputHTMLElement: HTMLInputElement = element.querySelector('input');
+ inputHTMLElement.focus();
+ inputHTMLElement.value = 'M';
+ inputHTMLElement.dispatchEvent(new Event('input'));
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ const usersList = fixture.debugElement.queryAll(By.css('mat-option'));
+ expect(usersList.length).toBe(0);
+ });
+ }));
+
+ it('should validate access to the app when appName is specified', async(() => {
+ component.appName = 'sample-app';
+
+ fixture.detectChanges();
+ let inputHTMLElement: HTMLInputElement = element.querySelector('input');
+ inputHTMLElement.focus();
+ inputHTMLElement.value = 'M';
+ inputHTMLElement.dispatchEvent(new Event('input'));
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(checkUserHasAccessSpy).toHaveBeenCalledTimes(mockUsers.length);
+ });
+ }));
+
+ it('should not validate access to the app when appName is not specified', async(() => {
+ fixture.detectChanges();
+ let inputHTMLElement: HTMLInputElement = element.querySelector('input');
+ inputHTMLElement.focus();
+ inputHTMLElement.value = 'M';
+ inputHTMLElement.dispatchEvent(new Event('input'));
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ fixture.detectChanges();
+ expect(checkUserHasAccessSpy).not.toHaveBeenCalled();
+ });
+ }));
+
});
diff --git a/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.ts b/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.ts
index eafe2e0355..ad46898fb9 100644
--- a/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.ts
+++ b/lib/process-services-cloud/src/lib/task/start-task/components/people-cloud/people-cloud.component.ts
@@ -16,8 +16,9 @@
*/
import { FormControl } from '@angular/forms';
-import { Component, OnInit, Output, EventEmitter, ViewEncapsulation, Input } from '@angular/core';
-import { Observable, of } from 'rxjs';
+import { Component, OnInit, Output, EventEmitter, ViewEncapsulation, Input, ViewChild, ElementRef } from '@angular/core';
+import { Observable, of, BehaviorSubject } from 'rxjs';
+import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter } from 'rxjs/operators';
import { FullNamePipe, IdentityUserModel, IdentityUserService } from '@alfresco/adf-core';
import { trigger, state, style, transition, animate } from '@angular/animations';
@@ -40,71 +41,227 @@ import { trigger, state, style, transition, animate } from '@angular/animations'
export class PeopleCloudComponent implements OnInit {
- static ROLE_ACTIVITI_ADMIN = 'ACTIVITI_ADMIN';
- static ROLE_ACTIVITI_USER = 'ACTIVITI_USER';
- static ROLE_ACTIVITI_MODELER = 'ACTIVITI_MODELER';
+ static MODE_SINGLE = 'single';
+ static MODE_MULTIPLE = 'multiple';
- /** Show current user in the list or not. */
+ /** Name of the application. If specified, shows the users who have access to the app. */
@Input()
- showCurrentUser: boolean = true;
+ appName: string;
+
+ /** Mode of the user selection (single/multiple). */
+ @Input()
+ mode: string = PeopleCloudComponent.MODE_SINGLE;
+
+ /** Role names of the users to be listed. */
+ @Input()
+ roles: string[];
+
+ /** Array of users to be pre-selected. Pre-select all users in multi selection mode and only the first user of the array in single selection mode. */
+ @Input()
+ preSelectUsers: IdentityUserModel[];
/** Emitted when a user is selected. */
@Output()
- selectedUser: EventEmitter = new EventEmitter();
+ selectUser: EventEmitter = new EventEmitter();
+
+ /** Emitted when a selected user is removed in multi selection mode. */
+ @Output()
+ removeUser: EventEmitter = new EventEmitter();
/** Emitted when an error occurs. */
@Output()
error: EventEmitter = new EventEmitter();
- users$: Observable;
+ @ViewChild('userInput')
+ private userInput: ElementRef;
- searchUser: FormControl = new FormControl();
+ private _selectedUsers: IdentityUserModel[] = [];
+ private _searchUsers: IdentityUserModel[] = [];
+ private selectedUsers: BehaviorSubject;
+ private searchUsers: BehaviorSubject;
+ selectedUsers$: Observable;
+ searchUsers$: Observable;
+
+ searchUserCtrl: FormControl = new FormControl();
_subscriptAnimationState: string = 'enter';
- users: IdentityUserModel[] = [];
+ clientId: string;
- dataError = false;
+ isFocused: boolean;
- currentUser: IdentityUserModel;
-
- constructor(private identityUserService: IdentityUserService) { }
+ constructor(private identityUserService: IdentityUserService) {
+ this.selectedUsers = new BehaviorSubject(this._selectedUsers);
+ this.searchUsers = new BehaviorSubject(this._searchUsers);
+ this.selectedUsers$ = this.selectedUsers.asObservable();
+ this.searchUsers$ = this.searchUsers.asObservable();
+ }
ngOnInit() {
- this.loadUsers();
+ if (this.hasPreSelectUsers()) {
+ this.loadPreSelectUsers();
+ }
+
this.initSearch();
- }
- initSearch() {
- this.searchUser.valueChanges.subscribe((keyword) => {
- this.users$ = this.searchUsers(keyword);
- });
- }
-
- private async loadUsers() {
- const roles = [PeopleCloudComponent.ROLE_ACTIVITI_ADMIN, PeopleCloudComponent.ROLE_ACTIVITI_MODELER, PeopleCloudComponent.ROLE_ACTIVITI_USER];
- if (this.showCurrentUser) {
- this.users = await this.identityUserService.getUsersByRolesWithCurrentUser(roles);
- } else {
- this.users = await this.identityUserService.getUsersByRolesWithoutCurrentUser(roles);
+ if (this.appName) {
+ this.disableSearch();
+ this.loadClientId();
}
}
- private searchUsers(keyword: string): Observable {
- const filteredUsers = this.users.filter((user) => {
- return user.username.toLowerCase().indexOf(keyword.toString().toLowerCase()) !== -1;
+ private initSearch() {
+ this.searchUserCtrl.valueChanges.pipe(
+ filter((value) => {
+ return typeof value === 'string';
+ }),
+ tap((value) => {
+ if (value) {
+ this.setError();
+ } else {
+ this.clearError();
+ }
+ }),
+ debounceTime(500),
+ distinctUntilChanged(),
+ tap(() => {
+ this.resetSearchUsers();
+ }),
+ switchMap((search) => this.identityUserService.findUsersByName(search)),
+ mergeMap((users) => {
+ return users;
+ }),
+ filter((user: any) => {
+ return !this.isUserAlreadySelected(user);
+ }),
+ mergeMap((user: any) => {
+ if (this.appName) {
+ return this.checkUserHasAccess(user.id).pipe(
+ mergeMap((hasRole) => {
+ return hasRole ? of(user) : of();
+ })
+ );
+ } else {
+ return of(user);
+ }
+ })
+ ).subscribe((user) => {
+ this._searchUsers.push(user);
+ this.searchUsers.next(this._searchUsers);
});
- this.dataError = filteredUsers.length === 0;
- return of(filteredUsers);
}
- onSelect(selectedUser: IdentityUserModel) {
- this.selectedUser.emit(selectedUser);
- this.dataError = false;
+ private checkUserHasAccess(userId: string): Observable {
+ if (this.hasRoles()) {
+ return this.identityUserService.checkUserHasAnyClientAppRole(userId, this.clientId, this.roles);
+ } else {
+ return this.identityUserService.checkUserHasClientApp(userId, this.clientId);
+ }
+ }
+
+ private hasRoles(): boolean {
+ return this.roles && this.roles.length > 0;
+ }
+
+ private isUserAlreadySelected(user: IdentityUserModel): boolean {
+ if (this._selectedUsers && this._selectedUsers.length > 0) {
+ const result = this._selectedUsers.find((selectedUser) => {
+ return selectedUser.id === user.id;
+ });
+
+ return !!result;
+ }
+ return false;
+ }
+
+ private loadPreSelectUsers() {
+ if (this.isMultipleMode()) {
+ if (this.preSelectUsers && this.preSelectUsers.length > 0) {
+ this.selectedUsers.next(this.preSelectUsers);
+ }
+ } else {
+ this.selectedUsers.next(this.preSelectUsers);
+ this.searchUserCtrl.setValue(this.preSelectUsers[0]);
+ }
+ }
+
+ private async loadClientId() {
+ this.clientId = await this.identityUserService.getClientIdByApplicationName(this.appName).toPromise();
+
+ if (this.clientId) {
+ this.enableSearch();
+ }
+ }
+
+ onSelect(user: IdentityUserModel) {
+ if (this.isMultipleMode()) {
+
+ if (!this.isUserAlreadySelected(user)) {
+ this._selectedUsers.push(user);
+ this.selectedUsers.next(this._selectedUsers);
+ this.selectUser.emit(user);
+ }
+
+ this.userInput.nativeElement.value = '';
+ this.searchUserCtrl.setValue('');
+ } else {
+ this.selectUser.emit(user);
+ }
+
+ this.clearError();
+ this.resetSearchUsers();
+ }
+
+ onRemove(user: IdentityUserModel) {
+ this.removeUser.emit(user);
+ const indexToRemove = this._selectedUsers.findIndex((selectedUser) => { return selectedUser.id === user.id; });
+ this._selectedUsers.splice(indexToRemove, 1);
+ this.selectedUsers.next(this._selectedUsers);
}
getDisplayName(user): string {
return FullNamePipe.prototype.transform(user);
}
+ isMultipleMode(): boolean {
+ return this.mode === PeopleCloudComponent.MODE_MULTIPLE;
+ }
+
+ private hasPreSelectUsers(): boolean {
+ return this.preSelectUsers && this.preSelectUsers.length > 0;
+ }
+
+ private resetSearchUsers() {
+ this._searchUsers = [];
+ this.searchUsers.next(this._searchUsers);
+ }
+
+ private setError() {
+ this.searchUserCtrl.setErrors({invalid: true});
+ }
+
+ private clearError() {
+ this.searchUserCtrl.setErrors(null);
+ }
+
+ setFocus(isFocused: boolean) {
+ this.isFocused = isFocused;
+ }
+
+ hasError(): boolean {
+ return !!this.searchUserCtrl.errors;
+ }
+
+ hasErrorMessage(): boolean {
+ return !this.isFocused && this.hasError();
+ }
+
+ private disableSearch() {
+ this.searchUserCtrl.disable();
+ }
+
+ private enableSearch() {
+ this.searchUserCtrl.enable();
+ }
+
}
diff --git a/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.html b/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.html
index 71b46d5eec..486347cccb 100644
--- a/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.html
+++ b/lib/process-services-cloud/src/lib/task/start-task/components/start-task-cloud.component.html
@@ -62,7 +62,7 @@