mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-3812] Add multi selection and roles filtering to adf-cloud-people component (#4068)
* [ADF-3812] Added multiple user selection and user pre-selection * [ADF-3812] Added tests * [ADF-3812] Added jsdoc * [ADF-3812] Improved variable naming * [ADF-3812] Improved mode selection * [ADF-3812] Changed input name and emit logic * [ADF-3812] Used modified emitter name in start task * [ADF-3812] Improved default role selection * Use the new strategy to fetch the authorized users * * Fixed pre-selection in single mode * * Added invalid selection validation * * Added start task assignee validation * * Improved preset loading * * Improved tests * * Added test to validate default assignee * * Added methods to check user has access to an app * * Added app access to people cloud and start task * * Refactored methods and removed unused input - showCurrentUser * * Added tests * * Changed service names and removed unwated services * * Used formControl error instead of manual error flag * * Used new hasError method of people component inside start task * * Improved tests * * Updated callCustomApi call signature * * Added documentation * * Disabled search until clientId is retrieved * * Changed realm name * * Added jsdoc for service methods * Remove the useless doc
This commit is contained in:
committed by
Eugenio Romano
parent
46150a65f2
commit
f08ad08d0f
@@ -1,27 +1,49 @@
|
||||
<form>
|
||||
<mat-form-field class="adf-people-cloud">
|
||||
<mat-label id="assignee-id">{{'ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.ASSIGNEE' | translate}}</mat-label>
|
||||
<input #inputValue
|
||||
matInput
|
||||
class="adf-cloud-input"
|
||||
data-automation-id="adf-people-cloud-search-input"
|
||||
type="text"
|
||||
[formControl]="searchUser"
|
||||
[matAutocomplete]="auto">
|
||||
<mat-label id="assignee-id">{{'ADF_TASK_LIST.START_TASK.FORM.LABEL.ASSIGNEE' | translate}}</mat-label>
|
||||
<mat-chip-list #userChipList *ngIf="isMultipleMode(); else singleSelection">
|
||||
<mat-chip
|
||||
*ngFor="let user of selectedUsers$ | async"
|
||||
(removed)="onRemove(user)">
|
||||
{{user | fullName}}
|
||||
<mat-icon matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
<input
|
||||
#userInput
|
||||
matInput
|
||||
[formControl]="searchUserCtrl"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="userChipList"
|
||||
class="adf-cloud-input"
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
data-automation-id="adf-people-cloud-search-input">
|
||||
</mat-chip-list>
|
||||
|
||||
<ng-template #singleSelection>
|
||||
<input matInput
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
class="adf-cloud-input"
|
||||
data-automation-id="adf-people-cloud-search-input"
|
||||
type="text"
|
||||
[formControl]="searchUserCtrl"
|
||||
[matAutocomplete]="auto">
|
||||
</ng-template>
|
||||
<mat-autocomplete autoActiveFirstOption class="adf-people-cloud-list"
|
||||
#auto="matAutocomplete"
|
||||
(optionSelected)="onSelect($event.option.value)"
|
||||
[displayWith]="getDisplayName">
|
||||
<mat-option *ngFor="let user of users$ | async; let i = index" [value]="user">
|
||||
<mat-option *ngFor="let user of searchUsers$ | async; let i = index" [value]="user">
|
||||
<div class="adf-people-cloud-row" id="adf-people-cloud-user-{{i}}">
|
||||
<div [outerHTML]="user | usernameInitials:'adf-people-widget-pic'"></div>
|
||||
<span class="adf-people-label-name"> {{user | fullName}}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
</mat-form-field>
|
||||
<div class="adf-start-task-cloud-error">
|
||||
<div *ngIf="dataError" fxLayout="row" fxLayoutAlign="start start" [@transitionMessages]="_subscriptAnimationState">
|
||||
<div *ngIf="hasErrorMessage()" fxLayout="row" fxLayoutAlign="start start" [@transitionMessages]="_subscriptAnimationState">
|
||||
<div class="adf-start-task-cloud-error-message">{{ 'ADF_CLOUD_START_TASK.ERROR.MESSAGE' | translate }}</div>
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
</div>
|
||||
|
@@ -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<PeopleCloudComponent>;
|
||||
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(<IdentityUserModel[]> mockUsers);
|
||||
component.searchUsers$ = of(<IdentityUserModel[]> mockUsers);
|
||||
fixture.detectChanges();
|
||||
let inputHTMLElement: HTMLInputElement = <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 = <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 = <HTMLInputElement> element.querySelector('input');
|
||||
const inputHTMLElement: HTMLInputElement = <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 = <any> [{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 = <any> [{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 = <any> [{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 = <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 = <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 = <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 = <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();
|
||||
});
|
||||
}));
|
||||
|
||||
});
|
||||
|
@@ -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<IdentityUserModel> = new EventEmitter<IdentityUserModel>();
|
||||
selectUser: EventEmitter<IdentityUserModel> = new EventEmitter<IdentityUserModel>();
|
||||
|
||||
/** Emitted when a selected user is removed in multi selection mode. */
|
||||
@Output()
|
||||
removeUser: EventEmitter<IdentityUserModel> = new EventEmitter<IdentityUserModel>();
|
||||
|
||||
/** Emitted when an error occurs. */
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
users$: Observable<IdentityUserModel[]>;
|
||||
@ViewChild('userInput')
|
||||
private userInput: ElementRef<HTMLInputElement>;
|
||||
|
||||
searchUser: FormControl = new FormControl();
|
||||
private _selectedUsers: IdentityUserModel[] = [];
|
||||
private _searchUsers: IdentityUserModel[] = [];
|
||||
private selectedUsers: BehaviorSubject<IdentityUserModel[]>;
|
||||
private searchUsers: BehaviorSubject<IdentityUserModel[]>;
|
||||
selectedUsers$: Observable<IdentityUserModel[]>;
|
||||
searchUsers$: Observable<IdentityUserModel[]>;
|
||||
|
||||
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<IdentityUserModel[]>(this._selectedUsers);
|
||||
this.searchUsers = new BehaviorSubject<IdentityUserModel[]>(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<IdentityUserModel[]> {
|
||||
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<boolean> {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@
|
||||
</mat-form-field>
|
||||
|
||||
<div fxFlex>
|
||||
<adf-cloud-people (selectedUser)="onAssigneeSelect($event)"></adf-cloud-people>
|
||||
<adf-cloud-people #peopleInput *ngIf="currentUser" [appName]="appName" [preSelectUsers]="[currentUser]" (selectUser)="onAssigneeSelect($event)"></adf-cloud-people>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
@@ -78,7 +78,7 @@
|
||||
</button>
|
||||
<button
|
||||
color="primary"
|
||||
type="submit" [disabled]="dateError || !taskForm.valid || submitted"
|
||||
type="submit" [disabled]="dateError || !taskForm.valid || submitted || assignee.hasError()"
|
||||
mat-button
|
||||
id="button-start">
|
||||
{{'ADF_CLOUD_TASK_LIST.START_TASK.FORM.ACTION.START'|translate}}
|
||||
|
@@ -32,7 +32,6 @@ import { taskDetailsMock } from '../mock/task-details.mock';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ProcessServiceCloudTestingModule } from './../../../testing/process-service-cloud.testing.module';
|
||||
import { StartTaskCloudTestingModule } from '../testing/start-task-cloud.testing.module';
|
||||
import { mockRoles, mockUsers } from '../mock/user-cloud.mock';
|
||||
import { TaskDetailsCloudModel } from '../models/task-details-cloud.model';
|
||||
|
||||
describe('StartTaskCloudComponent', () => {
|
||||
@@ -43,8 +42,6 @@ describe('StartTaskCloudComponent', () => {
|
||||
let identityService: IdentityUserService;
|
||||
let element: HTMLElement;
|
||||
let createNewTaskSpy: jasmine.Spy;
|
||||
let getRolesByUserIdSpy: jasmine.Spy;
|
||||
let getUserSpy: jasmine.Spy;
|
||||
|
||||
setupTestBed({
|
||||
imports: [ProcessServiceCloudTestingModule, StartTaskCloudTestingModule],
|
||||
@@ -60,9 +57,7 @@ describe('StartTaskCloudComponent', () => {
|
||||
service = TestBed.get(StartTaskCloudService);
|
||||
identityService = TestBed.get(IdentityUserService);
|
||||
createNewTaskSpy = spyOn(service, 'createNewTask').and.returnValue(of(taskDetailsMock));
|
||||
getRolesByUserIdSpy = spyOn(identityService, 'getUserRoles').and.returnValue(of(mockRoles));
|
||||
getUserSpy = spyOn(identityService, 'getUsers').and.returnValue(of(mockUsers));
|
||||
spyOn(identityService, 'getCurrentUserInfo').and.returnValue(new IdentityUserModel({username: 'currentUser'}));
|
||||
spyOn(identityService, 'getCurrentUserInfo').and.returnValue(new IdentityUserModel({username: 'currentUser', firstName: 'Test', lastName: 'User'}));
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
@@ -70,27 +65,8 @@ describe('StartTaskCloudComponent', () => {
|
||||
expect(component instanceof StartTaskCloudComponent).toBe(true, 'should create StartTaskCloudComponent');
|
||||
});
|
||||
|
||||
it('should defined adf-cloud-people and fetch users ', () => {
|
||||
component.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
const peopleElement = fixture.debugElement.nativeElement.querySelector('adf-cloud-people');
|
||||
expect(peopleElement).toBeDefined();
|
||||
expect(getRolesByUserIdSpy).toHaveBeenCalled();
|
||||
expect(getUserSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('create task', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
createNewTaskSpy.and.returnValue(of(
|
||||
{
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
assignee: 'fake-assignee'
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
it('should create new task when start button is clicked', async(() => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
component.taskForm.controls['name'].setValue('fakeName');
|
||||
@@ -113,11 +89,7 @@ describe('StartTaskCloudComponent', () => {
|
||||
createTaskButton.click();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(successSpy).toHaveBeenCalledWith({
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
assignee: 'fake-assignee'
|
||||
});
|
||||
expect(successSpy).toHaveBeenCalledWith(taskDetailsMock);
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -143,20 +115,18 @@ describe('StartTaskCloudComponent', () => {
|
||||
expect(successSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should assign task when an assignee is selected', async(() => {
|
||||
let successSpy = spyOn(component.success, 'emit');
|
||||
it('should assign task to the logged in user when invalid assignee is selected', async(() => {
|
||||
component.taskForm.controls['name'].setValue('fakeName');
|
||||
component.assigneeName = 'fake-assignee';
|
||||
fixture.detectChanges();
|
||||
let createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
const assigneeInput = <HTMLElement> element.querySelector('input.adf-cloud-input');
|
||||
assigneeInput.nodeValue = 'a';
|
||||
fixture.detectChanges();
|
||||
const createTaskButton = <HTMLElement> element.querySelector('#button-start');
|
||||
createTaskButton.click();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
expect(successSpy).toHaveBeenCalledWith({
|
||||
id: 91,
|
||||
name: 'fakeName',
|
||||
assignee: 'fake-assignee'
|
||||
});
|
||||
const taskRequest = new TaskDetailsCloudModel({ name: 'fakeName', assignee: 'currentUser'});
|
||||
expect(createNewTaskSpy).toHaveBeenCalledWith(taskRequest);
|
||||
});
|
||||
}));
|
||||
|
||||
@@ -173,12 +143,18 @@ describe('StartTaskCloudComponent', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('should select logged in user as assignee by default', () => {
|
||||
fixture.detectChanges();
|
||||
const assignee = fixture.nativeElement.querySelector('[data-automation-id="adf-people-cloud-search-input"]');
|
||||
expect(assignee.value).toBe('Test User');
|
||||
});
|
||||
|
||||
it('should show start task button', () => {
|
||||
component.taskForm.controls['name'].setValue('fakeName');
|
||||
fixture.detectChanges();
|
||||
expect(element.querySelector('#button-start')).toBeDefined();
|
||||
expect(element.querySelector('#button-start')).not.toBeNull();
|
||||
expect(element.querySelector('#button-start').textContent).toContain('ADF_CLOUD_TASK_LIST.START_TASK.FORM.ACTION.START');
|
||||
const startButton = element.querySelector('#button-start');
|
||||
expect(startButton).toBeDefined();
|
||||
expect(startButton.textContent).toContain('ADF_CLOUD_TASK_LIST.START_TASK.FORM.ACTION.START');
|
||||
});
|
||||
|
||||
it('should disable start button if name is empty', () => {
|
||||
|
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation, OnDestroy } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
||||
import { MOMENT_DATE_FORMATS, MomentDateAdapter } from '@alfresco/adf-core';
|
||||
import moment from 'moment-es6';
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
IdentityUserService,
|
||||
IdentityUserModel
|
||||
} from '@alfresco/adf-core';
|
||||
import { PeopleCloudComponent } from './people-cloud/people-cloud.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-cloud-start-task',
|
||||
@@ -70,6 +71,9 @@ export class StartTaskCloudComponent implements OnInit, OnDestroy {
|
||||
@Output()
|
||||
error: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
@ViewChild('peopleInput')
|
||||
assignee: PeopleCloudComponent;
|
||||
|
||||
users$: Observable<any[]>;
|
||||
|
||||
taskId: string;
|
||||
|
Reference in New Issue
Block a user