diff --git a/demo-shell/resources/i18n/en.json b/demo-shell/resources/i18n/en.json index 268c079ac3..059f18ce4e 100644 --- a/demo-shell/resources/i18n/en.json +++ b/demo-shell/resources/i18n/en.json @@ -308,7 +308,11 @@ "APP_NAME": "Application Name", "APP_FILTER_MODE": "Filter by application name", "ROLE_FILTER_MODE": "Filter by role", - "PRESELECT_VALIDATION": "Preselect validation" + "PRESELECT_VALIDATION": "Preselect validation", + "ALL_SELECTED_USERS": "All Selected Users", + "ALL_SELECTED_GROUPS": "All Selected Groups", + "INVALID_USERS": "Invalid Users", + "INVALID_GROUPS": "Invalid Groups" }, "SETTINGS_CLOUD": { "MULTISELECTION": "Multiselection", diff --git a/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.html b/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.html index 247bf07f54..b42ad00475 100644 --- a/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.html +++ b/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.html @@ -40,16 +40,29 @@ [appName]="peopleAppName" [roles]="peopleRoles" [title]="'ADF_TASK_LIST.START_TASK.FORM.LABEL.ASSIGNEE'" - [mode]="peopleMode"> + [mode]="peopleMode" + (selectUser)="onSelectUser($event)" + (warning)="onUsersWarning($event)">
+

{{ 'PEOPLE_GROUPS_CLOUD.ALL_SELECTED_USERS' | translate }}

person - {{ item?.firstName }} {{ item?.lastName }} + {{item | fullName}} + +
+

{{ 'PEOPLE_GROUPS_CLOUD.INVALID_USERS' | translate }} warning

+ + + person + {{invalidUser | fullName}} + + +
@@ -86,19 +99,39 @@ Preselect: {{ DEFAULT_GROUP_PLACEHOLDER }} + {{ + 'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }}
- +
+

{{ 'PEOPLE_GROUPS_CLOUD.ALL_SELECTED_GROUPS' | translate }}

group {{ item.name }} + +
+

{{ 'PEOPLE_GROUPS_CLOUD.INVALID_GROUPS' | translate }} warning

+ + + group + {{invalidGroup?.name}} + + +
diff --git a/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.ts b/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.ts index d353e2079d..6dd41084b9 100644 --- a/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.ts +++ b/demo-shell/src/app/components/cloud/people-groups-cloud-demo.component.ts @@ -18,7 +18,7 @@ import { Component, ViewEncapsulation } from '@angular/core'; import { PeopleCloudComponent, GroupCloudComponent } from '@alfresco/adf-process-services-cloud'; import { MatRadioChange, MatCheckboxChange } from '@angular/material'; -import { IdentityGroupModel } from '@alfresco/adf-core'; +import { IdentityGroupModel, IdentityUserModel } from '@alfresco/adf-core'; @Component({ selector: 'app-people-groups-cloud', @@ -33,14 +33,17 @@ export class PeopleGroupCloudDemoComponent { DEFAULT_PEOPLE_PLACEHOLDER: string = `[{"id": "1", email": "user@user.com", "firstName":"user", "lastName": "lastName", "username": "user"}]`; peopleMode: string = PeopleCloudComponent.MODE_SINGLE; - preSelectUsers: string[] = []; + preSelectUsers: IdentityUserModel[] = []; + invalidUsers: IdentityGroupModel[] = []; peopleRoles: string[] = []; peopleAppName: string; peopleFilterMode: string = this.DEFAULT_FILTER_MODE; peoplePreselectValidation: Boolean = false; + groupPreselectValidation = false; groupMode: string = GroupCloudComponent.MODE_SINGLE; preSelectGroup: IdentityGroupModel[] = []; + invalidGroups: IdentityGroupModel[] = []; selectedGroupList: IdentityGroupModel[] = []; groupRoles: string[]; groupAppName: string; @@ -117,6 +120,25 @@ export class PeopleGroupCloudDemoComponent { onChangePeopleValidation(event: MatCheckboxChange) { this.peoplePreselectValidation = event.checked; this.preSelectUsers = [...this.preSelectUsers]; + if (!this.peoplePreselectValidation) { + this.invalidUsers = []; + } + } + + onChangeGroupValidation(event: MatCheckboxChange) { + this.groupPreselectValidation = event.checked; + this.preSelectGroup = [...this.preSelectGroup]; + if (!this.groupPreselectValidation) { + this.invalidGroups = []; + } + } + + onGroupsWarning(warning: any) { + this.invalidGroups = warning.groups; + } + + onUsersWarning(warning: any) { + this.invalidUsers = warning.users; } isStringArray(str: string) { @@ -149,6 +171,12 @@ export class PeopleGroupCloudDemoComponent { this.preSelectGroup = this.preSelectGroup.filter((value: any) => value.id !== group.id); } + onSelectUser(user: IdentityUserModel) { + if (this.peopleMode === PeopleCloudComponent.MODE_MULTIPLE) { + this.preSelectUsers.push(user); + } + } + onSelectGroup(group: IdentityGroupModel) { if (this.groupMode === GroupCloudComponent.MODE_MULTIPLE) { this.preSelectGroup.push(group); diff --git a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts index 70ad3d966b..b1df775c7b 100644 --- a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.spec.ts @@ -324,8 +324,8 @@ describe('GroupCloudComponent', () => { component.preSelectGroups = []; fixture.detectChanges(); fixture.whenStable().then(() => { - const selectedUser = component.searchGroupsControl.value; - expect(selectedUser).toBeNull(); + const selectedGroup = component.searchGroupsControl.value; + expect(selectedGroup).toBeNull(); done(); }); }); @@ -355,7 +355,7 @@ describe('GroupCloudComponent', () => { it('should pre-select all preSelectGroups when mode=multiple', (done) => { component.mode = 'multiple'; fixture.detectChanges(); - component.ngOnChanges({ 'preSelectUsers': change }); + component.ngOnChanges({ 'preSelectGroups': change }); fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); @@ -407,7 +407,7 @@ describe('GroupCloudComponent', () => { }); }); - it('should not filter groups if user does not have any specified role', (done) => { + it('should not filter groups if group does not have any specified role', (done) => { fixture.detectChanges(); checkGroupHasRoleSpy.and.returnValue(of(false)); const inputHTMLElement: HTMLInputElement = element.querySelector('input'); @@ -498,4 +498,64 @@ describe('GroupCloudComponent', () => { }); }); }); + + describe('Multiple Mode and Pre-selected groups with validate flag', () => { + + beforeEach(async(() => { + component.mode = 'multiple'; + component.validate = true; + component.preSelectGroups = mockIdentityGroups; + element = fixture.nativeElement; + alfrescoApiService = TestBed.get(AlfrescoApiService); + fixture.detectChanges(); + })); + + it('should emit warning if are invalid groups', (done) => { + findGroupsByNameSpy.and.returnValue(Promise.resolve([])); + const warnMessage = { message: 'INVALID_PRESELECTED_GROUPS', groups: [{ name: 'invalidGroupOne' }, { name: 'invalidGroupTwo' }] }; + component.validate = true; + component.preSelectGroups = [{ name: 'invalidGroupOne' }, { name: 'invalidGroupTwo' }]; + fixture.detectChanges(); + component.loadSinglePreselectGroup(); + component.warning.subscribe((response) => { + expect(response).toEqual(warnMessage); + expect(response.message).toEqual(warnMessage.message); + expect(response.groups).toEqual(warnMessage.groups); + expect(response.groups[0].name).toEqual('invalidGroupOne'); + done(); + }); + }); + + it('should filter group by name if validate true', (done) => { + findGroupsByNameSpy.and.returnValue(of(mockIdentityGroups)); + component.mode = 'multiple'; + component.validate = true; + component.preSelectGroups = [{ name: mockIdentityGroups[1].name }, { name: mockIdentityGroups[2].name }]; + fixture.detectChanges(); + fixture.whenStable().then(() => { + component.filterPreselectGroups().then((result) => { + expect(findGroupsByNameSpy).toHaveBeenCalled(); + expect(component.groupExists(result[0])).toEqual(true); + expect(component.groupExists(result[1])).toEqual(true); + done(); + }); + }); + }); + + it('should not preselect any group if name is invalid and validation enable', (done) => { + findGroupsByNameSpy.and.returnValue(of([])); + component.mode = 'single'; + component.validate = true; + component.preSelectGroups = [{ name: 'invalid group' }]; + fixture.detectChanges(); + fixture.whenStable().then(() => { + component.validatePreselectGroups().then((result) => { + fixture.detectChanges(); + expect(findGroupsByNameSpy).toHaveBeenCalled(); + expect(result.length).toEqual(0); + done(); + }); + }); + }); + }); }); diff --git a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.ts b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.ts index 11d48a9130..4b537ce123 100644 --- a/lib/process-services-cloud/src/lib/group/components/group-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/group/components/group-cloud.component.ts @@ -27,14 +27,15 @@ import { SimpleChanges, OnChanges, OnDestroy, - ChangeDetectionStrategy + ChangeDetectionStrategy, + SimpleChange } from '@angular/core'; import { FormControl } from '@angular/forms'; 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 } from '@alfresco/adf-core'; +import { IdentityGroupModel, IdentityGroupSearchParam, IdentityGroupService, LogService } from '@alfresco/adf-core'; @Component({ selector: 'adf-cloud-group', @@ -57,7 +58,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { static MODE_SINGLE = 'single'; static MODE_MULTIPLE = 'multiple'; - /** Name of the application. If specified this shows the users who have access to the app. */ + /** Name of the application. If specified this shows the groups who have access to the app. */ @Input() appName: string; @@ -65,14 +66,21 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { @Input() title: string; - /** User selection mode (single/multiple). */ + /** Group selection mode (single/multiple). */ @Input() mode: string = GroupCloudComponent.MODE_SINGLE; - /** Array of users to be pre-selected. This pre-selects all users in multi selection mode and only the first user of the array in single selection mode. */ + /** Array of groups to be pre-selected. This pre-selects all groups in multi selection mode and only the first group of the array in single selection mode. */ @Input() preSelectGroups: IdentityGroupModel[] = []; + /** This flag enables the validation on the preSelectGroups passed as input. + * In case the flag is true the components call the identity service to verify the validity of the information passed as input. + * Otherwise, no check will be done. + */ + @Input() + validate: Boolean = false; + /** Show the info in readonly mode */ @Input() @@ -98,6 +106,10 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { @Output() changedGroups = new EventEmitter(); + /** Emitted when an warning occurs. */ + @Output() + warning = new EventEmitter(); + @ViewChild('groupInput') private groupInput: ElementRef; @@ -121,8 +133,12 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { private onDestroy$ = new Subject(); currentTimeout: any; + invalidGroups: IdentityGroupModel[] = []; - constructor(private identityGroupService: IdentityGroupService) { } + constructor( + private identityGroupService: IdentityGroupService, + private logService: LogService + ) { } ngOnInit() { this.initSearch(); @@ -130,13 +146,15 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { ngOnChanges(changes: SimpleChanges) { - if (changes.preSelectGroups && this.hasPreSelectGroups()) { - this.loadPreSelectGroups(); - } else { - this.searchGroupsControl.setValue(''); + if (this.isPreselectedGroupsChanged(changes)) { + if (this.isValidationEnabled()) { + this.loadPreSelectGroups(); + } else { + this.loadNoValidationPreselectGroups(); + } } - if (changes.appName && this.isAppNameChanged(changes.appName)) { + if (this.isAppNameChanged(changes.appName)) { this.disableSearch(); this.loadClientId(); } else { @@ -144,8 +162,14 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { } } - private isAppNameChanged(change): boolean { - return change.previousValue !== change.currentValue && this.appName && this.appName.length > 0; + 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; } private async loadClientId() { @@ -220,20 +244,114 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { return false; } - private loadPreSelectGroups() { - if (this.isMultipleMode()) { - const groups = this.removeDuplicatedGroups([...this.preSelectGroups]); + async searchGroup(groupName: any): Promise { + return (await this.identityGroupService.findGroupsByName(this.createSearchParam(groupName)).toPromise())[0]; + } + + async filterPreselectGroups() { + const promiseBatch = this.preSelectGroups.map(async (group: IdentityGroupModel) => { + let result: any; + try { + result = await this.searchGroup(group.name); + } catch (error) { + result = []; + 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 { + 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(); + } + } + + public checkPreselectValidationErrors() { + if (this.invalidGroups.length > 0) { + this.warning.emit({ + message: 'INVALID_PRESELECTED_GROUPS', + groups: this.invalidGroups + }); + } + } + + private loadPreSelectGroups() { + if (!this.isMultipleMode()) { + this.loadSinglePreselectGroup(); + } else { + this.loadMultiplePreselectGroups(); + } + } + + loadNoValidationPreselectGroups() { + this.selectedGroups = [...this.removeDuplicatedGroups([...this.preSelectGroups])]; + if (this.isMultipleMode()) { + this.selectedGroups$.next(this.selectedGroups); + } else { if (this.currentTimeout) { clearTimeout(this.currentTimeout); } this.currentTimeout = setTimeout(() => { - this.searchGroupsControl.setValue(this.preSelectGroups[0]); - this.onSelect(this.preSelectGroups[0]); + this.searchGroupsControl.setValue(this.selectedGroups[0]); + this.onSelect(this.selectedGroups[0]); }, 0); } } @@ -290,7 +408,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { private removeDuplicatedGroups(groups: IdentityGroupModel[]): IdentityGroupModel[] { return groups.filter((group, index, self) => index === self.findIndex((auxGroup) => { - return group.id === auxGroup.id; + return group.id === auxGroup.id && group.name === auxGroup.name; })); } @@ -333,6 +451,10 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy { this.isFocused = isFocused; } + isValidationEnabled() { + return this.validate === true; + } + hasErrorMessage(): boolean { return !this.isFocused && this.hasError(); }