mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-08-07 17:48:54 +00:00
[AAE-9019] - People/Group cloud with HxP (#7658)
* Cover the use cases by mocking them * Replace the mock with real stream and remove useless code * Provide new service to fetch groups * Fix group tests * Use the interface and token injection * [AAE-8870] add unit test and mock for new service * Improve roles condifion * initialize the stream as part of NgOnInit to be sure it relies on the correct FormControl instance(input) * Rollback tmp change for roles * [AAE-8641] people abstraction mock * [AAE-8641] revert angular.json changes * [AAE-8641] few conditions and code improvements * [AAE-8641] revert change input controls name * [AAE-8641] initialize the stream as part of ngOnInit * [AAE-8641] people abstraction improvements * [AAE-8870] cherry pick people abstraction * [AAE-8641] fix people-group e2es * fix lint * remove hardcoded identityHost * Use the identityhost api in case of cloud * Solve issue with returnType array string * Rebase and use GroupModel from cloud * Rebase and use GroupModel from cloud * Use the bpmHost instead of identityFor * Add identity ingress for user access service * Rename test * Fix linting issues * Fix playwright storybook e2e for people and group * Trigger travis * Fix people group e2e * improved formatting * Remove not needed travis var override * Remove unused import after rebase * Make roles in filter optional + remove comments Co-authored-by: Tomasz <tomasz.gnyp@hyland.com> Co-authored-by: arditdomi <ardit.domi@hyland.com>
This commit is contained in:
@@ -26,18 +26,21 @@ import {
|
||||
SimpleChanges,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
ViewChild, ElementRef, SimpleChange
|
||||
ViewChild,
|
||||
ElementRef,
|
||||
Inject
|
||||
} from '@angular/core';
|
||||
import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
|
||||
import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, map, takeUntil } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, takeUntil } from 'rxjs/operators';
|
||||
import {
|
||||
FullNamePipe,
|
||||
IdentityUserModel,
|
||||
IdentityUserService,
|
||||
LogService
|
||||
} from '@alfresco/adf-core';
|
||||
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||
import { ComponentSelectionMode } from '../../types';
|
||||
import { IdentityUserModel } from '../models/identity-user.model';
|
||||
import { IdentityUserServiceInterface } from '../services/identity-user.service.interface';
|
||||
import { IDENTITY_USER_SERVICE_TOKEN } from '../services/identity-user-service.token';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-cloud-people',
|
||||
@@ -139,15 +142,14 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@ViewChild('userInput')
|
||||
private userInput: ElementRef<HTMLInputElement>;
|
||||
|
||||
private _searchUsers: IdentityUserModel[] = [];
|
||||
private searchUsers: IdentityUserModel[] = [];
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
selectedUsers: IdentityUserModel[] = [];
|
||||
invalidUsers: IdentityUserModel[] = [];
|
||||
|
||||
searchUsers$ = new BehaviorSubject<IdentityUserModel[]>(this._searchUsers);
|
||||
searchUsers$ = new BehaviorSubject<IdentityUserModel[]>(this.searchUsers);
|
||||
subscriptAnimationState: string = 'enter';
|
||||
clientId: string;
|
||||
isFocused: boolean;
|
||||
touched: boolean = false;
|
||||
|
||||
@@ -157,20 +159,19 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
validationLoading = false;
|
||||
searchLoading = false;
|
||||
|
||||
typingUniqueValueNotEmpty$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
private identityUserService: IdentityUserService,
|
||||
@Inject(IDENTITY_USER_SERVICE_TOKEN)
|
||||
private identityUserService: IdentityUserServiceInterface,
|
||||
private logService: LogService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadClientId();
|
||||
this.initSearch();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (this.valueChanged(changes.preSelectUsers)
|
||||
|| this.valueChanged(changes.mode)
|
||||
|| this.valueChanged(changes.validate)
|
||||
) {
|
||||
if (this.hasPreselectedUsersChanged(changes) || this.hasModeChanged(changes) || this.isValidationChanged(changes)) {
|
||||
if (this.hasPreSelectUsers()) {
|
||||
this.loadPreSelectUsers();
|
||||
} else if (this.hasPreselectedUsersCleared(changes)) {
|
||||
@@ -182,32 +183,51 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.invalidUsers = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.appName && this.isAppNameChanged(changes.appName)) {
|
||||
this.loadClientId();
|
||||
}
|
||||
}
|
||||
|
||||
private async loadClientId(): Promise<void> {
|
||||
this.clientId = await this.identityUserService.getClientIdByApplicationName(this.appName).toPromise();
|
||||
if (this.clientId) {
|
||||
this.searchUserCtrl.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private initSearch(): void {
|
||||
this.searchUserCtrl.valueChanges.pipe(
|
||||
filter((value) => {
|
||||
this.initializeStream();
|
||||
this.typingUniqueValueNotEmpty$.pipe(
|
||||
switchMap((name: string) =>
|
||||
this.identityUserService.search(name, { roles: this.roles, withinApplication: this.appName, groups: this.groupsRestriction })
|
||||
),
|
||||
mergeMap((users: IdentityUserModel[]) => {
|
||||
this.resetSearchUsers();
|
||||
this.searchLoading = false;
|
||||
return users;
|
||||
}),
|
||||
filter(user => !this.isUserAlreadySelected(user) && !this.isExcludedUser(user)),
|
||||
takeUntil(this.onDestroy$)
|
||||
).subscribe((user: IdentityUserModel) => {
|
||||
this.searchUsers.push(user);
|
||||
this.searchUsers$.next(this.searchUsers);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeStream() {
|
||||
const typingValueFromControl$ = this.searchUserCtrl.valueChanges;
|
||||
|
||||
const typingValueTypeSting$ = typingValueFromControl$.pipe(
|
||||
filter(value => {
|
||||
this.searchLoading = true;
|
||||
return typeof value === 'string';
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const typingValueHandleErrorMessage$ = typingValueTypeSting$.pipe(
|
||||
tap((value: string) => {
|
||||
if (value) {
|
||||
this.setTypingError();
|
||||
}
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const typingValueDebouncedUnique$ = typingValueHandleErrorMessage$.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
|
||||
this.typingUniqueValueNotEmpty$ = typingValueDebouncedUnique$.pipe(
|
||||
tap((value: string) => {
|
||||
if (value.trim()) {
|
||||
this.searchedValue = value;
|
||||
@@ -216,87 +236,17 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.searchUserCtrl.markAsUntouched();
|
||||
}
|
||||
}),
|
||||
tap(() => {
|
||||
this.resetSearchUsers();
|
||||
}),
|
||||
switchMap((search) =>
|
||||
this.findUsers(search)
|
||||
),
|
||||
mergeMap((users) => {
|
||||
this.resetSearchUsers();
|
||||
this.searchLoading = false;
|
||||
return users;
|
||||
}),
|
||||
filter(user => !this.isUserAlreadySelected(user) && !this.isExcludedUser(user)),
|
||||
mergeMap(user => this.filterUsersByGroupsRestriction(user)),
|
||||
mergeMap(user => {
|
||||
if (this.appName) {
|
||||
return this.checkUserHasAccess(user.id).pipe(
|
||||
mergeMap(
|
||||
hasRole => hasRole ? of(user) : of()
|
||||
)
|
||||
);
|
||||
} else if (this.hasRoles()) {
|
||||
return this.filterUsersByRoles(user);
|
||||
} else {
|
||||
return of(user);
|
||||
}
|
||||
}),
|
||||
takeUntil(this.onDestroy$)
|
||||
).subscribe(user => {
|
||||
this._searchUsers.push(user);
|
||||
this.searchUsers$.next(this._searchUsers);
|
||||
});
|
||||
tap(() => this.resetSearchUsers())
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
private isAppNameChanged(change: SimpleChange): boolean {
|
||||
return change && change.previousValue !== change.currentValue && this.appName && this.appName.length > 0;
|
||||
}
|
||||
|
||||
isValidationEnabled(): boolean {
|
||||
private isValidationEnabled(): boolean {
|
||||
return this.validate === true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
filterUsersByRoles(user: IdentityUserModel): Observable<IdentityUserModel> {
|
||||
return this.identityUserService.checkUserHasRole(user.id, this.roles).pipe(
|
||||
map((hasRole: boolean) => ({ hasRole, user })),
|
||||
filter((filteredUser: { hasRole: boolean; user: IdentityUserModel }) => filteredUser.hasRole),
|
||||
map((filteredUser: { hasRole: boolean; user: IdentityUserModel }) => filteredUser.user));
|
||||
}
|
||||
|
||||
private filterUsersByGroupsRestriction(user: IdentityUserModel): Observable<IdentityUserModel> {
|
||||
if (this.groupsRestriction?.length) {
|
||||
return this.isUserPartOfAllRestrictedGroups(user).pipe(
|
||||
mergeMap(isPartOfAllGroups => isPartOfAllGroups ? of(user) : of())
|
||||
);
|
||||
}
|
||||
return of(user);
|
||||
}
|
||||
|
||||
private findUsers(search: string): Observable<IdentityUserModel[]> {
|
||||
return this.identityUserService.findUsersByName(search.trim());
|
||||
}
|
||||
|
||||
private isUserAlreadySelected(searchUser: IdentityUserModel): boolean {
|
||||
if (this.selectedUsers && this.selectedUsers.length > 0) {
|
||||
const result = this.selectedUsers.find((selectedUser) => this.compare(selectedUser, searchUser));
|
||||
const result = this.selectedUsers.find((selectedUser) => this.equalsUsers(selectedUser, searchUser));
|
||||
|
||||
return !!result;
|
||||
}
|
||||
@@ -305,7 +255,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
private isExcludedUser(searchUser: IdentityUserModel): boolean {
|
||||
if (this.excludedUsers?.length > 0) {
|
||||
return !!this.excludedUsers.find(excludedUser => this.compare(excludedUser, searchUser));
|
||||
return !!this.excludedUsers.find(excludedUser => this.equalsUsers(excludedUser, searchUser));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -334,28 +284,14 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
async validatePreselectUsers(): Promise<any> {
|
||||
private async validatePreselectUsers(): Promise<any> {
|
||||
this.invalidUsers = [];
|
||||
const validUsers: IdentityUserModel[] = [];
|
||||
|
||||
for (const user of this.getPreselectedUsers()) {
|
||||
try {
|
||||
const validationResult = await this.searchUser(user);
|
||||
const validationResult = (await this.identityUserService.search(user.username, { roles: this.roles, withinApplication: this.appName, groups: this.groupsRestriction }).toPromise())[0];
|
||||
|
||||
if (this.compare(user, validationResult)) {
|
||||
validationResult.readonly = user.readonly;
|
||||
if (this.groupsRestriction?.length) {
|
||||
const isUserPartOfAllRestrictedGroups = await this.isUserPartOfAllRestrictedGroups(validationResult).toPromise();
|
||||
|
||||
if (isUserPartOfAllRestrictedGroups) {
|
||||
validUsers.push(user);
|
||||
} else {
|
||||
this.invalidUsers.push(user);
|
||||
}
|
||||
} else {
|
||||
validUsers.push(validationResult);
|
||||
}
|
||||
} else {
|
||||
if (!this.equalsUsers(user, validationResult)) {
|
||||
this.invalidUsers.push(user);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -365,10 +301,9 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
this.checkPreselectValidationErrors();
|
||||
this.selectedUsers = validUsers.concat(this.invalidUsers);
|
||||
}
|
||||
|
||||
compare(preselectedUser: IdentityUserModel, identityUser: IdentityUserModel): boolean {
|
||||
equalsUsers(preselectedUser: IdentityUserModel, identityUser: IdentityUserModel): boolean {
|
||||
if (preselectedUser && identityUser) {
|
||||
const uniquePropertyIdentifiers = ['id', 'username', 'email'];
|
||||
for (const property of Object.keys(preselectedUser)) {
|
||||
@@ -380,33 +315,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
private getSearchKey(user: IdentityUserModel): string {
|
||||
if (user.id) {
|
||||
return 'id';
|
||||
} else if (user.email) {
|
||||
return 'email';
|
||||
} else if (user.username) {
|
||||
return 'username';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async searchUser(user: IdentityUserModel): Promise<IdentityUserModel> {
|
||||
const key = this.getSearchKey(user);
|
||||
|
||||
switch (key) {
|
||||
case 'id':
|
||||
return this.identityUserService.findUserById(user[key]).toPromise();
|
||||
case 'username':
|
||||
return (await this.identityUserService.findUserByUsername(user[key]).toPromise())[0];
|
||||
case 'email':
|
||||
return (await this.identityUserService.findUserByEmail(user[key]).toPromise())[0];
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
removeDuplicatedUsers(users: IdentityUserModel[]): IdentityUserModel[] {
|
||||
return users.filter((user, index, self) =>
|
||||
index === self.findIndex(auxUser =>
|
||||
@@ -414,19 +322,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
));
|
||||
}
|
||||
|
||||
checkPreselectValidationErrors(): void {
|
||||
this.invalidUsers = this.removeDuplicatedUsers(this.invalidUsers);
|
||||
|
||||
if (this.invalidUsers.length > 0) {
|
||||
this.generateInvalidUsersMessage();
|
||||
}
|
||||
|
||||
this.warning.emit({
|
||||
message: 'INVALID_PRESELECTED_USERS',
|
||||
users: this.invalidUsers
|
||||
});
|
||||
}
|
||||
|
||||
onSelect(user: IdentityUserModel): void {
|
||||
if (user) {
|
||||
this.selectUser.emit(user);
|
||||
@@ -442,7 +337,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
this.userInput.nativeElement.value = '';
|
||||
this.searchUserCtrl.setValue('');
|
||||
this.userChipsCtrlValue(this.selectedUsers[0].username);
|
||||
this.userChipsControlValue(this.selectedUsers[0].username);
|
||||
|
||||
this.changedUsers.emit(this.selectedUsers);
|
||||
this.resetSearchUsers();
|
||||
@@ -454,10 +349,10 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.removeUserFromSelected(userToRemove);
|
||||
this.changedUsers.emit(this.selectedUsers);
|
||||
if (this.selectedUsers.length === 0) {
|
||||
this.userChipsCtrlValue('');
|
||||
this.userChipsControlValue('');
|
||||
|
||||
} else {
|
||||
this.userChipsCtrlValue(this.selectedUsers[0].username);
|
||||
this.userChipsControlValue(this.selectedUsers[0].username);
|
||||
}
|
||||
this.searchUserCtrl.markAsDirty();
|
||||
this.searchUserCtrl.markAsTouched();
|
||||
@@ -468,10 +363,17 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private userChipsCtrlValue(value: string) {
|
||||
this.userChipsCtrl.setValue(value);
|
||||
this.userChipsCtrl.markAsDirty();
|
||||
this.userChipsCtrl.markAsTouched();
|
||||
private checkPreselectValidationErrors(): void {
|
||||
this.invalidUsers = this.removeDuplicatedUsers(this.invalidUsers);
|
||||
|
||||
if (this.invalidUsers.length > 0) {
|
||||
this.generateInvalidUsersMessage();
|
||||
}
|
||||
|
||||
this.warning.emit({
|
||||
message: 'INVALID_PRESELECTED_USERS',
|
||||
users: this.invalidUsers
|
||||
});
|
||||
}
|
||||
|
||||
private removeUserFromSelected({ id, username, email }: IdentityUserModel): void {
|
||||
@@ -494,7 +396,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
generateInvalidUsersMessage(): void {
|
||||
private generateInvalidUsersMessage(): void {
|
||||
this.validateUsersMessage = '';
|
||||
|
||||
this.invalidUsers.forEach((invalidUser, index) => {
|
||||
@@ -506,13 +408,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
setTypingError(): void {
|
||||
this.searchUserCtrl.setErrors({
|
||||
searchTypingError: true,
|
||||
...this.searchUserCtrl.errors
|
||||
});
|
||||
}
|
||||
|
||||
hasPreselectError(): boolean {
|
||||
return this.invalidUsers
|
||||
&& this.invalidUsers.length > 0;
|
||||
@@ -522,11 +417,11 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return FullNamePipe.prototype.transform(user);
|
||||
}
|
||||
|
||||
isMultipleMode(): boolean {
|
||||
private isMultipleMode(): boolean {
|
||||
return this.mode === 'multiple';
|
||||
}
|
||||
|
||||
isSingleMode(): boolean {
|
||||
private isSingleMode(): boolean {
|
||||
return this.mode === 'single';
|
||||
}
|
||||
|
||||
@@ -541,9 +436,22 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
&& this.preSelectUsers.length > 0;
|
||||
}
|
||||
|
||||
private valueChanged(change: SimpleChange): boolean {
|
||||
return change
|
||||
&& change.currentValue !== change.previousValue;
|
||||
private hasModeChanged(changes: SimpleChanges): boolean {
|
||||
return changes
|
||||
&& changes.mode
|
||||
&& changes.mode.currentValue !== changes.mode.previousValue;
|
||||
}
|
||||
|
||||
private isValidationChanged(changes: SimpleChanges): boolean {
|
||||
return changes
|
||||
&& changes.validate
|
||||
&& changes.validate.currentValue !== changes.validate.previousValue;
|
||||
}
|
||||
|
||||
private hasPreselectedUsersChanged(changes: SimpleChanges): boolean {
|
||||
return changes
|
||||
&& changes.preSelectUsers
|
||||
&& changes.preSelectUsers.currentValue !== changes.preSelectUsers.previousValue;
|
||||
}
|
||||
|
||||
private hasPreselectedUsersCleared(changes: SimpleChanges): boolean {
|
||||
@@ -554,20 +462,21 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
private resetSearchUsers(): void {
|
||||
this._searchUsers = [];
|
||||
this.searchUsers$.next(this._searchUsers);
|
||||
this.searchUsers = [];
|
||||
this.searchUsers$.next(this.searchUsers);
|
||||
}
|
||||
|
||||
private isUserPartOfAllRestrictedGroups(user: IdentityUserModel): Observable<boolean> {
|
||||
return this.getUserGroups(user.id).pipe(
|
||||
map(userGroups => this.groupsRestriction.every(restricted => userGroups.includes(restricted)))
|
||||
);
|
||||
private setTypingError(): void {
|
||||
this.searchUserCtrl.setErrors({
|
||||
searchTypingError: true,
|
||||
...this.searchUserCtrl.errors
|
||||
});
|
||||
}
|
||||
|
||||
private getUserGroups(userId: string): Observable<string[]> {
|
||||
return this.identityUserService.getInvolvedGroups(userId).pipe(
|
||||
map(groups => groups.map((group) => group.name))
|
||||
);
|
||||
private userChipsControlValue(value: string) {
|
||||
this.userChipsCtrl.setValue(value);
|
||||
this.userChipsCtrl.markAsDirty();
|
||||
this.userChipsCtrl.markAsTouched();
|
||||
}
|
||||
|
||||
getSelectedUsers(): IdentityUserModel[] {
|
||||
@@ -617,4 +526,9 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
getValidationMinLength(): string {
|
||||
return this.searchUserCtrl.errors.minlength.requiredLength;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user