mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-12 17:04:57 +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:
parent
91abe87ccc
commit
3c3aa7599a
@ -309,10 +309,11 @@
|
||||
"APP_FILTER_MODE": "Filter by application name",
|
||||
"ROLE_FILTER_MODE": "Filter by role",
|
||||
"PRESELECT_VALIDATION": "Preselect validation",
|
||||
"ALL_SELECTED_USERS": "All Selected Users",
|
||||
"ALL_SELECTED_GROUPS": "All Selected Groups",
|
||||
"ALL_PRESELECTED_USERS": "All Pre-selected Users",
|
||||
"ALL_PRESELECTED_GROUPS": "All Pre-selected Groups",
|
||||
"INVALID_USERS": "Invalid Users",
|
||||
"INVALID_GROUPS": "Invalid Groups"
|
||||
"INVALID_GROUPS": "Invalid Groups",
|
||||
"READONLY_MODE": "Readonly Mode"
|
||||
},
|
||||
"SETTINGS_CLOUD": {
|
||||
"MULTISELECTION": "Multiselection",
|
||||
|
@ -32,21 +32,23 @@
|
||||
</mat-form-field>
|
||||
<mat-checkbox class="app-preselect-value" (change)="onChangePeopleValidation($event)">{{
|
||||
'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }}</mat-checkbox>
|
||||
<mat-checkbox data-automation-id="app-people-readonly" value="{{ peopleReadonly }}" (change)="onChangePeopleReadonly($event)">{{
|
||||
'PEOPLE_GROUPS_CLOUD.READONLY_MODE' | translate }}</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<adf-cloud-people
|
||||
[preSelectUsers]="preSelectUsers"
|
||||
[readOnly]="peopleReadonly"
|
||||
[validate]="peoplePreselectValidation"
|
||||
[appName]="peopleAppName"
|
||||
[roles]="peopleRoles"
|
||||
[title]="'ADF_TASK_LIST.START_TASK.FORM.LABEL.ASSIGNEE'"
|
||||
[mode]="peopleMode"
|
||||
(selectUser)="onSelectUser($event)"
|
||||
(warning)="onUsersWarning($event)"></adf-cloud-people>
|
||||
</div>
|
||||
|
||||
<div class="app-people-list" *ngIf="canShowPeopleList()">
|
||||
<h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_SELECTED_USERS' | translate }}</h4>
|
||||
<h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_PRESELECTED_USERS' | translate }}</h4>
|
||||
<mat-list role="list">
|
||||
<mat-list-item *ngFor="let item of preSelectUsers" role="listitem">
|
||||
<mat-icon mat-list-icon>person</mat-icon>
|
||||
@ -99,23 +101,24 @@
|
||||
<mat-label>Preselect: {{ DEFAULT_GROUP_PLACEHOLDER }}</mat-label>
|
||||
<input matInput (input)="setGroupsPreselectValue($event)" data-automation-id="app-group-preselect-input" />
|
||||
</mat-form-field>
|
||||
<mat-checkbox class="app-group-preselect-validation" (change)="onChangeGroupValidation($event)">{{
|
||||
<mat-checkbox class="app-preselect-value" (change)="onChangeGroupValidation($event)">{{
|
||||
'PEOPLE_GROUPS_CLOUD.PRESELECT_VALIDATION' | translate }}</mat-checkbox>
|
||||
<mat-checkbox data-automation-id="app-group-readonly" value="{{ groupReadonly }}" (change)="onChangeGroupReadonly($event)">{{
|
||||
'PEOPLE_GROUPS_CLOUD.READONLY_MODE' | translate }}</mat-checkbox>
|
||||
</div>
|
||||
<div>
|
||||
<adf-cloud-group
|
||||
[mode]="groupMode"
|
||||
[readOnly]="groupReadonly"
|
||||
[validate]="groupPreselectValidation"
|
||||
[roles]="groupRoles"
|
||||
[appName]="groupAppName"
|
||||
[preSelectGroups]="preSelectGroup"
|
||||
(selectGroup)="onSelectGroup($event)"
|
||||
(removeGroup)="onRemoveGroup($event)"
|
||||
(warning)="onGroupsWarning($event)"></adf-cloud-group>
|
||||
</div>
|
||||
|
||||
<div class="app-group-list" *ngIf="canShowGroupList()">
|
||||
<h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_SELECTED_GROUPS' | translate }}</h4>
|
||||
<h4>{{ 'PEOPLE_GROUPS_CLOUD.ALL_PRESELECTED_GROUPS' | translate }}</h4>
|
||||
<mat-list role="list">
|
||||
<mat-list-item *ngFor="let item of preSelectGroup" role="listitem">
|
||||
<mat-icon mat-list-icon>group</mat-icon>
|
||||
|
@ -34,17 +34,18 @@ export class PeopleGroupCloudDemoComponent {
|
||||
|
||||
peopleMode: string = PeopleCloudComponent.MODE_SINGLE;
|
||||
preSelectUsers: IdentityUserModel[] = [];
|
||||
invalidUsers: IdentityGroupModel[] = [];
|
||||
invalidUsers: IdentityUserModel[] = [];
|
||||
peopleRoles: string[] = [];
|
||||
peopleAppName: string;
|
||||
peopleFilterMode: string = this.DEFAULT_FILTER_MODE;
|
||||
peoplePreselectValidation: Boolean = false;
|
||||
groupPreselectValidation = false;
|
||||
peopleReadonly = false;
|
||||
groupReadonly = false;
|
||||
|
||||
groupMode: string = GroupCloudComponent.MODE_SINGLE;
|
||||
preSelectGroup: IdentityGroupModel[] = [];
|
||||
invalidGroups: IdentityGroupModel[] = [];
|
||||
selectedGroupList: IdentityGroupModel[] = [];
|
||||
groupRoles: string[];
|
||||
groupAppName: string;
|
||||
groupFilterMode: string = this.DEFAULT_FILTER_MODE;
|
||||
@ -75,12 +76,18 @@ export class PeopleGroupCloudDemoComponent {
|
||||
|
||||
onChangePeopleMode(event: MatRadioChange) {
|
||||
this.peopleMode = event.value;
|
||||
this.preSelectUsers = [...this.preSelectUsers];
|
||||
}
|
||||
|
||||
onChangePeopleReadonly(event: MatCheckboxChange) {
|
||||
this.peopleReadonly = event.checked;
|
||||
}
|
||||
|
||||
onChangeGroupReadonly(event: MatCheckboxChange) {
|
||||
this.groupReadonly = event.checked;
|
||||
}
|
||||
|
||||
onChangeGroupsMode(event: MatRadioChange) {
|
||||
this.groupMode = event.value;
|
||||
this.preSelectGroup = [...this.preSelectGroup];
|
||||
}
|
||||
|
||||
onChangePeopleFilterMode(event: MatRadioChange) {
|
||||
@ -119,18 +126,10 @@ export class PeopleGroupCloudDemoComponent {
|
||||
|
||||
onChangePeopleValidation(event: MatCheckboxChange) {
|
||||
this.peoplePreselectValidation = event.checked;
|
||||
this.preSelectUsers = [...this.preSelectUsers];
|
||||
if (!this.peoplePreselectValidation) {
|
||||
this.invalidUsers = [];
|
||||
}
|
||||
}
|
||||
|
||||
onChangeGroupValidation(event: MatCheckboxChange) {
|
||||
this.groupPreselectValidation = event.checked;
|
||||
this.preSelectGroup = [...this.preSelectGroup];
|
||||
if (!this.groupPreselectValidation) {
|
||||
this.invalidGroups = [];
|
||||
}
|
||||
}
|
||||
|
||||
onGroupsWarning(warning: any) {
|
||||
@ -167,22 +166,6 @@ export class PeopleGroupCloudDemoComponent {
|
||||
return this.groupMode === GroupCloudComponent.MODE_MULTIPLE;
|
||||
}
|
||||
|
||||
onRemoveGroup(group: IdentityGroupModel) {
|
||||
this.preSelectGroup = this.preSelectGroup.filter((value: any) => value.id !== group.id);
|
||||
}
|
||||
|
||||
onSelectUser(user: IdentityUserModel) {
|
||||
if (this.peopleMode === PeopleCloudComponent.MODE_MULTIPLE) {
|
||||
this.preSelectUsers.push(user);
|
||||
}
|
||||
}
|
||||
|
||||
onSelectGroup(group: IdentityGroupModel) {
|
||||
if (this.groupMode === GroupCloudComponent.MODE_MULTIPLE) {
|
||||
this.preSelectGroup.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
get peopleSingleMode() {
|
||||
return PeopleCloudComponent.MODE_SINGLE;
|
||||
}
|
||||
|
@ -26,9 +26,10 @@ Searches Groups.
|
||||
|
||||
| Name | Type | Default value | Description |
|
||||
| ---- | ---- | ------------- | ----------- |
|
||||
| appName | `string` | | Name of the application. If specified this shows the users who have access to the app. |
|
||||
| appName | `string` | | Name of the application. If specified this shows the groups who have access to the app. |
|
||||
| readOnly | `boolean` | false | readOnly mode (true/false). |
|
||||
| mode | `string` | | User selection mode (single/multiple). |
|
||||
| preSelectGroups | [`IdentityGroupModel`](../../../lib/core/models/identity-group.model.ts)`[]` | \[] | Array of users to be pre-selected. This pre-selects all users in multi selection mode and only the first user of the array in single selection mode. |
|
||||
| preSelectGroups | [`IdentityGroupModel`](../../../lib/core/models/identity-group.model.ts)`[]` | \[] | Array of groups to be pre-selected. This pre-selects all groups in multi selection mode and only the first group of the array in single selection mode. |
|
||||
| roles | `string[]` | \[] | Role names of the groups to be listed. |
|
||||
| searchGroupsControl | `FormControl` | new FormControl() | FormControl to search the group |
|
||||
| title | `string` | | Title of the field |
|
||||
@ -95,6 +96,20 @@ export class MyComponent {
|
||||
|
||||
### Read-only
|
||||
|
||||
You can use `readonly` property to set the component in `readonly` mode. Readonly mode will disable any interaction with the component.
|
||||
|
||||
```html
|
||||
<adf-cloud-group
|
||||
[appName]="'simple-app'"
|
||||
[mode]="'multiple'"
|
||||
[preSelectGroups]="groups"
|
||||
[readOnly]="true">
|
||||
</adf-cloud-group>
|
||||
```
|
||||
|
||||
If you want to manage each group seperately you can set their readonly property at your preference.
|
||||
You need to have component's readonly property set to false. Component's readonly mode overwrites groups level.
|
||||
|
||||
You can use `readonly` property to make preselected groups read-only in `multiple` mode.
|
||||
|
||||
Usage example:
|
||||
|
@ -25,6 +25,7 @@ Allows one or more users to be selected (with auto-suggestion) based on the inpu
|
||||
| Name | Type | Default value | Description |
|
||||
| ---- | ---- | ------------- | ----------- |
|
||||
| appName | `string` | | Name of the application. If specified, this shows the users who have access to the app. |
|
||||
| readOnly | `boolean` | false | readOnly mode (true/false). |
|
||||
| mode | `string` | | User selection mode (single/multiple). |
|
||||
| preSelectUsers | [`IdentityUserModel`](../../../lib/core/models/identity-user.model.ts)`[]` | | Array of users to be pre-selected. All users in the array are pre-selected in multi selection mode, but only the first user is pre-selected in single selection mode. Mandatory properties are: id, email, username |
|
||||
| roles | `string[]` | | Role names of the users to be listed. |
|
||||
@ -44,7 +45,19 @@ Allows one or more users to be selected (with auto-suggestion) based on the inpu
|
||||
|
||||
### Read-only
|
||||
|
||||
You can use `readonly` property to make preselected users read-only in `multiple` mode.
|
||||
You can use `readonly` property to set the component in `readonly` mode. Readonly mode will disable any interaction with the component.
|
||||
|
||||
```html
|
||||
<adf-cloud-people
|
||||
[appName]="'simple-app'"
|
||||
[mode]="'multiple'"
|
||||
[preSelectUsers]="preSelectUsers"
|
||||
[readOnly]="true">
|
||||
</adf-cloud-people>
|
||||
```
|
||||
|
||||
If you want to manage each user seperately you can set their readonly property at your preference.
|
||||
You need to have component's readonly property set to false. Component's readonly mode overwrites users level.
|
||||
|
||||
```ts
|
||||
const preSelectUsers = [
|
||||
@ -55,7 +68,7 @@ const preSelectUsers = [
|
||||
```
|
||||
```html
|
||||
<adf-cloud-people
|
||||
[mode]="'multiple'",
|
||||
[mode]="'multiple'"
|
||||
[preSelectUsers]="preSelectUsers">
|
||||
</adf-cloud-people>
|
||||
```
|
||||
|
@ -193,21 +193,22 @@ describe('People Groups Cloud Component', () => {
|
||||
|
||||
await peopleGroupCloudComponentPage.clickPeopleCloudSingleSelection();
|
||||
await peopleGroupCloudComponentPage.checkPeopleCloudSingleSelectionIsSelected();
|
||||
|
||||
await peopleGroupCloudComponentPage.enterPeoplePreselect('[{"id":"12345","username":"someUsername","email":"someEmail"}]');
|
||||
await expect(await peopleCloudComponent.checkSelectedPeople('someUsername'));
|
||||
|
||||
await peopleGroupCloudComponentPage.clickPreselectValidation();
|
||||
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true');
|
||||
|
||||
await peopleGroupCloudComponentPage.enterPeoplePreselect('[{"id":"12345","username":"someUsername","email":"someEmail"}]');
|
||||
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe('');
|
||||
|
||||
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true');
|
||||
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"id":"${noRoleUser.idIdentityService}"}]`);
|
||||
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe(`${noRoleUser.firstName} ${noRoleUser.lastName}`);
|
||||
await expect(await peopleCloudComponent.checkSelectedPeople(`${noRoleUser.firstName} ${noRoleUser.lastName}`));
|
||||
|
||||
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"email":"${apsUser.email}"}]`);
|
||||
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe(`${apsUser.firstName} ${apsUser.lastName}`);
|
||||
await expect(await peopleCloudComponent.checkSelectedPeople(`${apsUser.firstName} ${apsUser.lastName}`));
|
||||
|
||||
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"username":"${testUser.username}"}]`);
|
||||
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe(`${testUser.firstName} ${testUser.lastName}`);
|
||||
await expect(await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`));
|
||||
});
|
||||
|
||||
it('[C309676] Should fetch the preselect users based on the Validate flag set to True in Multiple mode selection', async () => {
|
||||
@ -254,18 +255,6 @@ describe('People Groups Cloud Component', () => {
|
||||
|
||||
});
|
||||
|
||||
it('[C309678] Should not fetch the preselect users when mandatory parameters Id, Email and username are missing', async () => {
|
||||
await peopleGroupCloudComponentPage.clickPeopleCloudMultipleSelection();
|
||||
await peopleGroupCloudComponentPage.checkPeopleCloudMultipleSelectionIsSelected();
|
||||
await peopleGroupCloudComponentPage.clickPreselectValidation();
|
||||
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true');
|
||||
|
||||
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"firstName":"${apsUser.firstName}","lastName":"${apsUser.lastName},"` +
|
||||
`{"firstName":"${testUser.firstName}","lastName":"${testUser.lastName}",{"firstName":"${noRoleUser.firstName}","lastName":"${noRoleUser.lastName}"]`);
|
||||
await browser.sleep(200);
|
||||
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe('');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -90,7 +90,7 @@ describe('People Groups Cloud Component', () => {
|
||||
await peopleCloudComponent.checkUserIsDisplayed(`${testUser.firstName} ${testUser.lastName}`);
|
||||
await peopleCloudComponent.selectAssigneeFromList(`${testUser.firstName} ${testUser.lastName}`);
|
||||
await browser.sleep(100);
|
||||
await expect(await peopleCloudComponent.getAssigneeFieldContent()).toBe(`${testUser.firstName} ${testUser.lastName}`);
|
||||
await expect(await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`));
|
||||
});
|
||||
|
||||
it('[C305041] Should filter the People Multiple Selection with the Application name filter', async () => {
|
||||
@ -118,7 +118,7 @@ describe('People Groups Cloud Component', () => {
|
||||
await groupCloudComponentPage.searchGroups(hrGroup.name);
|
||||
await groupCloudComponentPage.checkGroupIsDisplayed(hrGroup.name);
|
||||
await groupCloudComponentPage.selectGroupFromList(hrGroup.name);
|
||||
await expect(await groupCloudComponentPage.getGroupsFieldContent()).toBe(hrGroup.name);
|
||||
await expect(await groupCloudComponentPage.checkSelectedGroup(hrGroup.name));
|
||||
});
|
||||
|
||||
it('[C305041] Should filter the Groups Multiple Selection with the Application name filter', async () => {
|
||||
|
@ -106,7 +106,7 @@ describe('Start Task', () => {
|
||||
it('[C297675] Should create a task unassigned when assignee field is empty in Start Task form', async () => {
|
||||
await tasksCloudDemoPage.openNewTaskForm();
|
||||
await startTask.checkFormIsDisplayed();
|
||||
await peopleCloudComponent.clearAssignee();
|
||||
await peopleCloudComponent.clearAssigneeFromChip(testUser.username);
|
||||
await startTask.addName(unassignedTaskName);
|
||||
await startTask.clickStartButton();
|
||||
await tasksCloudDemoPage.editTaskFilterCloudComponent();
|
||||
@ -125,7 +125,7 @@ describe('Start Task', () => {
|
||||
it('[C291956] Should be able to create a new standalone task without assignee', async () => {
|
||||
await tasksCloudDemoPage.openNewTaskForm();
|
||||
await startTask.checkFormIsDisplayed();
|
||||
await peopleCloudComponent.clearAssignee();
|
||||
await peopleCloudComponent.clearAssigneeFromChip(testUser.username);
|
||||
await startTask.addName(unassignedTaskName);
|
||||
await startTask.checkStartButtonIsEnabled();
|
||||
await startTask.clickStartButton();
|
||||
@ -203,7 +203,7 @@ describe('Start Task', () => {
|
||||
it('[C291953] Assignee field should display the logged user as default', async () => {
|
||||
await tasksCloudDemoPage.openNewTaskForm();
|
||||
await startTask.checkFormIsDisplayed();
|
||||
await expect(await peopleCloudComponent.getAssignee()).toContain(testUser.firstName, 'does not contain Admin');
|
||||
await expect(await peopleCloudComponent.checkSelectedPeople(testUser.firstName));
|
||||
await startTask.clickCancelButton();
|
||||
});
|
||||
|
||||
@ -212,7 +212,7 @@ describe('Start Task', () => {
|
||||
await startTask.checkFormIsDisplayed();
|
||||
await startTask.addName(reassignTaskName);
|
||||
|
||||
await expect(await peopleCloudComponent.getAssignee()).toBe(`${testUser.firstName} ${testUser.lastName}`);
|
||||
await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`);
|
||||
await peopleCloudComponent.searchAssignee(apsUser.username);
|
||||
await peopleCloudComponent.checkUserIsDisplayed(`${apsUser.firstName} ${apsUser.lastName}`);
|
||||
await peopleCloudComponent.selectAssigneeFromList(`${apsUser.firstName} ${apsUser.lastName}`);
|
||||
|
@ -1,13 +1,14 @@
|
||||
<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">
|
||||
<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$ | async"
|
||||
*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 }}"
|
||||
[attr.data-automation-id]="'adf-cloud-group-chip-' + group.name">
|
||||
matTooltip="{{ (group.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}">
|
||||
{{group.name}}
|
||||
<mat-icon
|
||||
*ngIf="!(group.readonly || readOnly)"
|
||||
@ -17,45 +18,54 @@
|
||||
</mat-chip>
|
||||
<input matInput
|
||||
[formControl]="searchGroupsControl"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="groupChipList"
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
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>
|
||||
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">
|
||||
<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-form-field>
|
||||
<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_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();
|
||||
});
|
||||
});
|
||||
|
||||
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) => {
|
||||
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();
|
||||
});
|
||||
expect(chips.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should emit removeGroup when a selected group is removed', (done) => {
|
||||
const removeSpy = spyOn(component.removeGroup, 'emit');
|
||||
it('should not pre-select any group when preSelectGroups is empty - multiple mode', () => {
|
||||
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();
|
||||
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();
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
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;
|
||||
it('should chip list be disabled and show one single chip - single mode', () => {
|
||||
component.mode = 'single';
|
||||
component.readOnly = true;
|
||||
component.preSelectGroups = <any> mockIdentityGroups;
|
||||
element = fixture.nativeElement;
|
||||
alfrescoApiService = TestBed.get(AlfrescoApiService);
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
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();
|
||||
});
|
||||
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 filter group by name if validate true', (done) => {
|
||||
findGroupsByNameSpy.and.returnValue(of(mockIdentityGroups));
|
||||
it('should chip list be disabled and show all the chips - multiple mode', () => {
|
||||
component.mode = 'multiple';
|
||||
component.validate = true;
|
||||
component.preSelectGroups = <any> [{ name: mockIdentityGroups[1].name }, { name: mockIdentityGroups[2].name }];
|
||||
component.readOnly = true;
|
||||
component.preSelectGroups = <any> mockIdentityGroups;
|
||||
component.ngOnChanges({ 'preSelectGroups': change });
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.filterPreselectGroups().then((result) => {
|
||||
expect(findGroupsByNameSpy).toHaveBeenCalled();
|
||||
expect(component.groupExists(result[0])).toEqual(true);
|
||||
expect(component.groupExists(result[1])).toEqual(true);
|
||||
done();
|
||||
});
|
||||
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 not preselect any group if name is invalid and validation enable', (done) => {
|
||||
findGroupsByNameSpy.and.returnValue(of([]));
|
||||
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> [{ name: 'invalid group' }];
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
component.validatePreselectGroups().then((result) => {
|
||||
fixture.detectChanges();
|
||||
expect(findGroupsByNameSpy).toHaveBeenCalled();
|
||||
expect(result.length).toEqual(0);
|
||||
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 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;
|
||||
try {
|
||||
result = await this.searchGroup(group.name);
|
||||
} catch (error) {
|
||||
result = [];
|
||||
this.logService.error(error);
|
||||
}
|
||||
const isGroupValid: boolean = this.groupExists(result);
|
||||
return isGroupValid ? result : null;
|
||||
});
|
||||
return Promise.all(promiseBatch);
|
||||
}
|
||||
async validatePreselectGroups(): Promise<any> {
|
||||
this.invalidGroups = [];
|
||||
|
||||
public groupExists(result: IdentityGroupModel): boolean {
|
||||
return result
|
||||
&& (result.id !== undefined
|
||||
|| result.name !== undefined);
|
||||
}
|
||||
let preselectedGroupsToValidate: IdentityGroupModel[] = [];
|
||||
|
||||
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);
|
||||
if (this.isSingleMode()) {
|
||||
preselectedGroupsToValidate = [this.preSelectGroups[0]];
|
||||
} else {
|
||||
preselectedGroupsToValidate = this.removeDuplicatedGroups(this.preSelectGroups);
|
||||
}
|
||||
|
||||
await Promise.all(preselectedGroupsToValidate.map(async (group: IdentityGroupModel) => {
|
||||
try {
|
||||
const validationResult = await this.searchGroup(group.name);
|
||||
if (!this.hasGroupIdOrName(validationResult)) {
|
||||
this.invalidGroups.push(group);
|
||||
}
|
||||
});
|
||||
validGroups = this.removeDuplicatedGroups(validGroups);
|
||||
return validGroups;
|
||||
} catch (error) {
|
||||
this.invalidGroups.push(group);
|
||||
this.logService.error(error);
|
||||
}
|
||||
|
||||
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.isLoading = false;
|
||||
}
|
||||
|
||||
public checkPreselectValidationErrors() {
|
||||
|
||||
this.invalidGroups = this.removeDuplicatedGroups(this.invalidGroups);
|
||||
|
||||
if (this.invalidGroups.length > 0) {
|
||||
this.generateInvalidGroupsMessage();
|
||||
}
|
||||
|
||||
this.warning.emit({
|
||||
message: 'INVALID_PRESELECTED_GROUPS',
|
||||
groups: this.invalidGroups
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private loadPreSelectGroups() {
|
||||
if (!this.isMultipleMode()) {
|
||||
this.loadSinglePreselectGroup();
|
||||
generateInvalidGroupsMessage() {
|
||||
this.validateGroupsMessage = '';
|
||||
|
||||
this.invalidGroups.forEach((invalidGroup: IdentityGroupModel, index) => {
|
||||
if (index === this.invalidGroups.length - 1) {
|
||||
this.validateGroupsMessage += `${invalidGroup.name} `;
|
||||
} else {
|
||||
this.loadMultiplePreselectGroups();
|
||||
this.validateGroupsMessage += `${invalidGroup.name}, `;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadNoValidationPreselectGroups() {
|
||||
this.selectedGroups = [...this.removeDuplicatedGroups([...this.preSelectGroups])];
|
||||
if (this.isMultipleMode()) {
|
||||
this.selectedGroups$.next(this.selectedGroups);
|
||||
private async loadPreSelectGroups() {
|
||||
this.selectedGroups = [];
|
||||
|
||||
if (this.isSingleMode()) {
|
||||
this.selectedGroups = [this.preSelectGroups[0]];
|
||||
} else {
|
||||
|
||||
if (this.currentTimeout) {
|
||||
clearTimeout(this.currentTimeout);
|
||||
this.selectedGroups = this.removeDuplicatedGroups(this.preSelectGroups);
|
||||
}
|
||||
|
||||
this.currentTimeout = setTimeout(() => {
|
||||
this.searchGroupsControl.setValue(this.selectedGroups[0]);
|
||||
this.onSelect(this.selectedGroups[0]);
|
||||
}, 0);
|
||||
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,7 +401,7 @@ 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;
|
||||
@ -416,47 +412,65 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
|
||||
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,13 +1,13 @@
|
||||
<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-list #userMultipleChipList [disabled]="isReadonly() || isValidationLoading()" data-automation-id="adf-cloud-people-chip-list">
|
||||
<mat-chip
|
||||
*ngFor="let user of selectedUsers$ | async"
|
||||
*ngFor="let user of selectedUsers"
|
||||
[removable]="!(user.readonly)"
|
||||
[attr.data-automation-id]="'adf-people-cloud-chip-' + user.username"
|
||||
matTooltip="{{ (user.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}"
|
||||
(removed)="onRemove(user)">
|
||||
(removed)="onRemove(user)"
|
||||
matTooltip="{{ (user.readonly ? 'ADF_CLOUD_GROUPS.MANDATORY' : '') | translate }}">
|
||||
{{user | fullName}}
|
||||
<mat-icon
|
||||
matChipRemove
|
||||
@ -16,28 +16,16 @@
|
||||
cancel
|
||||
</mat-icon>
|
||||
</mat-chip>
|
||||
<input
|
||||
#userInput
|
||||
matInput
|
||||
<input matInput
|
||||
[formControl]="searchUserCtrl"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="userChipList"
|
||||
class="adf-cloud-input"
|
||||
[matChipInputFor]="userMultipleChipList"
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
data-automation-id="adf-people-cloud-search-input">
|
||||
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)"
|
||||
@ -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-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>
|
||||
</div>
|
||||
</div>
|
||||
{{ '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();
|
||||
it('should show only one mat chip with the first preSelectedUser', (done) => {
|
||||
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();
|
||||
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('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();
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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 }
|
||||
];
|
||||
const change = new SimpleChange(null, component.preSelectUsers, false);
|
||||
component.mode = 'multiple';
|
||||
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();
|
||||
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(() => {
|
||||
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';
|
||||
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');
|
||||
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 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';
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const removeIcon = fixture.debugElement.query(By.css('mat-chip mat-icon'));
|
||||
removeIcon.nativeElement.click();
|
||||
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.readOnly = true;
|
||||
component.preSelectUsers = <any> mockPreselectedUsers;
|
||||
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);
|
||||
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();
|
||||
});
|
||||
}));
|
||||
|
||||
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();
|
||||
});
|
||||
component.preSelectUsers = <any> [mockPreselectedUsers[0], mockPreselectedUsers[1]];
|
||||
component.ngOnChanges({ 'preSelectUsers': new SimpleChange(null, [mockPreselectedUsers[0], mockPreselectedUsers[1]], false) });
|
||||
});
|
||||
});
|
||||
|
||||
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 removeDuplicateUsers return only unique users', () => {
|
||||
const duplicatedUsers = [{ id: mockUsers[0].id }, { id: mockUsers[0].id }];
|
||||
expect(component.removeDuplicatedUsers(duplicatedUsers)).toEqual([{ id: mockUsers[0].id }]);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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) {
|
||||
|
||||
if (this.hasPreselectedUsersChanged(changes) || this.hasModeChanged(changes) || this.isValidationChanged(changes)) {
|
||||
if (this.hasPreSelectUsers()) {
|
||||
this.loadPreSelectUsers();
|
||||
} else if (this.hasPreselectedUsersCleared(changes)) {
|
||||
this.selectedUsers = [];
|
||||
this.invalidUsers = [];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
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 (!this.isValidationEnabled()) {
|
||||
this.invalidUsers = [];
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
if (changes.appName && this.isAppNameChanged(changes.appName)) {
|
||||
this.loadClientId();
|
||||
this.initSearch();
|
||||
}
|
||||
}
|
||||
|
||||
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,106 +269,172 @@ 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);
|
||||
preselectedUsersToValidate = this.removeDuplicatedUsers(this.preSelectUsers);
|
||||
}
|
||||
|
||||
this.currentTimeout = setTimeout(() => {
|
||||
this.searchUserCtrl.setValue(this.selectedUsers[0]);
|
||||
this.onSelect(this.selectedUsers[0]);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public async loadSinglePreselectUser() {
|
||||
const users = await this.validatePreselectUsers();
|
||||
if (users && users.length > 0) {
|
||||
this.checkPreselectValidationErrors();
|
||||
this.searchUserCtrl.setValue(users[0]);
|
||||
await Promise.all(preselectedUsersToValidate.map(async (user: IdentityUserModel) => {
|
||||
try {
|
||||
const validationResult = await this.searchUser(user);
|
||||
if (!this.hasUserDetails(validationResult)) {
|
||||
this.invalidUsers.push(user);
|
||||
} else {
|
||||
this.checkPreselectValidationErrors();
|
||||
validUsers.push(validationResult);
|
||||
}
|
||||
} catch (error) {
|
||||
this.invalidUsers.push(user);
|
||||
this.logService.error(error);
|
||||
}
|
||||
}));
|
||||
this.checkPreselectValidationErrors();
|
||||
this.alignUsersAfterValidation(validUsers);
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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.generateInvalidUsersMessage();
|
||||
}
|
||||
|
||||
this.warning.emit({
|
||||
message: 'INVALID_PRESELECTED_USERS',
|
||||
users: this.invalidUsers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async loadClientId() {
|
||||
this.clientId = await this.identityUserService.getClientIdByApplicationName(this.appName).toPromise();
|
||||
|
||||
if (this.clientId) {
|
||||
this.enableSearch();
|
||||
}
|
||||
}
|
||||
|
||||
onSelect(user: IdentityUserModel) {
|
||||
this.selectUser.emit(user);
|
||||
if (this.isMultipleMode()) {
|
||||
if (!this.isUserAlreadySelected(user)) {
|
||||
this.selectedUsers.push(user);
|
||||
this.selectedUsersSubject.next(this.selectedUsers);
|
||||
}
|
||||
} else {
|
||||
this.invalidUsers = [];
|
||||
this.selectedUsers = [user];
|
||||
}
|
||||
|
||||
this.userInput.nativeElement.value = '';
|
||||
this.searchUserCtrl.setValue('');
|
||||
} else {
|
||||
this.selectedUsers = [user];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,6 +29,11 @@ export class PeopleCloudComponentPage {
|
||||
await this.peopleCloudSearch.sendKeys(protractor.Key.BACK_SPACE);
|
||||
}
|
||||
|
||||
async clearAssigneeFromChip(username: string): Promise<void> {
|
||||
const assigneeChipRemoveIcon = element(by.css(`[data-automation-id="adf-people-cloud-chip-remove-icon-${username}"]`));
|
||||
await assigneeChipRemoveIcon.click();
|
||||
}
|
||||
|
||||
async searchAssigneeAndSelect(name: string): Promise<void> {
|
||||
await BrowserActions.clearSendKeys(this.peopleCloudSearch, name);
|
||||
await this.selectAssigneeFromList(name);
|
||||
|
Loading…
x
Reference in New Issue
Block a user