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
@@ -1,61 +1,71 @@
|
||||
<div class="adf-cloud-group">
|
||||
<mat-form-field>
|
||||
<mat-label id="adf-group-cloud-title-id">{{ (title || 'ADF_CLOUD_GROUPS.SEARCH-GROUP') | translate }}</mat-label>
|
||||
<mat-chip-list #groupChipList *ngIf="isMultipleMode()" [disabled]="isDisabled" data-automation-id="adf-cloud-group-chip-list" class="apa-group-chip-list">
|
||||
<mat-chip
|
||||
*ngFor="let group of selectedGroups$ | async"
|
||||
[removable]="!(group.readonly)"
|
||||
(removed)="onRemove(group)"
|
||||
matTooltip="{{ (group.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}"
|
||||
[attr.data-automation-id]="'adf-cloud-group-chip-' + group.name">
|
||||
{{group.name}}
|
||||
<mat-icon
|
||||
*ngIf="!(group.readonly || readOnly)"
|
||||
matChipRemove [attr.data-automation-id]="'adf-cloud-group-chip-remove-icon-' + group.name">
|
||||
cancel
|
||||
</mat-icon>
|
||||
</mat-chip>
|
||||
<input matInput
|
||||
[formControl]="searchGroupsControl"
|
||||
class="adf-group-input"
|
||||
id="group-name"
|
||||
[attr.disabled]="isDisabled"
|
||||
data-automation-id="adf-cloud-group-search-input"
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="groupChipList" #groupInput>
|
||||
</mat-chip-list>
|
||||
<form>
|
||||
<mat-form-field class="adf-cloud-group">
|
||||
<mat-label
|
||||
id="adf-group-cloud-title-id">{{ (title || 'ADF_CLOUD_GROUPS.SEARCH-GROUP') | translate }}</mat-label>
|
||||
<mat-chip-list #groupChipList [disabled]="isReadonly() || isValidationLoading()" data-automation-id="adf-cloud-group-chip-list" class="apa-group-chip-list">
|
||||
<mat-chip
|
||||
*ngFor="let group of selectedGroups"
|
||||
[removable]="!(group.readonly)"
|
||||
[attr.data-automation-id]="'adf-cloud-group-chip-' + group.name"
|
||||
(removed)="onRemove(group)"
|
||||
matTooltip="{{ (group.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}">
|
||||
{{group.name}}
|
||||
<mat-icon
|
||||
*ngIf="!(group.readonly || readOnly)"
|
||||
matChipRemove [attr.data-automation-id]="'adf-cloud-group-chip-remove-icon-' + group.name">
|
||||
cancel
|
||||
</mat-icon>
|
||||
</mat-chip>
|
||||
<input matInput
|
||||
[formControl]="searchGroupsControl"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="groupChipList"
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
class="adf-group-input"
|
||||
id="group-name"
|
||||
data-automation-id="adf-cloud-group-search-input" #groupInput>
|
||||
</mat-chip-list>
|
||||
|
||||
<input *ngIf="!isMultipleMode()"
|
||||
matInput
|
||||
class="adf-group-input"
|
||||
data-automation-id="adf-cloud-group-search-input"
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
[formControl]="searchGroupsControl"
|
||||
[matAutocomplete]="auto">
|
||||
<mat-autocomplete
|
||||
autoActiveFirstOption
|
||||
#auto="matAutocomplete"
|
||||
class="adf-cloud-group-list"
|
||||
(optionSelected)="onSelect($event.option.value)"
|
||||
[displayWith]="getDisplayName"
|
||||
data-automation-id="adf-cloud-group-autocomplete">
|
||||
<mat-option *ngFor="let group of searchGroups$ | async; let i = index" [value]="group"
|
||||
[attr.data-automation-id]="'adf-cloud-group-chip-' + group.name">
|
||||
<div class="adf-cloud-group-row" id="adf-group-{{i}}" fxLayout="row" fxLayoutAlign="start center"
|
||||
fxLayoutGap="20px">
|
||||
<button class="adf-group-short-name" mat-fab>{{group | groupNameInitial }}</button>
|
||||
<span>{{group.name}}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
<mat-progress-bar
|
||||
*ngIf="isLoading"
|
||||
mode="indeterminate">
|
||||
</mat-progress-bar>
|
||||
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
class="adf-cloud-group-list"
|
||||
(optionSelected)="onSelect($event.option.value)"
|
||||
[displayWith]="getDisplayName"
|
||||
data-automation-id="adf-cloud-group-autocomplete">
|
||||
<mat-option *ngFor="let group of searchGroups$ | async; let i = index" [value]="group" [attr.data-automation-id]="'adf-cloud-group-chip-' + group.name">
|
||||
<div class="adf-cloud-group-row" id="adf-group-{{i}}" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="20px">
|
||||
<button class="adf-group-short-name" mat-fab>{{group | groupNameInitial }}</button>
|
||||
<span>{{group.name}}</span>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
<div class="adf-cloud-group-error" *ngIf="hasErrorMessage()">
|
||||
<div fxLayout="row" fxLayoutAlign="start start" [@transitionMessages]="_subscriptAnimationState">
|
||||
<div class="adf-cloud-group-error-message">
|
||||
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : searchedValue } }}
|
||||
</div>
|
||||
<mat-icon class="adf-cloud-group-error-icon">warning</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : validateGroupsMessage } }}</mat-error>
|
||||
<mat-error *ngIf="searchGroupsControl.hasError('pattern')">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }}</mat-error>
|
||||
<mat-error *ngIf="searchGroupsControl.hasError('maxlength')">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="searchGroupsControl.hasError('minlength')">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</mat-error>
|
||||
<mat-error *ngIf="searchGroupsControl.hasError('required')">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }} </mat-error>
|
||||
<mat-error *ngIf="searchGroupsControl.hasError('searchTypingError') && !this.isFocused" data-automation-id="invalid-groups-typing-error">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : searchedValue } }}</mat-error>
|
||||
</form>
|
||||
|
@@ -6,7 +6,14 @@
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
.adf {
|
||||
&-cloud-group-list {
|
||||
margin: 5px 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
&-cloud-group {
|
||||
width: 100%;
|
||||
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -72,7 +72,7 @@ describe('GroupCloudComponent', () => {
|
||||
component.title = 'TITLE_KEY';
|
||||
fixture.detectChanges();
|
||||
const matLabel: HTMLInputElement = <HTMLInputElement> element.querySelector('#adf-group-cloud-title-id');
|
||||
fixture.whenStable().then( () => {
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(matLabel.textContent).toEqual('TITLE_KEY');
|
||||
});
|
||||
@@ -86,14 +86,14 @@ describe('GroupCloudComponent', () => {
|
||||
findGroupsByNameSpy = spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of(mockIdentityGroups));
|
||||
}));
|
||||
|
||||
it('should list the group if the typed result match', (done) => {
|
||||
findGroupsByNameSpy.and.returnValue(of(mockIdentityGroups));
|
||||
it('should list the groups as dropdown options if the search term has results', (done) => {
|
||||
const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||
inputHTMLElement.focus();
|
||||
inputHTMLElement.value = 'Mock';
|
||||
inputHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(5);
|
||||
@@ -116,13 +116,19 @@ describe('GroupCloudComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit selectedGroup if option is valid', (done) => {
|
||||
it('should selectedGroup and groupsChanged emit, update selected groups when a group is selected', (done) => {
|
||||
const group = { name: 'groupname' };
|
||||
fixture.detectChanges();
|
||||
spyOn(component, 'hasGroupIdOrName').and.returnValue(true);
|
||||
const selectEmitSpy = spyOn(component.selectGroup, 'emit');
|
||||
component.onSelect({ name: 'groupname' });
|
||||
const changedGroupsSpy = spyOn(component.changedGroups, 'emit');
|
||||
component.onSelect(group);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(selectEmitSpy).toHaveBeenCalled();
|
||||
expect(selectEmitSpy).toHaveBeenCalledWith(group);
|
||||
expect(changedGroupsSpy).toHaveBeenCalledWith([group]);
|
||||
expect(component.getSelectedGroups()[0]).toEqual(group);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -139,9 +145,9 @@ describe('GroupCloudComponent', () => {
|
||||
fixture.whenStable().then(() => {
|
||||
inputHTMLElement.blur();
|
||||
fixture.detectChanges();
|
||||
const errorMessage = element.querySelector('.adf-cloud-group-error-message');
|
||||
const errorMessage = element.querySelector('[data-automation-id="invalid-groups-typing-error"]');
|
||||
expect(errorMessage).not.toBeNull();
|
||||
expect(errorMessage.textContent).toContain(' ADF_CLOUD_GROUPS.ERROR.NOT_FOUND ');
|
||||
expect(errorMessage.textContent).toContain('ADF_CLOUD_GROUPS.ERROR.NOT_FOUND');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -162,13 +168,13 @@ describe('GroupCloudComponent', () => {
|
||||
element = fixture.nativeElement;
|
||||
}));
|
||||
|
||||
it('should fetch the client ID if appName specified', async(() => {
|
||||
it('should fetch the client ID if appName specified', async (() => {
|
||||
const getClientIdByApplicationNameSpy = spyOn(identityGroupService, 'getClientIdByApplicationName').and.callThrough();
|
||||
component.appName = 'mock-app-name';
|
||||
const change = new SimpleChange(null, 'mock-app-name', false);
|
||||
component.ngOnChanges({ 'appName': change });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then( () => {
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(getClientIdByApplicationNameSpy).toHaveBeenCalled();
|
||||
});
|
||||
@@ -294,89 +300,31 @@ describe('GroupCloudComponent', () => {
|
||||
fixture.whenStable().then(() => {
|
||||
inputHTMLElement.blur();
|
||||
fixture.detectChanges();
|
||||
const errorMessage = element.querySelector('.adf-cloud-group-error-message');
|
||||
const errorMessage = element.querySelector('[data-automation-id="invalid-groups-typing-error"]');
|
||||
expect(errorMessage).not.toBeNull();
|
||||
expect(errorMessage.textContent).toContain(' ADF_CLOUD_GROUPS.ERROR.NOT_FOUND ');
|
||||
expect(errorMessage.textContent).toContain('ADF_CLOUD_GROUPS.ERROR.NOT_FOUND');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Single Mode and Pre-selected groups', () => {
|
||||
describe('No preselected groups', () => {
|
||||
beforeEach(async () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
beforeEach(async(() => {
|
||||
it('should not pre-select any group when preSelectGroups is empty - single mode', () => {
|
||||
component.mode = 'single';
|
||||
component.preSelectGroups = <any> mockIdentityGroups;
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
}));
|
||||
|
||||
it('should not show chip list when mode=single', (done) => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const chip = element.querySelector('mat-chip-list');
|
||||
expect(chip).toBeNull();
|
||||
done();
|
||||
});
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
expect(chips.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should not pre-select any group when preSelectGroups is empty and mode=single', (done) => {
|
||||
component.preSelectGroups = [];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const selectedGroup = component.searchGroupsControl.value;
|
||||
expect(selectedGroup).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Mode and Pre-selected groups', () => {
|
||||
|
||||
const change = new SimpleChange(null, mockIdentityGroups, false);
|
||||
|
||||
beforeEach(async(() => {
|
||||
component.mode = 'multiple';
|
||||
component.preSelectGroups = <any> mockIdentityGroups;
|
||||
component.ngOnChanges({ 'preSelectGroups': change });
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
}));
|
||||
|
||||
it('should show chip list when mode=multiple', (done) => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const chip = element.querySelector('mat-chip-list');
|
||||
expect(chip).toBeDefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pre-select all preSelectGroups when mode=multiple', (done) => {
|
||||
it('should not pre-select any group when preSelectGroups is empty - multiple mode', () => {
|
||||
component.mode = 'multiple';
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({ 'preSelectGroups': change });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
expect(chips.length).toBe(5);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit removeGroup when a selected group is removed', (done) => {
|
||||
const removeSpy = spyOn(component.removeGroup, 'emit');
|
||||
component.mode = 'multiple';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const removeIcon = fixture.debugElement.query(By.css('mat-chip mat-icon'));
|
||||
removeIcon.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
expect(removeSpy).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
expect(chips.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -439,57 +387,114 @@ describe('GroupCloudComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Single Mode with pre-selected groups', () => {
|
||||
|
||||
const changes = new SimpleChange(null, mockIdentityGroups, false);
|
||||
|
||||
beforeEach(async(() => {
|
||||
component.mode = 'single';
|
||||
component.preSelectGroups = <any> mockIdentityGroups;
|
||||
component.ngOnChanges({ 'preSelectGroups': changes });
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
}));
|
||||
|
||||
it('should show only one mat chip with the first preSelectedGroup', () => {
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
expect(chips.length).toEqual(1);
|
||||
expect(chips[0].attributes['data-automation-id']).toEqual(`adf-cloud-group-chip-${mockIdentityGroups[0].name}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Mode with pre-selected groups', () => {
|
||||
|
||||
const change = new SimpleChange(null, mockIdentityGroups, false);
|
||||
|
||||
beforeEach(async(() => {
|
||||
component.mode = 'multiple';
|
||||
component.preSelectGroups = <any> mockIdentityGroups;
|
||||
component.ngOnChanges({ 'preSelectGroups': change });
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
}));
|
||||
|
||||
it('should pre-select all preSelectGroups', () => {
|
||||
component.mode = 'multiple';
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({ 'preSelectGroups': change });
|
||||
fixture.detectChanges();
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
expect(chips.length).toBe(5);
|
||||
});
|
||||
|
||||
it('should removeGroup and changedGroups emit when a selected group is removed', (done) => {
|
||||
const removeGroupEmitterSpy = spyOn(component.removeGroup, 'emit');
|
||||
const changedGroupsEmitterSpy = spyOn(component.changedGroups, 'emit');
|
||||
const groupToRemove = mockIdentityGroups[0];
|
||||
component.mode = 'multiple';
|
||||
fixture.detectChanges();
|
||||
|
||||
const removeIcon = fixture.debugElement.query(By.css('mat-chip mat-icon'));
|
||||
removeIcon.nativeElement.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(removeGroupEmitterSpy).toHaveBeenCalledWith(groupToRemove);
|
||||
expect(changedGroupsEmitterSpy).toHaveBeenCalledWith([mockIdentityGroups[1], mockIdentityGroups[2], mockIdentityGroups[3], mockIdentityGroups[4]]);
|
||||
expect(component.getSelectedGroups().indexOf({
|
||||
id: groupToRemove.id,
|
||||
name: groupToRemove.name,
|
||||
path: groupToRemove.path
|
||||
})).toEqual(-1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Multiple Mode with read-only', () => {
|
||||
|
||||
it('Should not show remove icon for pre-selected groups if readonly property set to true', (done) => {
|
||||
fixture.detectChanges();
|
||||
const preselectedGroups = [
|
||||
component.preSelectGroups = [
|
||||
{ id: mockIdentityGroups[0].id, name: mockIdentityGroups[0].name, readonly: true },
|
||||
{ id: mockIdentityGroups[1].id, name: mockIdentityGroups[1].name, readonly: true }
|
||||
];
|
||||
component.preSelectGroups = preselectedGroups;
|
||||
const change = new SimpleChange(null, preselectedGroups, false);
|
||||
const change = new SimpleChange(null, component.preSelectGroups, false);
|
||||
component.mode = 'multiple';
|
||||
component.ngOnChanges({ 'preSelectGroups': change });
|
||||
fixture.detectChanges();
|
||||
const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip');
|
||||
const removeIcon = <HTMLElement> fixture.nativeElement.querySelector('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]');
|
||||
expect(chipList.length).toBe(2);
|
||||
expect(component.preSelectGroups[0].readonly).toBeTruthy();
|
||||
expect(component.preSelectGroups[1].readonly).toBeTruthy();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip');
|
||||
const removeIcon = <HTMLElement> fixture.nativeElement.querySelector('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]');
|
||||
expect(chipList.length).toBe(2);
|
||||
expect(component.preSelectGroups[0].readonly).toBeTruthy();
|
||||
expect(component.preSelectGroups[1].readonly).toBeTruthy();
|
||||
expect(removeIcon).toBeNull();
|
||||
expect(component.preSelectGroups.length).toBe(2);
|
||||
expect(component.preSelectGroups[0].readonly).toBe(true, 'Not removable');
|
||||
expect(component.preSelectGroups[1].readonly).toBe(true, 'Not removable');
|
||||
expect(fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip').length).toBe(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to remove preselected groups if readonly property set to false', (done) => {
|
||||
fixture.detectChanges();
|
||||
const preselectedGroups = [
|
||||
component.preSelectGroups = [
|
||||
{ id: mockIdentityGroups[0].id, name: mockIdentityGroups[0].name, readonly: false },
|
||||
{ id: mockIdentityGroups[1].id, name: mockIdentityGroups[1].name, readonly: false }
|
||||
];
|
||||
component.preSelectGroups = preselectedGroups;
|
||||
const change = new SimpleChange(null, preselectedGroups, false);
|
||||
const change = new SimpleChange(null, component.preSelectGroups, false);
|
||||
component.mode = 'multiple';
|
||||
const removeGroupSpy = spyOn(component.removeGroup, 'emit');
|
||||
component.ngOnChanges({ 'preSelectGroups': change });
|
||||
const removeGroupSpy = spyOn(component.removeGroup, 'emit');
|
||||
fixture.detectChanges();
|
||||
const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip');
|
||||
const removeIcon = <HTMLElement> fixture.nativeElement.querySelector('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]');
|
||||
expect(chipList.length).toBe(2);
|
||||
expect(component.preSelectGroups[0].readonly).toBe(false, 'Removable');
|
||||
expect(component.preSelectGroups[1].readonly).toBe(false, 'Removable');
|
||||
removeIcon.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
const removeIcon = <HTMLElement> fixture.nativeElement.querySelector('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]');
|
||||
expect(chips.length).toBe(2);
|
||||
expect(component.preSelectGroups[0].readonly).toBe(false, 'Removable');
|
||||
expect(component.preSelectGroups[1].readonly).toBe(false, 'Removable');
|
||||
removeIcon.click();
|
||||
fixture.detectChanges();
|
||||
expect(removeGroupSpy).toHaveBeenCalled();
|
||||
@@ -497,65 +502,101 @@ describe('GroupCloudComponent', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Mode and Pre-selected groups with validate flag', () => {
|
||||
describe('Component readonly mode', () => {
|
||||
const change = new SimpleChange(null, mockIdentityGroups, false);
|
||||
|
||||
beforeEach(async(() => {
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
component.preSelectGroups = <any> mockIdentityGroups;
|
||||
element = fixture.nativeElement;
|
||||
alfrescoApiService = TestBed.get(AlfrescoApiService);
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
it('should chip list be disabled and show one single chip - single mode', () => {
|
||||
component.mode = 'single';
|
||||
component.readOnly = true;
|
||||
component.preSelectGroups = <any> mockIdentityGroups;
|
||||
component.ngOnChanges({ 'preSelectGroups': change });
|
||||
|
||||
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 = <any> [{ 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();
|
||||
fixture.detectChanges();
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
const chipList = fixture.nativeElement.querySelector('mat-chip-list');
|
||||
expect(chips).toBeDefined();
|
||||
expect(chipList).toBeDefined();
|
||||
expect(chips.length).toBe(1);
|
||||
expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
|
||||
});
|
||||
|
||||
it('should chip list be disabled and show all the chips - multiple mode', () => {
|
||||
component.mode = 'multiple';
|
||||
component.readOnly = true;
|
||||
component.preSelectGroups = <any> mockIdentityGroups;
|
||||
component.ngOnChanges({ 'preSelectGroups': change });
|
||||
fixture.detectChanges();
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
const chipList = fixture.nativeElement.querySelector('mat-chip-list');
|
||||
expect(chips).toBeDefined();
|
||||
expect(chipList).toBeDefined();
|
||||
expect(chips.length).toBe(5);
|
||||
expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter group by name if validate true', (done) => {
|
||||
findGroupsByNameSpy.and.returnValue(of(mockIdentityGroups));
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
component.preSelectGroups = <any> [{ 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);
|
||||
describe('Preselected groups and validation enabled', () => {
|
||||
|
||||
it('should check validation only for the first group and emit warning when group is invalid - single mode', (done) => {
|
||||
spyOn(identityGroupService, 'findGroupsByName').and.returnValue(Promise.resolve([]));
|
||||
spyOn(component, 'hasGroupIdOrName').and.returnValue(false);
|
||||
|
||||
const expectedWarning = {
|
||||
message: 'INVALID_PRESELECTED_GROUPS',
|
||||
groups: [{
|
||||
id: mockIdentityGroups[0].id,
|
||||
name: mockIdentityGroups[0].name,
|
||||
path: mockIdentityGroups[0].path,
|
||||
subGroups: []
|
||||
}]
|
||||
};
|
||||
component.warning.subscribe(warning => {
|
||||
expect(warning).toEqual(expectedWarning);
|
||||
done();
|
||||
});
|
||||
|
||||
component.mode = 'single';
|
||||
component.validate = true;
|
||||
component.preSelectGroups = <any> [mockIdentityGroups[0], mockIdentityGroups[1]];
|
||||
component.ngOnChanges({ 'preSelectGroups': new SimpleChange(null, [mockIdentityGroups[0], mockIdentityGroups[1]], false) });
|
||||
});
|
||||
|
||||
it('should check validation for all the groups and emit warning - multiple mode', (done) => {
|
||||
spyOn(identityGroupService, 'findGroupsByName').and.returnValue(Promise.resolve(undefined));
|
||||
|
||||
const expectedWarning = {
|
||||
message: 'INVALID_PRESELECTED_GROUPS',
|
||||
groups: [
|
||||
{
|
||||
id: mockIdentityGroups[0].id,
|
||||
name: mockIdentityGroups[0].name,
|
||||
path: mockIdentityGroups[0].path,
|
||||
subGroups: []
|
||||
},
|
||||
{
|
||||
id: mockIdentityGroups[1].id,
|
||||
name: mockIdentityGroups[1].name,
|
||||
path: mockIdentityGroups[1].path,
|
||||
subGroups: []
|
||||
}]
|
||||
};
|
||||
|
||||
component.warning.subscribe(warning => {
|
||||
expect(warning).toEqual(expectedWarning);
|
||||
done();
|
||||
});
|
||||
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
component.preSelectGroups = <any> [mockIdentityGroups[0], mockIdentityGroups[1]];
|
||||
component.ngOnChanges({ 'preSelectGroups': new SimpleChange(null, [mockIdentityGroups[0], mockIdentityGroups[1]], false) });
|
||||
});
|
||||
});
|
||||
|
||||
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 = <any> [{ name: 'invalid group' }];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.validatePreselectGroups().then((result) => {
|
||||
fixture.detectChanges();
|
||||
expect(findGroupsByNameSpy).toHaveBeenCalled();
|
||||
expect(result.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should removeDuplicatedGroups return only unique groups', () => {
|
||||
const duplicatedGroups = [{ name: mockIdentityGroups[0].name }, { name: mockIdentityGroups[0].name }];
|
||||
expect(component.removeDuplicatedGroups(duplicatedGroups)).toEqual([{ name: mockIdentityGroups[0].name }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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() {
|
||||
|
@@ -181,6 +181,20 @@
|
||||
"NOT_FOUND": "No group found with the name {{groupName}}"
|
||||
}
|
||||
},
|
||||
|
||||
"ADF_CLOUD_USERS": {
|
||||
"ERROR": {
|
||||
"NOT_FOUND": "No user found with the name {{userName}}"
|
||||
}
|
||||
},
|
||||
"ADF_CLOUD_PEOPLE_GROUPS": {
|
||||
"ERROR": {
|
||||
"INVALID_PATTERN": "Not mathcing pattern {{pattern}}",
|
||||
"INVALID_MIN_LENGTH": "Minimum length {{requiredLength}}",
|
||||
"INVALID_MAX_LENGTH": "Maximum length {{requiredLength}}",
|
||||
"REQUIRED": "Field is required"
|
||||
}
|
||||
},
|
||||
"ADF_CLOUD_TASK_HEADER": {
|
||||
"BUTTON": {
|
||||
"CLAIM": "Claim",
|
||||
|
@@ -1,47 +1,35 @@
|
||||
<form>
|
||||
<mat-form-field class="adf-people-cloud">
|
||||
<mat-label id="adf-people-cloud-title-id">{{ title | translate }}</mat-label>
|
||||
<mat-chip-list #userChipList *ngIf="isMultipleMode(); else singleSelection">
|
||||
<mat-chip
|
||||
*ngFor="let user of selectedUsers$ | async"
|
||||
[removable]="!(user.readonly)"
|
||||
[attr.data-automation-id]="'adf-people-cloud-chip-' + user.username"
|
||||
matTooltip="{{ (user.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}"
|
||||
(removed)="onRemove(user)">
|
||||
{{user | fullName}}
|
||||
<mat-icon
|
||||
matChipRemove
|
||||
*ngIf="!(user.readonly || readOnly)"
|
||||
[attr.data-automation-id]="'adf-people-cloud-chip-remove-icon-' + user.username">
|
||||
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 #userMultipleChipList [disabled]="isReadonly() || isValidationLoading()" data-automation-id="adf-cloud-people-chip-list">
|
||||
<mat-chip
|
||||
*ngFor="let user of selectedUsers"
|
||||
[removable]="!(user.readonly)"
|
||||
[attr.data-automation-id]="'adf-people-cloud-chip-' + user.username"
|
||||
(removed)="onRemove(user)"
|
||||
matTooltip="{{ (user.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}">
|
||||
{{user | fullName}}
|
||||
<mat-icon
|
||||
matChipRemove
|
||||
*ngIf="!(user.readonly || readOnly)"
|
||||
[attr.data-automation-id]="'adf-people-cloud-chip-remove-icon-' + user.username">
|
||||
cancel
|
||||
</mat-icon>
|
||||
</mat-chip>
|
||||
<input matInput
|
||||
[formControl]="searchUserCtrl"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="userMultipleChipList"
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
class="adf-cloud-input"
|
||||
data-automation-id="adf-people-cloud-search-input" #userInput>
|
||||
</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">
|
||||
#auto="matAutocomplete"
|
||||
(optionSelected)="onSelect($event.option.value)"
|
||||
[displayWith]="getDisplayName">
|
||||
<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>
|
||||
@@ -50,10 +38,28 @@
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
<div class="adf-start-task-cloud-error">
|
||||
<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>
|
||||
</div>
|
||||
<mat-progress-bar
|
||||
*ngIf="isLoading"
|
||||
mode="indeterminate">
|
||||
</mat-progress-bar>
|
||||
|
||||
<mat-error *ngIf="hasPreselectError() && !isValidationLoading()">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName : validateUsersMessage } }}</mat-error>
|
||||
<mat-error *ngIf="searchUserCtrl.hasError('pattern')">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_PATTERN' | translate: { pattern: getValidationPattern() } }}</mat-error>
|
||||
<mat-error *ngIf="searchUserCtrl.hasError('maxlength')">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MAX_LENGTH' | translate: { requiredLength: getValidationMaxLength() } }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="searchUserCtrl.hasError('minlength')">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.INVALID_MIN_LENGTH' | translate: { requiredLength: getValidationMinLength() } }}</mat-error>
|
||||
<mat-error *ngIf="searchUserCtrl.hasError('required')">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_PEOPLE_GROUPS.ERROR.REQUIRED' | translate }} </mat-error>
|
||||
<mat-error *ngIf="searchUserCtrl.hasError('searchTypingError') && !this.isFocused" data-automation-id="invalid-users-typing-error">
|
||||
<mat-icon class="adf-start-task-cloud-error-icon">warning</mat-icon>
|
||||
{{ 'ADF_CLOUD_USERS.ERROR.NOT_FOUND' | translate : { userName : searchedValue } }}</mat-error>
|
||||
</form>
|
||||
|
@@ -17,7 +17,12 @@
|
||||
|
||||
import { PeopleCloudComponent } from './people-cloud.component';
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
import { IdentityUserService, AlfrescoApiService, CoreModule, setupTestBed } from '@alfresco/adf-core';
|
||||
import {
|
||||
IdentityUserService,
|
||||
AlfrescoApiService,
|
||||
CoreModule,
|
||||
setupTestBed
|
||||
} from '@alfresco/adf-core';
|
||||
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
|
||||
import { of } from 'rxjs';
|
||||
import { mockUsers } from '../mock/user-cloud.mock';
|
||||
@@ -32,7 +37,6 @@ describe('PeopleCloudComponent', () => {
|
||||
let identityService: IdentityUserService;
|
||||
let alfrescoApiService: AlfrescoApiService;
|
||||
let findUsersByNameSpy: jasmine.Spy;
|
||||
let findUserByUsernameSpy: jasmine.Spy;
|
||||
|
||||
const mock = {
|
||||
oauth2Auth: {
|
||||
@@ -62,7 +66,6 @@ describe('PeopleCloudComponent', () => {
|
||||
identityService = TestBed.get(IdentityUserService);
|
||||
alfrescoApiService = TestBed.get(AlfrescoApiService);
|
||||
spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock);
|
||||
findUserByUsernameSpy = spyOn(identityService, 'findUserByUsername').and.returnValue(Promise.resolve([]));
|
||||
});
|
||||
|
||||
it('should create PeopleCloudComponent', () => {
|
||||
@@ -73,7 +76,7 @@ describe('PeopleCloudComponent', () => {
|
||||
component.title = 'TITLE_KEY';
|
||||
fixture.detectChanges();
|
||||
const matLabel: HTMLInputElement = <HTMLInputElement> fixture.nativeElement.querySelector('#adf-people-cloud-title-id');
|
||||
fixture.whenStable().then( () => {
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(matLabel.textContent).toEqual('TITLE_KEY');
|
||||
});
|
||||
@@ -83,7 +86,7 @@ describe('PeopleCloudComponent', () => {
|
||||
fixture.detectChanges();
|
||||
const matLabel: HTMLInputElement = <HTMLInputElement> fixture.nativeElement.querySelector('#adf-people-cloud-title-id');
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then( () => {
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(matLabel.textContent).toEqual('');
|
||||
});
|
||||
@@ -97,14 +100,14 @@ describe('PeopleCloudComponent', () => {
|
||||
findUsersByNameSpy = spyOn(identityService, 'findUsersByName').and.returnValue(of(mockUsers));
|
||||
}));
|
||||
|
||||
it('should list the users if the typed result match', (done) => {
|
||||
findUsersByNameSpy.and.returnValue(of(mockUsers));
|
||||
it('should list the users as dropdown options if the search term has results', (done) => {
|
||||
const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||
inputHTMLElement.focus();
|
||||
inputHTMLElement.value = 'first';
|
||||
inputHTMLElement.dispatchEvent(new Event('keyup'));
|
||||
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(fixture.debugElement.queryAll(By.css('mat-option')).length).toEqual(3);
|
||||
@@ -127,13 +130,19 @@ describe('PeopleCloudComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit selectedUser if option is valid', (done) => {
|
||||
it('should selectedUser and changedUsers emit, update selected users when a user is selected', (done) => {
|
||||
const user = { username: 'username' };
|
||||
fixture.detectChanges();
|
||||
spyOn(component, 'hasUserDetails').and.returnValue(true);
|
||||
const selectEmitSpy = spyOn(component.selectUser, 'emit');
|
||||
component.onSelect({ username: 'username' });
|
||||
const changedUsersSpy = spyOn(component.changedUsers, 'emit');
|
||||
component.onSelect(user);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
expect(selectEmitSpy).toHaveBeenCalled();
|
||||
expect(selectEmitSpy).toHaveBeenCalledWith(user);
|
||||
expect(changedUsersSpy).toHaveBeenCalledWith([user]);
|
||||
expect(component.getSelectedUsers()).toEqual([user]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -150,9 +159,9 @@ describe('PeopleCloudComponent', () => {
|
||||
fixture.whenStable().then(() => {
|
||||
inputHTMLElement.blur();
|
||||
fixture.detectChanges();
|
||||
const errorMessage = element.querySelector('.adf-start-task-cloud-error-message');
|
||||
const errorMessage = element.querySelector('[data-automation-id="invalid-users-typing-error"]');
|
||||
expect(errorMessage).not.toBeNull();
|
||||
expect(errorMessage.textContent).toContain('ADF_CLOUD_START_TASK.ERROR.MESSAGE');
|
||||
expect(errorMessage.textContent).toContain('ADF_CLOUD_USERS.ERROR.NOT_FOUND');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -173,6 +182,18 @@ describe('PeopleCloudComponent', () => {
|
||||
element = fixture.nativeElement;
|
||||
}));
|
||||
|
||||
it('should fetch the client ID if appName specified', async (() => {
|
||||
const getClientIdByApplicationNameSpy = spyOn(identityService, 'getClientIdByApplicationName').and.callThrough();
|
||||
component.appName = 'mock-app-name';
|
||||
const change = new SimpleChange(null, 'mock-app-name', false);
|
||||
component.ngOnChanges({ 'appName': change });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(getClientIdByApplicationNameSpy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should list users who have access to the app when appName is specified', (done) => {
|
||||
const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||
inputHTMLElement.focus();
|
||||
@@ -293,14 +314,34 @@ describe('PeopleCloudComponent', () => {
|
||||
fixture.whenStable().then(() => {
|
||||
inputHTMLElement.blur();
|
||||
fixture.detectChanges();
|
||||
const errorMessage = element.querySelector('.adf-start-task-cloud-error-message');
|
||||
const errorMessage = element.querySelector('[data-automation-id="invalid-users-typing-error"]');
|
||||
expect(errorMessage).not.toBeNull();
|
||||
expect(errorMessage.textContent).toContain('ADF_CLOUD_START_TASK.ERROR.MESSAGE');
|
||||
expect(errorMessage.textContent).toContain('ADF_CLOUD_USERS.ERROR.NOT_FOUND');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('No preselected users', () => {
|
||||
beforeEach(async () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should not pre-select any user when preSelectUsers is empty - single mode', () => {
|
||||
component.mode = 'single';
|
||||
fixture.detectChanges();
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
expect(chips.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should not pre-select any users when preSelectUsers is empty - multiple mode', () => {
|
||||
component.mode = 'multiple';
|
||||
fixture.detectChanges();
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
expect(chips.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When roles defined', () => {
|
||||
|
||||
let checkUserHasRoleSpy: jasmine.Spy;
|
||||
@@ -360,311 +401,170 @@ describe('PeopleCloudComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Single Mode and Pre-selected users with no validate flag', () => {
|
||||
describe('Single Mode with Pre-selected users', () => {
|
||||
|
||||
const changes = new SimpleChange(null, mockPreselectedUsers, false);
|
||||
|
||||
beforeEach(async(() => {
|
||||
component.mode = 'single';
|
||||
component.preSelectUsers = <any> mockPreselectedUsers;
|
||||
component.ngOnChanges({ 'preSelectUsers': changes });
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
}));
|
||||
|
||||
it('should not show chip list when mode=single', (done) => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const chip = element.querySelector('mat-chip-list');
|
||||
expect(chip).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not pre-select any user when preSelectUsers is empty and mode=single', (done) => {
|
||||
component.preSelectUsers = [];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const selectedUser = component.searchUserCtrl.value;
|
||||
expect(selectedUser).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Single Mode and Pre-selected users with validate flag', () => {
|
||||
|
||||
beforeEach(async(() => {
|
||||
component.mode = 'single';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> mockPreselectedUsers;
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
}));
|
||||
|
||||
it('should not show chip list when mode=single', (done) => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const chip = element.querySelector('mat-chip-list');
|
||||
expect(chip).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Mode and Pre-selected users with no validate flag', () => {
|
||||
|
||||
const change = new SimpleChange(null, mockPreselectedUsers, false);
|
||||
|
||||
beforeEach(async(() => {
|
||||
component.mode = 'multiple';
|
||||
component.preSelectUsers = <any> mockPreselectedUsers;
|
||||
fixture.detectChanges();
|
||||
element = fixture.nativeElement;
|
||||
alfrescoApiService = TestBed.get(AlfrescoApiService);
|
||||
}));
|
||||
|
||||
it('should show chip list when mode=multiple', (done) => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const chip = element.querySelector('mat-chip-list');
|
||||
expect(chip).toBeDefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pre-select all preSelectUsers when mode=multiple validation disabled', (done) => {
|
||||
component.mode = 'multiple';
|
||||
spyOn(component, 'filterPreselectUsers').and.returnValue(Promise.resolve(mockPreselectedUsers));
|
||||
component.ngOnChanges({ 'preSelectUsers': change });
|
||||
fixture.detectChanges();
|
||||
it('should show only one mat chip with the first preSelectedUser', (done) => {
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
component.selectedUsers$.subscribe((selectedUsers) => {
|
||||
expect(selectedUsers).toBeDefined();
|
||||
expect(selectedUsers.length).toEqual(2);
|
||||
expect(selectedUsers[0].id).toEqual('fake-id-2');
|
||||
done();
|
||||
});
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
expect(chips.length).toEqual(1);
|
||||
expect(chips[0].attributes['data-automation-id']).toEqual(`adf-people-cloud-chip-${mockPreselectedUsers[0].username}`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Mode with read-only mode', () => {
|
||||
describe('Multiple Mode with Pre-selected Users', () => {
|
||||
|
||||
it('Should not show remove icon for pre-selected users if readonly property set to true', (done) => {
|
||||
component.mode = 'multiple';
|
||||
const removeUserSpy = spyOn(component.removeUser, 'emit');
|
||||
fixture.detectChanges();
|
||||
component.preSelectUsers = [
|
||||
{ id: mockUsers[0].id, username: mockUsers[0].username, readonly: true },
|
||||
{ id: mockUsers[1].id, username: mockUsers[1].username, readonly: true }
|
||||
];
|
||||
fixture.detectChanges();
|
||||
const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip');
|
||||
const removeIcon = <HTMLElement> fixture.nativeElement.querySelector('[data-automation-id="adf-people-cloud-chip-remove-icon-first-name-1 last-name-1"]');
|
||||
expect(chipList.length).toBe(2);
|
||||
expect(component.preSelectUsers[0].readonly).toBeTruthy();
|
||||
expect(component.preSelectUsers[1].readonly).toBeTruthy();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(removeIcon).toBeNull();
|
||||
fixture.detectChanges();
|
||||
expect(removeUserSpy).not.toHaveBeenCalled();
|
||||
expect(component.preSelectUsers.length).toBe(2);
|
||||
expect(component.preSelectUsers[0].readonly).toBe(true, 'Not removable');
|
||||
expect(component.preSelectUsers[1].readonly).toBe(true, 'not removable');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple Mode and Pre-selected users with validate flag', () => {
|
||||
|
||||
const change = new SimpleChange(null, mockPreselectedUsers, false);
|
||||
|
||||
beforeEach(async(() => {
|
||||
const change = new SimpleChange(null, component.preSelectUsers, false);
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> mockPreselectedUsers;
|
||||
element = fixture.nativeElement;
|
||||
alfrescoApiService = TestBed.get(AlfrescoApiService);
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should show chip list when mode=multiple', (done) => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
const chip = element.querySelector('mat-chip-list');
|
||||
expect(chip).toBeDefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should pre-select all preSelectUsers when mode=multiple', (done) => {
|
||||
spyOn(component, 'searchUser').and.returnValue(Promise.resolve(mockPreselectedUsers));
|
||||
component.mode = 'multiple';
|
||||
fixture.detectChanges();
|
||||
component.ngOnChanges({ 'preSelectUsers': change });
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip');
|
||||
const removeIcon = <HTMLElement> fixture.nativeElement.querySelector('[data-automation-id="adf-people-cloud-chip-remove-icon-first-name-1 last-name-1"]');
|
||||
expect(chipList.length).toBe(2);
|
||||
expect(component.preSelectUsers[0].readonly).toBeTruthy();
|
||||
expect(component.preSelectUsers[1].readonly).toBeTruthy();
|
||||
expect(removeIcon).toBeNull();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Should be able to remove preselected users if readonly property set to false', (done) => {
|
||||
fixture.detectChanges();
|
||||
component.preSelectUsers = [
|
||||
{ id: mockUsers[0].id, username: mockUsers[0].username, readonly: false },
|
||||
{ id: mockUsers[1].id, username: mockUsers[1].username, readonly: false }
|
||||
];
|
||||
const change = new SimpleChange(null, component.preSelectUsers, false);
|
||||
component.mode = 'multiple';
|
||||
component.ngOnChanges({ 'preSelectUsers': change });
|
||||
const removeUserSpy = spyOn(component.removeUser, 'emit');
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
const removeIcon = <HTMLElement> fixture.nativeElement.querySelector(`[data-automation-id="adf-people-cloud-chip-remove-icon-${mockPreselectedUsers[0].username}"]`);
|
||||
expect(chips.length).toBe(2);
|
||||
expect(component.preSelectUsers[0].readonly).toBe(false, 'Removable');
|
||||
expect(component.preSelectUsers[1].readonly).toBe(false, 'Removable');
|
||||
removeIcon.click();
|
||||
fixture.detectChanges();
|
||||
expect(removeUserSpy).toHaveBeenCalled();
|
||||
expect(fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip').length).toBe(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit removeUser when a selected user is removed if mode=multiple', (done) => {
|
||||
spyOn(component.removeUser, 'emit');
|
||||
component.mode = 'multiple';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
describe('Component readonly mode', () => {
|
||||
const change = new SimpleChange(null, mockPreselectedUsers, false);
|
||||
|
||||
it('should chip list be disabled and show one single chip - single mode', () => {
|
||||
component.mode = 'single';
|
||||
component.readOnly = true;
|
||||
component.preSelectUsers = <any> mockPreselectedUsers;
|
||||
component.ngOnChanges({ 'preSelectUsers': change });
|
||||
|
||||
fixture.detectChanges();
|
||||
const removeIcon = fixture.debugElement.query(By.css('mat-chip mat-icon'));
|
||||
removeIcon.nativeElement.click();
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
const chipList = fixture.nativeElement.querySelector('mat-chip-list');
|
||||
expect(chips).toBeDefined();
|
||||
expect(chipList).toBeDefined();
|
||||
expect(chips.length).toBe(1);
|
||||
expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
|
||||
});
|
||||
|
||||
it('should chip list be disabled and show mat chips for all the preselected users - multiple mode', () => {
|
||||
component.mode = 'multiple';
|
||||
component.readOnly = true;
|
||||
component.preSelectUsers = <any> mockPreselectedUsers;
|
||||
component.ngOnChanges({ 'preSelectUsers': change });
|
||||
fixture.detectChanges();
|
||||
expect(component.removeUser.emit).toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should emit warning if are invalid users', (done) => {
|
||||
findUserByUsernameSpy.and.returnValue(Promise.resolve([]));
|
||||
const warnMessage = { message: 'INVALID_PRESELECTED_USERS', users: [{ username: 'invalidUsername' }] };
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> [{ username: 'invalidUsername' }];
|
||||
fixture.detectChanges();
|
||||
component.loadSinglePreselectUser();
|
||||
component.warning.subscribe((response) => {
|
||||
expect(response).toEqual(warnMessage);
|
||||
expect(response.message).toEqual(warnMessage.message);
|
||||
expect(response.users).toEqual(warnMessage.users);
|
||||
expect(response.users[0].username).toEqual('invalidUsername');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter user by id if validate true', async(() => {
|
||||
const findByIdSpy = spyOn(identityService, 'findUserById').and.returnValue(of(mockUsers[0]));
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
fixture.detectChanges();
|
||||
component.preSelectUsers = <any> [{ id: mockUsers[0].id }, { id: mockUsers[1].id }];
|
||||
component.ngOnChanges({ 'preSelectUsers': change });
|
||||
fixture.detectChanges();
|
||||
component.filterPreselectUsers().then((result: any) => {
|
||||
fixture.detectChanges();
|
||||
expect(findByIdSpy).toHaveBeenCalled();
|
||||
expect(component.userExists(result[0])).toEqual(true);
|
||||
expect(result[1].id).toBe(mockUsers[0].id);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should filter user by username if validate true', (done) => {
|
||||
findUserByUsernameSpy.and.returnValue(of(mockUsers));
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> [{ username: mockUsers[1].username }, { username: mockUsers[2].username }];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.filterPreselectUsers().then((result) => {
|
||||
expect(findUserByUsernameSpy).toHaveBeenCalled();
|
||||
expect(component.userExists(result[0])).toEqual(true);
|
||||
expect(component.userExists(result[1])).toEqual(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should filter user by email if validate true', (done) => {
|
||||
const findUserByEmailSpy = spyOn(identityService, 'findUserByEmail').and.returnValue(of(mockUsers));
|
||||
fixture.detectChanges();
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> [{ email: mockUsers[1].email }, { email: mockUsers[2].email }];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.filterPreselectUsers().then((result) => {
|
||||
expect(findUserByEmailSpy).toHaveBeenCalled();
|
||||
expect(component.userExists(result[0])).toEqual(true);
|
||||
expect(component.userExists(result[1])).toEqual(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should search user by id on single selection mode', (done) => {
|
||||
const findUserByIdSpy = spyOn(identityService, 'findUserById').and.returnValue(of(mockUsers[0]));
|
||||
component.mode = 'single';
|
||||
component.validate = true;
|
||||
fixture.detectChanges();
|
||||
component.preSelectUsers = <any> [{ id: mockUsers[0].id }];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.validatePreselectUsers().then((result) => {
|
||||
expect(findUserByIdSpy).toHaveBeenCalled();
|
||||
expect(result.length).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not preselect any user if email is invalid and validation enable', (done) => {
|
||||
const findUserByEmailSpy = spyOn(identityService, 'findUserByEmail').and.returnValue(of([]));
|
||||
component.mode = 'single';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> [{ email: 'invalid email' }];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.validatePreselectUsers().then((result) => {
|
||||
expect(findUserByEmailSpy).toHaveBeenCalled();
|
||||
expect(result.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not preselect any user if id is invalid and validation enable', (done) => {
|
||||
const findUserByIdSpy = spyOn(identityService, 'findUserById').and.returnValue(of([]));
|
||||
component.mode = 'single';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> [{ id: 'invalid id' }];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.validatePreselectUsers().then((result) => {
|
||||
expect(findUserByIdSpy).toHaveBeenCalled();
|
||||
expect(result.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not preselect any user if username is invalid and validation enable', (done) => {
|
||||
findUserByUsernameSpy.and.returnValue(of([]));
|
||||
component.mode = 'single';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> [{ username: 'invalid user' }];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.validatePreselectUsers().then((result) => {
|
||||
fixture.detectChanges();
|
||||
expect(findUserByUsernameSpy).toHaveBeenCalled();
|
||||
expect(result.length).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should remove duplicated preselcted users when a user is duplicated', () => {
|
||||
spyOn(identityService, 'findUserById').and.returnValue(of(mockUsers[0]));
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> [{ id: mockUsers[0].id }, { id: mockUsers[0].id }];
|
||||
component.ngOnChanges({ 'preSelectUsers': change });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.validatePreselectUsers().then((result) => {
|
||||
expect(result.length).toEqual(1);
|
||||
});
|
||||
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
|
||||
const chipList = fixture.nativeElement.querySelector('mat-chip-list');
|
||||
expect(chips).toBeDefined();
|
||||
expect(chipList).toBeDefined();
|
||||
expect(chips.length).toBe(2);
|
||||
expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Preselected users and validation enabled', () => {
|
||||
|
||||
it('should check validation only for the first user and emit warning when user is invalid - single mode', (done) => {
|
||||
spyOn(identityService, 'findUserById').and.returnValue(Promise.resolve([]));
|
||||
spyOn(component, 'hasUserDetails').and.returnValue(false);
|
||||
|
||||
const expectedWarning = {
|
||||
message: 'INVALID_PRESELECTED_USERS',
|
||||
users: [{
|
||||
id: mockPreselectedUsers[0].id,
|
||||
username: mockPreselectedUsers[0].username
|
||||
}]
|
||||
};
|
||||
component.warning.subscribe(warning => {
|
||||
expect(warning).toEqual(expectedWarning);
|
||||
done();
|
||||
});
|
||||
|
||||
component.mode = 'single';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> [mockPreselectedUsers[0], mockPreselectedUsers[1]];
|
||||
component.ngOnChanges({ 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) });
|
||||
});
|
||||
|
||||
it('should check validation for all the users and emit warning - multiple mode', (done) => {
|
||||
spyOn(identityService, 'findUserById').and.returnValue(Promise.resolve(undefined));
|
||||
|
||||
const expectedWarning = {
|
||||
message: 'INVALID_PRESELECTED_USERS',
|
||||
users: [
|
||||
{
|
||||
id: mockPreselectedUsers[0].id,
|
||||
username: mockPreselectedUsers[0].username
|
||||
},
|
||||
{
|
||||
id: mockPreselectedUsers[1].id,
|
||||
username: mockPreselectedUsers[1].username
|
||||
}]
|
||||
};
|
||||
|
||||
component.warning.subscribe(warning => {
|
||||
expect(warning).toEqual(expectedWarning);
|
||||
done();
|
||||
});
|
||||
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
component.preSelectUsers = <any> [mockPreselectedUsers[0], mockPreselectedUsers[1]];
|
||||
component.ngOnChanges({ 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) });
|
||||
});
|
||||
});
|
||||
|
||||
it('should removeDuplicateUsers return only unique users', () => {
|
||||
const duplicatedUsers = [{ id: mockUsers[0].id }, { id: mockUsers[0].id }];
|
||||
expect(component.removeDuplicatedUsers(duplicatedUsers)).toEqual([{ id: mockUsers[0].id }]);
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -16,10 +16,27 @@
|
||||
*/
|
||||
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Component, OnInit, Output, EventEmitter, ViewEncapsulation, Input, ViewChild, ElementRef, SimpleChanges, OnChanges, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewEncapsulation,
|
||||
Input,
|
||||
SimpleChanges,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
ChangeDetectionStrategy,
|
||||
ViewChild, ElementRef
|
||||
} from '@angular/core';
|
||||
import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
|
||||
import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, map, takeUntil } from 'rxjs/operators';
|
||||
import { FullNamePipe, IdentityUserModel, IdentityUserService, LogService } from '@alfresco/adf-core';
|
||||
import {
|
||||
FullNamePipe,
|
||||
IdentityUserModel,
|
||||
IdentityUserService,
|
||||
LogService
|
||||
} from '@alfresco/adf-core';
|
||||
import { trigger, state, style, transition, animate } from '@angular/animations';
|
||||
|
||||
@Component({
|
||||
@@ -79,7 +96,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
/** FormControl to search the user */
|
||||
@Input()
|
||||
searchUserCtrl: FormControl = new FormControl();
|
||||
searchUserCtrl: FormControl = new FormControl({ value: '', disabled: false });
|
||||
|
||||
/** Placeholder translation key
|
||||
*/
|
||||
@@ -106,166 +123,63 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
private userInput: ElementRef<HTMLInputElement>;
|
||||
|
||||
private _searchUsers: IdentityUserModel[] = [];
|
||||
private selectedUsersSubject: BehaviorSubject<IdentityUserModel[]>;
|
||||
private searchUsersSubject: BehaviorSubject<IdentityUserModel[]>;
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
|
||||
selectedUsers: IdentityUserModel[] = [];
|
||||
selectedUsers$: Observable<IdentityUserModel[]>;
|
||||
invalidUsers: IdentityUserModel[] = [];
|
||||
|
||||
searchUsers$: Observable<IdentityUserModel[]>;
|
||||
_subscriptAnimationState: string = 'enter';
|
||||
clientId: string;
|
||||
isFocused: boolean;
|
||||
invalidUsers: IdentityUserModel[] = [];
|
||||
|
||||
currentTimeout: any;
|
||||
validateUsersMessage: string;
|
||||
searchedValue = '';
|
||||
|
||||
constructor(private identityUserService: IdentityUserService, private logService: LogService) {
|
||||
}
|
||||
isLoading = false;
|
||||
|
||||
constructor(
|
||||
private identityUserService: IdentityUserService,
|
||||
private logService: LogService) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.hasPreSelectUsers()) {
|
||||
this.selectedUsers = [...this.preSelectUsers];
|
||||
}
|
||||
this.initSubjects();
|
||||
this.initSearch();
|
||||
|
||||
if (this.appName) {
|
||||
this.disableSearch();
|
||||
this.loadClientId();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this.initSubjects();
|
||||
|
||||
if (this.isPreselectedUserChanged(changes)) {
|
||||
if (this.isValidationEnabled()) {
|
||||
this.loadPreSelectUsers();
|
||||
} else {
|
||||
this.loadNoValidationPreselectUsers();
|
||||
}
|
||||
}
|
||||
|
||||
if (changes.appName && this.isAppNameChanged(changes.appName)) {
|
||||
this.disableSearch();
|
||||
this.loadClientId();
|
||||
} else {
|
||||
this.enableSearch();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
clearTimeout(this.currentTimeout);
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
initSubjects() {
|
||||
if (this.selectedUsersSubject === undefined) {
|
||||
this.selectedUsersSubject = new BehaviorSubject<IdentityUserModel[]>(this.preSelectUsers);
|
||||
this.selectedUsers$ = this.selectedUsersSubject.asObservable();
|
||||
}
|
||||
|
||||
if (this.searchUsersSubject === undefined) {
|
||||
this.searchUsersSubject = new BehaviorSubject<IdentityUserModel[]>(this._searchUsers);
|
||||
this.searchUsers$ = this.searchUsersSubject.asObservable();
|
||||
}
|
||||
|
||||
this.loadClientId();
|
||||
this.initSearch();
|
||||
}
|
||||
|
||||
private isAppNameChanged(change) {
|
||||
return change.previousValue !== change.currentValue && this.appName && this.appName.length > 0;
|
||||
}
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
|
||||
isPreselectedUserChanged(changes: SimpleChanges) {
|
||||
return changes.preSelectUsers
|
||||
&& changes.preSelectUsers.previousValue !== changes.preSelectUsers.currentValue
|
||||
&& this.hasPreSelectUsers();
|
||||
}
|
||||
|
||||
isValidationEnabled() {
|
||||
return this.validate === true;
|
||||
}
|
||||
|
||||
async validatePreselectUsers(): Promise<any> {
|
||||
let filteredPreselectUsers: IdentityUserModel[];
|
||||
let validUsers: IdentityUserModel[] = [];
|
||||
|
||||
try {
|
||||
filteredPreselectUsers = await this.filterPreselectUsers();
|
||||
} catch (error) {
|
||||
validUsers = [];
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
await this.preSelectUsers.map((user: IdentityUserModel) => {
|
||||
const validUser = this.isValidUser(filteredPreselectUsers, user);
|
||||
|
||||
if (validUser) {
|
||||
validUsers.push(validUser);
|
||||
} else {
|
||||
this.invalidUsers.push(user);
|
||||
if (this.hasPreselectedUsersChanged(changes) || this.hasModeChanged(changes) || this.isValidationChanged(changes)) {
|
||||
if (this.hasPreSelectUsers()) {
|
||||
this.loadPreSelectUsers();
|
||||
} else if (this.hasPreselectedUsersCleared(changes)) {
|
||||
this.selectedUsers = [];
|
||||
this.invalidUsers = [];
|
||||
}
|
||||
});
|
||||
validUsers = this.removeDuplicatedUsers(validUsers);
|
||||
return validUsers;
|
||||
}
|
||||
|
||||
private removeDuplicatedUsers(users: IdentityUserModel[]): IdentityUserModel[] {
|
||||
return users.filter((user, index, self) =>
|
||||
index === self.findIndex((auxUser) => {
|
||||
return user.id === auxUser.id && user.username === auxUser.username;
|
||||
}));
|
||||
}
|
||||
|
||||
async filterPreselectUsers() {
|
||||
const promiseBatch = this.preSelectUsers.map(async (user: IdentityUserModel) => {
|
||||
let result: any;
|
||||
try {
|
||||
result = await this.searchUser(user);
|
||||
} catch (error) {
|
||||
result = [];
|
||||
this.logService.error(error);
|
||||
if (!this.isValidationEnabled()) {
|
||||
this.invalidUsers = [];
|
||||
}
|
||||
const isUserValid: boolean = this.userExists(result);
|
||||
return isUserValid ? result : null;
|
||||
});
|
||||
return Promise.all(promiseBatch);
|
||||
}
|
||||
|
||||
async searchUser(user: IdentityUserModel) {
|
||||
let key: string = '';
|
||||
|
||||
if (user.id) {
|
||||
key = 'id';
|
||||
} else if (user.email) {
|
||||
key = 'email';
|
||||
} else if (user.username) {
|
||||
key = 'username';
|
||||
}
|
||||
|
||||
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 of([]);
|
||||
if (changes.appName && this.isAppNameChanged(changes.appName)) {
|
||||
this.loadClientId();
|
||||
this.initSearch();
|
||||
}
|
||||
}
|
||||
|
||||
private isValidUser(filteredUsers: IdentityUserModel[], user: IdentityUserModel) {
|
||||
return filteredUsers.find((filteredUser: IdentityUserModel) => {
|
||||
return filteredUser &&
|
||||
(filteredUser.id === user.id ||
|
||||
filteredUser.username === user.username ||
|
||||
filteredUser.email === user.email);
|
||||
});
|
||||
}
|
||||
|
||||
public userExists(result: IdentityUserModel): boolean {
|
||||
return result
|
||||
&& (result.id !== undefined
|
||||
|| result.username !== undefined
|
||||
|| result.email !== undefined);
|
||||
private async loadClientId() {
|
||||
this.clientId = await this.identityUserService.getClientIdByApplicationName(this.appName).toPromise();
|
||||
if (this.clientId) {
|
||||
this.searchUserCtrl.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private initSearch() {
|
||||
@@ -276,12 +190,9 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return typeof value === 'string';
|
||||
}),
|
||||
tap((value) => {
|
||||
this.searchedValue = value;
|
||||
if (value) {
|
||||
this.setError();
|
||||
} else {
|
||||
if (!this.isMultipleMode()) {
|
||||
this.removeUser.emit();
|
||||
}
|
||||
this.setTypingError();
|
||||
}
|
||||
}),
|
||||
tap(() => {
|
||||
@@ -296,7 +207,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}),
|
||||
mergeMap((user: any) => {
|
||||
if (this.appName) {
|
||||
|
||||
return this.checkUserHasAccess(user.id).pipe(
|
||||
mergeMap((hasRole) => {
|
||||
return hasRole ? of(user) : of();
|
||||
@@ -315,6 +225,20 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
clearTimeout(this.currentTimeout);
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
private isAppNameChanged(change): boolean {
|
||||
return change && change.previousValue !== change.currentValue && this.appName && this.appName.length > 0;
|
||||
}
|
||||
|
||||
isValidationEnabled(): boolean {
|
||||
return this.validate === true;
|
||||
}
|
||||
|
||||
private checkUserHasAccess(userId: string): Observable<boolean> {
|
||||
if (this.hasRoles()) {
|
||||
return this.identityUserService.checkUserHasAnyClientAppRole(userId, this.clientId, this.roles);
|
||||
@@ -345,79 +269,105 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return false;
|
||||
}
|
||||
|
||||
private loadPreSelectUsers() {
|
||||
if (!this.isMultipleMode()) {
|
||||
this.loadSinglePreselectUser();
|
||||
private async loadPreSelectUsers() {
|
||||
this.selectedUsers = [];
|
||||
|
||||
if (this.isSingleMode()) {
|
||||
this.selectedUsers = [this.preSelectUsers[0]];
|
||||
} else {
|
||||
this.loadMultiplePreselectUsers();
|
||||
this.selectedUsers = this.removeDuplicatedUsers(this.preSelectUsers);
|
||||
}
|
||||
|
||||
if (this.isValidationEnabled()) {
|
||||
this.isLoading = true;
|
||||
await this.validatePreselectUsers();
|
||||
}
|
||||
}
|
||||
|
||||
async loadNoValidationPreselectUsers() {
|
||||
this.selectedUsers = [...this.removeDuplicatedUsers(this.preSelectUsers)];
|
||||
async validatePreselectUsers(): Promise<any> {
|
||||
this.invalidUsers = [];
|
||||
const validUsers: IdentityUserModel[] = [];
|
||||
|
||||
if (this.isMultipleMode()) {
|
||||
this.selectedUsersSubject.next(this.selectedUsers);
|
||||
let preselectedUsersToValidate: IdentityUserModel[] = [];
|
||||
|
||||
if (this.isSingleMode()) {
|
||||
preselectedUsersToValidate = [this.preSelectUsers[0]];
|
||||
} else {
|
||||
|
||||
if (this.currentTimeout) {
|
||||
clearTimeout(this.currentTimeout);
|
||||
}
|
||||
|
||||
this.currentTimeout = setTimeout(() => {
|
||||
this.searchUserCtrl.setValue(this.selectedUsers[0]);
|
||||
this.onSelect(this.selectedUsers[0]);
|
||||
}, 0);
|
||||
preselectedUsersToValidate = this.removeDuplicatedUsers(this.preSelectUsers);
|
||||
}
|
||||
}
|
||||
|
||||
public async loadSinglePreselectUser() {
|
||||
const users = await this.validatePreselectUsers();
|
||||
if (users && users.length > 0) {
|
||||
this.checkPreselectValidationErrors();
|
||||
this.searchUserCtrl.setValue(users[0]);
|
||||
} else {
|
||||
this.checkPreselectValidationErrors();
|
||||
}
|
||||
}
|
||||
|
||||
public async loadMultiplePreselectUsers() {
|
||||
const users = await this.validatePreselectUsers();
|
||||
if (users && users.length > 0) {
|
||||
this.checkPreselectValidationErrors();
|
||||
this.selectedUsers = [...this.alignPreselectedReadonlyUsersAfterValidation(users)];
|
||||
this.selectedUsersSubject.next(this.selectedUsers);
|
||||
} else {
|
||||
this.checkPreselectValidationErrors();
|
||||
}
|
||||
}
|
||||
|
||||
private alignPreselectedReadonlyUsersAfterValidation(users: IdentityUserModel[]) {
|
||||
this.preSelectUsers.forEach((preSelectedUser, index) => {
|
||||
if (users[index]) {
|
||||
if ((preSelectedUser.id === users[index].id) || (preSelectedUser.username === users[index].username)) {
|
||||
users[index].readonly = preSelectedUser.readonly;
|
||||
await Promise.all(preselectedUsersToValidate.map(async (user: IdentityUserModel) => {
|
||||
try {
|
||||
const validationResult = await this.searchUser(user);
|
||||
if (!this.hasUserDetails(validationResult)) {
|
||||
this.invalidUsers.push(user);
|
||||
} else {
|
||||
validUsers.push(validationResult);
|
||||
}
|
||||
} catch (error) {
|
||||
this.invalidUsers.push(user);
|
||||
this.logService.error(error);
|
||||
}
|
||||
}));
|
||||
this.checkPreselectValidationErrors();
|
||||
this.alignUsersAfterValidation(validUsers);
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
private alignUsersAfterValidation(validatedUsers: IdentityUserModel[]) {
|
||||
this.selectedUsers.forEach((selectedUser, index) => {
|
||||
validatedUsers.forEach(validatedUser => {
|
||||
if ((selectedUser.id === validatedUser.id) || (selectedUser.username === validatedUser.username)
|
||||
|| (selectedUser.email === validatedUser.email)) {
|
||||
validatedUser.readonly = selectedUser.readonly;
|
||||
this.selectedUsers[index] = validatedUser;
|
||||
}
|
||||
});
|
||||
});
|
||||
return users;
|
||||
}
|
||||
|
||||
async searchUser(user: IdentityUserModel) {
|
||||
let key: string = '';
|
||||
|
||||
if (user.id) {
|
||||
key = 'id';
|
||||
} else if (user.email) {
|
||||
key = 'email';
|
||||
} else if (user.username) {
|
||||
key = 'username';
|
||||
}
|
||||
|
||||
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 of([]);
|
||||
}
|
||||
}
|
||||
|
||||
removeDuplicatedUsers(users: IdentityUserModel[]): IdentityUserModel[] {
|
||||
return users.filter((user, index, self) =>
|
||||
index === self.findIndex((auxUser) => {
|
||||
return user.id === auxUser.id && user.username === auxUser.username && user.email === auxUser.email;
|
||||
}));
|
||||
}
|
||||
|
||||
public checkPreselectValidationErrors() {
|
||||
|
||||
this.invalidUsers = this.removeDuplicatedUsers(this.invalidUsers);
|
||||
|
||||
if (this.invalidUsers.length > 0) {
|
||||
this.warning.emit({
|
||||
message: 'INVALID_PRESELECTED_USERS',
|
||||
users: this.invalidUsers
|
||||
});
|
||||
this.generateInvalidUsersMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private async loadClientId() {
|
||||
this.clientId = await this.identityUserService.getClientIdByApplicationName(this.appName).toPromise();
|
||||
|
||||
if (this.clientId) {
|
||||
this.enableSearch();
|
||||
}
|
||||
this.warning.emit({
|
||||
message: 'INVALID_PRESELECTED_USERS',
|
||||
users: this.invalidUsers
|
||||
});
|
||||
}
|
||||
|
||||
onSelect(user: IdentityUserModel) {
|
||||
@@ -425,26 +375,66 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (this.isMultipleMode()) {
|
||||
if (!this.isUserAlreadySelected(user)) {
|
||||
this.selectedUsers.push(user);
|
||||
this.selectedUsersSubject.next(this.selectedUsers);
|
||||
}
|
||||
|
||||
this.userInput.nativeElement.value = '';
|
||||
this.searchUserCtrl.setValue('');
|
||||
} else {
|
||||
this.invalidUsers = [];
|
||||
this.selectedUsers = [user];
|
||||
}
|
||||
|
||||
this.userInput.nativeElement.value = '';
|
||||
this.searchUserCtrl.setValue('');
|
||||
|
||||
this.changedUsers.emit(this.selectedUsers);
|
||||
this.clearError();
|
||||
this.resetSearchUsers();
|
||||
}
|
||||
|
||||
onRemove(user: IdentityUserModel) {
|
||||
this.removeUser.emit(user);
|
||||
const indexToRemove = this.selectedUsers.findIndex((selectedUser) => { return selectedUser.id === user.id; });
|
||||
onRemove(userToRemove: IdentityUserModel) {
|
||||
this.removeUser.emit(userToRemove);
|
||||
const indexToRemove = this.selectedUsers.findIndex((selectedUser: IdentityUserModel) => {
|
||||
return selectedUser.id === userToRemove.id;
|
||||
});
|
||||
this.selectedUsers.splice(indexToRemove, 1);
|
||||
this.selectedUsersSubject.next(this.selectedUsers);
|
||||
this.changedUsers.emit(this.selectedUsers);
|
||||
this.searchUserCtrl.markAsDirty();
|
||||
|
||||
if (this.isValidationEnabled()) {
|
||||
this.removeUserFromValidation(userToRemove.username);
|
||||
this.checkPreselectValidationErrors();
|
||||
}
|
||||
}
|
||||
|
||||
private removeUserFromValidation(username: string) {
|
||||
const indexToRemove = this.invalidUsers.findIndex((invalidUser) => {
|
||||
return invalidUser.username === username;
|
||||
});
|
||||
|
||||
if (indexToRemove !== -1) {
|
||||
this.invalidUsers.splice(indexToRemove, 1);
|
||||
}
|
||||
}
|
||||
|
||||
hasUserDetails(user: IdentityUserModel): boolean {
|
||||
return user && (user.id !== undefined || user.username !== undefined || user.email !== undefined);
|
||||
}
|
||||
|
||||
generateInvalidUsersMessage() {
|
||||
this.validateUsersMessage = '';
|
||||
|
||||
this.invalidUsers.forEach((invalidUser: IdentityUserModel, index) => {
|
||||
if (index === this.invalidUsers.length - 1) {
|
||||
this.validateUsersMessage += `${invalidUser.username} `;
|
||||
} else {
|
||||
this.validateUsersMessage += `${invalidUser.username}, `;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setTypingError() {
|
||||
this.searchUserCtrl.setErrors({ searchTypingError: true, ...this.searchUserCtrl.errors });
|
||||
}
|
||||
|
||||
hasPreselectError(): boolean {
|
||||
return this.invalidUsers && this.invalidUsers.length > 0;
|
||||
}
|
||||
|
||||
getDisplayName(user): string {
|
||||
@@ -455,21 +445,49 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return this.mode === PeopleCloudComponent.MODE_MULTIPLE;
|
||||
}
|
||||
|
||||
isSingleMode(): boolean {
|
||||
return this.mode === PeopleCloudComponent.MODE_SINGLE;
|
||||
}
|
||||
|
||||
private isSingleSelectionReadonly(): boolean {
|
||||
return this.isSingleMode() && this.selectedUsers.length === 1 && this.selectedUsers[0].readonly === true;
|
||||
}
|
||||
|
||||
private hasPreSelectUsers(): boolean {
|
||||
return this.preSelectUsers && this.preSelectUsers.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 hasPreselectedUsersChanged(changes): boolean {
|
||||
return changes && changes.preSelectUsers && changes.preSelectUsers.currentValue !== changes.preSelectUsers.previousValue;
|
||||
}
|
||||
|
||||
private hasPreselectedUsersCleared(changes): boolean {
|
||||
return changes && changes.preSelectUsers && changes.preSelectUsers.currentValue.length === 0;
|
||||
}
|
||||
|
||||
private resetSearchUsers() {
|
||||
this._searchUsers = [];
|
||||
this.searchUsersSubject.next(this._searchUsers);
|
||||
}
|
||||
|
||||
private setError() {
|
||||
this.searchUserCtrl.setErrors({ invalid: true });
|
||||
getSelectedUsers(): IdentityUserModel[] {
|
||||
return this.selectedUsers;
|
||||
}
|
||||
|
||||
private clearError() {
|
||||
this.searchUserCtrl.setErrors(null);
|
||||
isReadonly(): boolean {
|
||||
return this.readOnly || this.isSingleSelectionReadonly();
|
||||
}
|
||||
|
||||
isValidationLoading(): boolean {
|
||||
return this.isValidationEnabled() && this.isLoading;
|
||||
}
|
||||
|
||||
setFocus(isFocused: boolean) {
|
||||
@@ -480,16 +498,15 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return !!this.searchUserCtrl.errors;
|
||||
}
|
||||
|
||||
hasErrorMessage(): boolean {
|
||||
return !this.isFocused && this.hasError();
|
||||
getValidationPattern(): string {
|
||||
return this.searchUserCtrl.errors.pattern.requiredPattern;
|
||||
}
|
||||
|
||||
private disableSearch() {
|
||||
this.searchUserCtrl.disable();
|
||||
getValidationMaxLength(): string {
|
||||
return this.searchUserCtrl.errors.maxlength.requiredLength;
|
||||
}
|
||||
|
||||
private enableSearch() {
|
||||
this.searchUserCtrl.enable();
|
||||
getValidationMinLength(): string {
|
||||
return this.searchUserCtrl.errors.minlength.requiredLength;
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user