mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[AAE-1372] Refactor People/Group cloud component (#5355)
* [AAE-1372] Fix read-only preselected users can be deleted * [AAE-1372] Fix people/group cloud component readonly mode * [AAE-1372] Refactor People/Group Cloud components * [AAE-1372] Refactor People/Group Cloud components * [AAE-1372] Clear invalid user in single mode after replacing with a valid user * [AAE-1372] Add progress bar while validation loading. When user gets removed remove from validation * [AAE-1372] Fix lint errors * [AAE-1372] Fix single selection e2e * [AAE-1372] Fix unit tests - people/group cloud components * [AAE-1372] Fix e2e, set People/Group formControls invalid when has preselect errors * [AAE-1372] Fix invalid form control bug
This commit is contained in:
committed by
Maurizio Vitale
parent
91abe87ccc
commit
3c3aa7599a
@@ -35,7 +35,12 @@ import { trigger, state, style, transition, animate } from '@angular/animations'
|
||||
import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/internal/operators/debounceTime';
|
||||
import { distinctUntilChanged, switchMap, mergeMap, filter, tap, map, takeUntil } from 'rxjs/operators';
|
||||
import { IdentityGroupModel, IdentityGroupSearchParam, IdentityGroupService, LogService } from '@alfresco/adf-core';
|
||||
import {
|
||||
IdentityGroupModel,
|
||||
IdentityGroupSearchParam,
|
||||
IdentityGroupService,
|
||||
LogService
|
||||
} from '@alfresco/adf-core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-cloud-group',
|
||||
@@ -88,7 +93,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
/** FormControl to search the group */
|
||||
@Input()
|
||||
searchGroupsControl: FormControl = new FormControl();
|
||||
searchGroupsControl: FormControl = new FormControl({ value: '', disabled: false });
|
||||
|
||||
/** Role names of the groups to be listed. */
|
||||
@Input()
|
||||
@@ -113,61 +118,59 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@ViewChild('groupInput')
|
||||
private groupInput: ElementRef<HTMLInputElement>;
|
||||
|
||||
private selectedGroups: IdentityGroupModel[] = [];
|
||||
|
||||
private searchGroups: IdentityGroupModel[] = [];
|
||||
private searchGroupsSubject: BehaviorSubject<IdentityGroupModel[]>;
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
searchGroups$ = new BehaviorSubject<IdentityGroupModel[]>([]);
|
||||
|
||||
selectedGroups$ = new BehaviorSubject<IdentityGroupModel[]>([]);
|
||||
selectedGroups: IdentityGroupModel[] = [];
|
||||
invalidGroups: IdentityGroupModel[] = [];
|
||||
|
||||
searchGroups$: Observable<IdentityGroupModel[]>;
|
||||
_subscriptAnimationState = 'enter';
|
||||
|
||||
clientId: string;
|
||||
|
||||
searchedValue = '';
|
||||
|
||||
isFocused: boolean;
|
||||
|
||||
isDisabled: boolean;
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
currentTimeout: any;
|
||||
invalidGroups: IdentityGroupModel[] = [];
|
||||
validateGroupsMessage: string;
|
||||
searchedValue = '';
|
||||
|
||||
isLoading = false;
|
||||
|
||||
constructor(
|
||||
private identityGroupService: IdentityGroupService,
|
||||
private logService: LogService
|
||||
) { }
|
||||
private logService: LogService) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.searchGroupsSubject === undefined) {
|
||||
this.searchGroupsSubject = new BehaviorSubject<IdentityGroupModel[]>(this.searchGroups);
|
||||
this.searchGroups$ = this.searchGroupsSubject.asObservable();
|
||||
}
|
||||
|
||||
this.loadClientId();
|
||||
this.initSearch();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
|
||||
if (this.isPreselectedGroupsChanged(changes)) {
|
||||
if (this.isValidationEnabled()) {
|
||||
if (this.hasPreselectedGroupsChanged(changes) || this.hasModeChanged(changes) || this.isValidationChanged(changes)) {
|
||||
if (this.hasPreSelectGroups()) {
|
||||
this.loadPreSelectGroups();
|
||||
} else {
|
||||
this.loadNoValidationPreselectGroups();
|
||||
} else if (this.hasPreselectedGroupsCleared(changes)) {
|
||||
this.selectedGroups = [];
|
||||
this.invalidGroups = [];
|
||||
}
|
||||
|
||||
if (!this.isValidationEnabled()) {
|
||||
this.invalidGroups = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isAppNameChanged(changes.appName)) {
|
||||
this.disableSearch();
|
||||
if (changes.appName && this.isAppNameChanged(changes.appName)) {
|
||||
this.loadClientId();
|
||||
} else {
|
||||
this.enableSearch();
|
||||
this.initSearch();
|
||||
}
|
||||
}
|
||||
|
||||
private isPreselectedGroupsChanged(changes: SimpleChanges): boolean {
|
||||
return changes.preSelectGroups
|
||||
&& changes.preSelectGroups.previousValue !== changes.preSelectGroups.currentValue
|
||||
&& this.hasPreSelectGroups();
|
||||
}
|
||||
|
||||
private isAppNameChanged(change: SimpleChange): boolean {
|
||||
return change && change.previousValue !== change.currentValue && this.appName && this.appName.length > 0;
|
||||
}
|
||||
@@ -175,23 +178,23 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
private async loadClientId() {
|
||||
this.clientId = await this.identityGroupService.getClientIdByApplicationName(this.appName).toPromise();
|
||||
if (this.clientId) {
|
||||
this.enableSearch();
|
||||
this.searchGroupsControl.enable();
|
||||
}
|
||||
}
|
||||
|
||||
initSearch() {
|
||||
this.searchGroupsControl.valueChanges.pipe(
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
filter((value) => {
|
||||
return typeof value === 'string';
|
||||
}),
|
||||
tap((value) => {
|
||||
this.searchedValue = value;
|
||||
if (value) {
|
||||
this.setError();
|
||||
this.setTypingError();
|
||||
}
|
||||
}),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap(() => {
|
||||
this.resetSearchGroups();
|
||||
}),
|
||||
@@ -221,7 +224,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
takeUntil(this.onDestroy$)
|
||||
).subscribe((searchedGroup: any) => {
|
||||
this.searchGroups.push(searchedGroup);
|
||||
this.searchGroups$.next(this.searchGroups);
|
||||
this.searchGroupsSubject.next(this.searchGroups);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -236,7 +239,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
private isGroupAlreadySelected(group: IdentityGroupModel): boolean {
|
||||
if (this.selectedGroups && this.selectedGroups.length > 0 && this.isMultipleMode()) {
|
||||
const result = this.selectedGroups.find((selectedGroup: IdentityGroupModel) => {
|
||||
return selectedGroup.id === group.id;
|
||||
return selectedGroup.name === group.name;
|
||||
});
|
||||
|
||||
return !!result;
|
||||
@@ -244,115 +247,74 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
async searchGroup(groupName: any): Promise<IdentityGroupModel> {
|
||||
return (await this.identityGroupService.findGroupsByName(this.createSearchParam(groupName)).toPromise())[0];
|
||||
async searchGroup(groupName: string): Promise<IdentityGroupModel> {
|
||||
return (await this.identityGroupService.findGroupsByName({ name: groupName }).toPromise())[0];
|
||||
}
|
||||
|
||||
async filterPreselectGroups() {
|
||||
const promiseBatch = this.preSelectGroups.map(async (group: IdentityGroupModel) => {
|
||||
let result: any;
|
||||
async validatePreselectGroups(): Promise<any> {
|
||||
this.invalidGroups = [];
|
||||
|
||||
let preselectedGroupsToValidate: IdentityGroupModel[] = [];
|
||||
|
||||
if (this.isSingleMode()) {
|
||||
preselectedGroupsToValidate = [this.preSelectGroups[0]];
|
||||
} else {
|
||||
preselectedGroupsToValidate = this.removeDuplicatedGroups(this.preSelectGroups);
|
||||
}
|
||||
|
||||
await Promise.all(preselectedGroupsToValidate.map(async (group: IdentityGroupModel) => {
|
||||
try {
|
||||
result = await this.searchGroup(group.name);
|
||||
const validationResult = await this.searchGroup(group.name);
|
||||
if (!this.hasGroupIdOrName(validationResult)) {
|
||||
this.invalidGroups.push(group);
|
||||
}
|
||||
} catch (error) {
|
||||
result = [];
|
||||
this.invalidGroups.push(group);
|
||||
this.logService.error(error);
|
||||
}
|
||||
const isGroupValid: boolean = this.groupExists(result);
|
||||
return isGroupValid ? result : null;
|
||||
});
|
||||
return Promise.all(promiseBatch);
|
||||
}
|
||||
|
||||
public groupExists(result: IdentityGroupModel): boolean {
|
||||
return result
|
||||
&& (result.id !== undefined
|
||||
|| result.name !== undefined);
|
||||
}
|
||||
|
||||
private isValidGroup(filteredGroups: IdentityGroupModel[], group: IdentityGroupModel): IdentityGroupModel {
|
||||
return filteredGroups.find((filteredGroup: IdentityGroupModel) => {
|
||||
return filteredGroup &&
|
||||
(filteredGroup.id === group.id ||
|
||||
filteredGroup.name === group.name);
|
||||
});
|
||||
}
|
||||
|
||||
async validatePreselectGroups(): Promise<IdentityGroupModel[]> {
|
||||
let filteredPreselectGroups: IdentityGroupModel[];
|
||||
let validGroups: IdentityGroupModel[] = [];
|
||||
|
||||
try {
|
||||
filteredPreselectGroups = await this.filterPreselectGroups();
|
||||
} catch (error) {
|
||||
validGroups = [];
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
await this.preSelectGroups.map((group: IdentityGroupModel) => {
|
||||
const validGroup = this.isValidGroup(filteredPreselectGroups, group);
|
||||
|
||||
if (validGroup) {
|
||||
validGroups.push(validGroup);
|
||||
} else {
|
||||
this.invalidGroups.push(group);
|
||||
}
|
||||
});
|
||||
validGroups = this.removeDuplicatedGroups(validGroups);
|
||||
return validGroups;
|
||||
}
|
||||
|
||||
public async loadSinglePreselectGroup() {
|
||||
const groups = await this.validatePreselectGroups();
|
||||
if (groups && groups.length > 0) {
|
||||
this.checkPreselectValidationErrors();
|
||||
this.searchGroupsControl.setValue(groups[0]);
|
||||
} else {
|
||||
this.checkPreselectValidationErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public async loadMultiplePreselectGroups() {
|
||||
const groups = await this.validatePreselectGroups();
|
||||
if (groups && groups.length > 0) {
|
||||
this.checkPreselectValidationErrors();
|
||||
this.selectedGroups = [...groups];
|
||||
this.selectedGroups$.next(this.selectedGroups);
|
||||
} else {
|
||||
this.checkPreselectValidationErrors();
|
||||
}
|
||||
}));
|
||||
this.checkPreselectValidationErrors();
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
public checkPreselectValidationErrors() {
|
||||
|
||||
this.invalidGroups = this.removeDuplicatedGroups(this.invalidGroups);
|
||||
|
||||
if (this.invalidGroups.length > 0) {
|
||||
this.warning.emit({
|
||||
message: 'INVALID_PRESELECTED_GROUPS',
|
||||
groups: this.invalidGroups
|
||||
});
|
||||
this.generateInvalidGroupsMessage();
|
||||
}
|
||||
|
||||
this.warning.emit({
|
||||
message: 'INVALID_PRESELECTED_GROUPS',
|
||||
groups: this.invalidGroups
|
||||
});
|
||||
}
|
||||
|
||||
private loadPreSelectGroups() {
|
||||
if (!this.isMultipleMode()) {
|
||||
this.loadSinglePreselectGroup();
|
||||
} else {
|
||||
this.loadMultiplePreselectGroups();
|
||||
}
|
||||
}
|
||||
generateInvalidGroupsMessage() {
|
||||
this.validateGroupsMessage = '';
|
||||
|
||||
loadNoValidationPreselectGroups() {
|
||||
this.selectedGroups = [...this.removeDuplicatedGroups([...this.preSelectGroups])];
|
||||
if (this.isMultipleMode()) {
|
||||
this.selectedGroups$.next(this.selectedGroups);
|
||||
} else {
|
||||
|
||||
if (this.currentTimeout) {
|
||||
clearTimeout(this.currentTimeout);
|
||||
this.invalidGroups.forEach((invalidGroup: IdentityGroupModel, index) => {
|
||||
if (index === this.invalidGroups.length - 1) {
|
||||
this.validateGroupsMessage += `${invalidGroup.name} `;
|
||||
} else {
|
||||
this.validateGroupsMessage += `${invalidGroup.name}, `;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.currentTimeout = setTimeout(() => {
|
||||
this.searchGroupsControl.setValue(this.selectedGroups[0]);
|
||||
this.onSelect(this.selectedGroups[0]);
|
||||
}, 0);
|
||||
private async loadPreSelectGroups() {
|
||||
this.selectedGroups = [];
|
||||
|
||||
if (this.isSingleMode()) {
|
||||
this.selectedGroups = [this.preSelectGroups[0]];
|
||||
} else {
|
||||
this.selectedGroups = this.removeDuplicatedGroups(this.preSelectGroups);
|
||||
}
|
||||
|
||||
if (this.isValidationEnabled()) {
|
||||
this.isLoading = true;
|
||||
await this.validatePreselectGroups();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,33 +330,67 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (this.isMultipleMode()) {
|
||||
if (!this.isGroupAlreadySelected(group)) {
|
||||
this.selectedGroups.push(group);
|
||||
this.selectedGroups$.next(this.selectedGroups);
|
||||
this.searchGroups$.next([]);
|
||||
}
|
||||
this.groupInput.nativeElement.value = '';
|
||||
this.searchGroupsControl.setValue('');
|
||||
} else {
|
||||
this.invalidGroups = [];
|
||||
this.selectedGroups = [group];
|
||||
}
|
||||
this.changedGroups.emit(this.selectedGroups);
|
||||
|
||||
this.clearError();
|
||||
this.groupInput.nativeElement.value = '';
|
||||
this.searchGroupsControl.setValue('');
|
||||
|
||||
this.changedGroups.emit(this.selectedGroups);
|
||||
this.resetSearchGroups();
|
||||
}
|
||||
|
||||
onRemove(removedGroup: IdentityGroupModel) {
|
||||
this.removeGroup.emit(removedGroup);
|
||||
onRemove(groupToRemove: IdentityGroupModel) {
|
||||
this.removeGroup.emit(groupToRemove);
|
||||
const indexToRemove = this.selectedGroups.findIndex((group: IdentityGroupModel) => {
|
||||
return group.id === removedGroup.id;
|
||||
return group.id === groupToRemove.id;
|
||||
});
|
||||
this.selectedGroups.splice(indexToRemove, 1);
|
||||
this.selectedGroups$.next(this.selectedGroups);
|
||||
this.changedGroups.emit(this.selectedGroups);
|
||||
this.searchGroupsControl.markAsDirty();
|
||||
|
||||
if (this.isValidationEnabled()) {
|
||||
this.removeGroupFromValidation(groupToRemove.name);
|
||||
this.checkPreselectValidationErrors();
|
||||
}
|
||||
}
|
||||
|
||||
private removeGroupFromValidation(groupName: string) {
|
||||
const indexToRemove = this.invalidGroups.findIndex((invalidGroup) => {
|
||||
return invalidGroup.name === groupName;
|
||||
});
|
||||
|
||||
if (indexToRemove !== -1) {
|
||||
this.invalidGroups.splice(indexToRemove, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private resetSearchGroups() {
|
||||
this.searchGroups = [];
|
||||
this.searchGroups$.next([]);
|
||||
this.searchGroupsSubject.next(this.searchGroups);
|
||||
}
|
||||
|
||||
hasGroupIdOrName(group: IdentityGroupModel): boolean {
|
||||
return group && (group.id !== undefined || group.name !== undefined);
|
||||
}
|
||||
|
||||
isSingleMode(): boolean {
|
||||
return this.mode === GroupCloudComponent.MODE_SINGLE;
|
||||
}
|
||||
|
||||
private isSingleSelectionReadonly(): boolean {
|
||||
return this.isSingleMode() && this.selectedGroups.length === 1 && this.selectedGroups[0].readonly === true;
|
||||
}
|
||||
|
||||
hasPreselectError(): boolean {
|
||||
return this.invalidGroups && this.invalidGroups.length > 0;
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
return this.readOnly || this.isSingleSelectionReadonly();
|
||||
}
|
||||
|
||||
isMultipleMode(): boolean {
|
||||
@@ -405,58 +401,76 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return group ? group.name : '';
|
||||
}
|
||||
|
||||
private removeDuplicatedGroups(groups: IdentityGroupModel[]): IdentityGroupModel[] {
|
||||
removeDuplicatedGroups(groups: IdentityGroupModel[]): IdentityGroupModel[] {
|
||||
return groups.filter((group, index, self) =>
|
||||
index === self.findIndex((auxGroup) => {
|
||||
return group.id === auxGroup.id && group.name === auxGroup.name;
|
||||
}));
|
||||
index === self.findIndex((auxGroup) => {
|
||||
return group.id === auxGroup.id && group.name === auxGroup.name;
|
||||
}));
|
||||
}
|
||||
|
||||
private hasPreSelectGroups(): boolean {
|
||||
return this.preSelectGroups && this.preSelectGroups.length > 0;
|
||||
}
|
||||
|
||||
private hasModeChanged(changes): boolean {
|
||||
return changes && changes.mode && changes.mode.currentValue !== changes.mode.previousValue;
|
||||
}
|
||||
|
||||
private isValidationChanged(changes): boolean {
|
||||
return changes && changes.validate && changes.validate.currentValue !== changes.validate.previousValue;
|
||||
}
|
||||
|
||||
private hasPreselectedGroupsChanged(changes): boolean {
|
||||
return changes && changes.preSelectGroups && changes.preSelectGroups.currentValue !== changes.preSelectGroups.previousValue;
|
||||
}
|
||||
|
||||
private hasPreselectedGroupsCleared(changes): boolean {
|
||||
return changes && changes.preSelectGroups && changes.preSelectGroups.currentValue.length === 0;
|
||||
}
|
||||
|
||||
private createSearchParam(value: string): IdentityGroupSearchParam {
|
||||
const queryParams: IdentityGroupSearchParam = { name: value };
|
||||
return queryParams;
|
||||
}
|
||||
|
||||
getSelectedGroups(): IdentityGroupModel[] {
|
||||
return this.selectedGroups;
|
||||
}
|
||||
|
||||
private hasRoles(): boolean {
|
||||
return this.roles && this.roles.length > 0;
|
||||
}
|
||||
|
||||
private disableSearch() {
|
||||
this.searchGroupsControl.disable();
|
||||
this.isDisabled = true;
|
||||
}
|
||||
|
||||
private enableSearch() {
|
||||
this.searchGroupsControl.enable();
|
||||
this.isDisabled = false;
|
||||
}
|
||||
|
||||
private setError() {
|
||||
this.searchGroupsControl.setErrors({ invalid: true });
|
||||
}
|
||||
|
||||
private clearError() {
|
||||
this.searchGroupsControl.setErrors(null);
|
||||
private setTypingError() {
|
||||
this.searchGroupsControl.setErrors({ searchTypingError: true, ...this.searchGroupsControl.errors });
|
||||
}
|
||||
|
||||
hasError(): boolean {
|
||||
return this.searchGroupsControl && this.searchGroupsControl.errors && (this.searchGroupsControl.errors.invalid || this.searchGroupsControl.errors.required);
|
||||
return !!this.searchGroupsControl.errors;
|
||||
}
|
||||
|
||||
isValidationLoading(): boolean {
|
||||
return this.isValidationEnabled() && this.isLoading;
|
||||
}
|
||||
|
||||
setFocus(isFocused: boolean) {
|
||||
this.isFocused = isFocused;
|
||||
}
|
||||
|
||||
isValidationEnabled() {
|
||||
isValidationEnabled(): boolean {
|
||||
return this.validate === true;
|
||||
}
|
||||
|
||||
hasErrorMessage(): boolean {
|
||||
return !this.isFocused && this.hasError();
|
||||
getValidationPattern(): string {
|
||||
return this.searchGroupsControl.errors.pattern.requiredPattern;
|
||||
}
|
||||
|
||||
getValidationMaxLength(): string {
|
||||
return this.searchGroupsControl.errors.maxlength.requiredLength;
|
||||
}
|
||||
|
||||
getValidationMinLength(): string {
|
||||
return this.searchGroupsControl.errors.minlength.requiredLength;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
Reference in New Issue
Block a user