[AAE-9019] - People/Group cloud with HxP (#7658)

* Cover the use cases by mocking them

* Replace the mock with real stream and remove useless code

* Provide new service to fetch groups

* Fix group tests

* Use the interface and token injection

* [AAE-8870] add unit test and mock for new service

* Improve roles condifion

* initialize the stream as part of NgOnInit to be sure it relies on the correct FormControl instance(input)

* Rollback tmp change for roles

* [AAE-8641] people abstraction mock

* [AAE-8641] revert angular.json changes

* [AAE-8641] few conditions and code improvements

* [AAE-8641] revert change input controls name

* [AAE-8641] initialize the stream as part of ngOnInit

* [AAE-8641] people abstraction improvements

* [AAE-8870] cherry pick people abstraction

* [AAE-8641] fix people-group e2es

* fix lint

* remove hardcoded identityHost

* Use the identityhost api in case of cloud

* Solve issue with returnType array string

* Rebase and use GroupModel from cloud

* Rebase and use GroupModel from cloud

* Use the bpmHost instead of identityFor

* Add identity ingress for user access service

* Rename test

* Fix linting issues

* Fix playwright storybook e2e for people and group

* Trigger travis

* Fix people group e2e

* improved formatting

* Remove not needed travis var override

* Remove unused import after rebase

* Make roles in filter optional + remove comments

Co-authored-by: Tomasz <tomasz.gnyp@hyland.com>
Co-authored-by: arditdomi <ardit.domi@hyland.com>
This commit is contained in:
Maurizio Vitale 2022-06-28 16:21:59 +01:00 committed by GitHub
parent 93c5619e23
commit e27833d770
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 2117 additions and 1937 deletions

View File

@ -181,7 +181,7 @@ jobs:
use:
- built_libs_cache
- built_demo_shell_cache
- stage: "e2e Test"
name: "Core"
before_script:

View File

@ -843,8 +843,5 @@
}
}
},
"defaultProject": "demoshell",
"cli": {
"analytics": false
}
}
"defaultProject": "demoshell"
}

View File

@ -332,7 +332,8 @@
"ALL_PRESELECTED_GROUPS": "All Preselected Groups",
"INVALID_USERS": "Invalid Users",
"INVALID_GROUPS": "Invalid Groups",
"READONLY_MODE": "Read-only Mode"
"READONLY_MODE": "Read-only Mode",
"GROUPS_RESTRICTION": "Groups Restriction"
},
"SETTINGS_CLOUD": {
"MULTISELECTION": "Multiselection",

View File

@ -12,20 +12,22 @@
'PEOPLE_GROUPS_CLOUD.MULTI' | translate }}</mat-radio-button>
</mat-radio-group>
<div class="app-people-control-options">
<mat-radio-group (change)="onChangePeopleFilterMode($event)">
<mat-radio-button [checked]="true" class="app-people-single-mode" value="appName">{{
'PEOPLE_GROUPS_CLOUD.APP_FILTER_MODE' | translate }}</mat-radio-button>
<mat-radio-button class="app-people-multiple-mode" data-automation-id="app-people-filter-role" value="role">{{
'PEOPLE_GROUPS_CLOUD.ROLE_FILTER_MODE' | translate }}</mat-radio-button>
</mat-radio-group>
<mat-form-field *ngIf="!isPeopleAppNameSelected()" class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.ROLE' | translate }} ["ACTIVITI_ADMIN", "ACTIVITI_USER"]</mat-label>
<input matInput (input)="setPeopleRoles($any($event).target?.value)" data-automation-id="app-people-roles-input" />
</mat-form-field>
<mat-form-field *ngIf="isPeopleAppNameSelected()" class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.APP_NAME' | translate }}</mat-label>
<input matInput (input)="setPeopleAppName($any($event).target?.value)" data-automation-id="app-people-app-input" />
</mat-form-field>
<mat-form-field class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.ROLE' | translate }} ["ACTIVITI_ADMIN", "ACTIVITI_USER"]</mat-label>
<input matInput
(input)="setPeopleRoles($any($event).target?.value)"
data-automation-id="app-people-roles-input"/>
</mat-form-field>
<mat-form-field class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.GROUPS_RESTRICTION' | translate }} ["hr", "sales"]</mat-label>
<input matInput
(input)="setPeopleGroupsRestriction($any($event).target?.value)"
data-automation-id="app-people-groups-restriction-input"/>
</mat-form-field>
<mat-form-field class="app-preselect-value-full">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.PRESELECTED_VALUE' | translate }} {{ defaultPeoplePlaceholder }}</mat-label>
<input matInput (input)="setPeoplePreselectValue($any($event).target?.value)" data-automation-id="app-people-preselect-input" />
@ -42,6 +44,7 @@
[validate]="peoplePreselectValidation"
[appName]="peopleAppName"
[roles]="peopleRoles"
[groupsRestriction]="groupsRestriction"
[title]="'ADF_TASK_LIST.START_TASK.FORM.LABEL.ASSIGNEE'"
[mode]="peopleMode"
(warning)="onUsersWarning($event)"></adf-cloud-people>
@ -83,24 +86,18 @@
'PEOPLE_GROUPS_CLOUD.MULTI' | translate }}</mat-radio-button>
</mat-radio-group>
<div class="app-groups-control-options">
<mat-radio-group (change)="onChangeGroupsFilterMode($event)">
<mat-radio-button [checked]="true" class="app-people-single-mode" value="appName">{{
'PEOPLE_GROUPS_CLOUD.APP_FILTER_MODE' | translate }}</mat-radio-button>
<mat-radio-button class="app-people-multiple-mode" data-automation-id="app-group-filter-role" value="role">{{
'PEOPLE_GROUPS_CLOUD.ROLE_FILTER_MODE' | translate }}</mat-radio-button>
</mat-radio-group>
<mat-form-field *ngIf="!isGroupAppNameSelected()" class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.ROLE' | translate }} ["ACTIVITI_ADMIN", "ACTIVITI_USER"]</mat-label>
<input matInput
(input)="setGroupRoles($any($event).target?.value)"
data-automation-id="app-group-roles-input"/>
</mat-form-field>
<mat-form-field *ngIf="isGroupAppNameSelected()" class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.APP_NAME' | translate }}</mat-label>
<input matInput
(input)="setGroupAppName($any($event).target?.value)"
data-automation-id="app-group-app-input"/>
</mat-form-field>
<mat-form-field class="app-preselect-value">
<mat-label>{{ 'PEOPLE_GROUPS_CLOUD.ROLE' | translate }} ["ACTIVITI_ADMIN", "ACTIVITI_USER"]</mat-label>
<input matInput
(input)="setGroupRoles($any($event).target?.value)"
data-automation-id="app-group-roles-input"/>
</mat-form-field>
<mat-form-field class="app-preselect-value-full">
<mat-label>Preselect: {{ defaultGroupPlaceholder }}</mat-label>
<input matInput

View File

@ -16,10 +16,9 @@
*/
import { Component, ViewEncapsulation } from '@angular/core';
import { ComponentSelectionMode } from '@alfresco/adf-process-services-cloud';
import { ComponentSelectionMode, IdentityUserModel, IdentityGroupModel } from '@alfresco/adf-process-services-cloud';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MatRadioChange } from '@angular/material/radio';
import { IdentityGroupModel, IdentityUserModel } from '@alfresco/adf-core';
@Component({
selector: 'app-people-groups-cloud',
@ -37,6 +36,7 @@ export class PeopleGroupCloudDemoComponent {
preSelectUsers: IdentityUserModel[] = [];
invalidUsers: IdentityUserModel[] = [];
peopleRoles: string[] = [];
groupsRestriction: string[] = [];
peopleAppName: string;
peopleFilterMode: string = this.defaultFilterMode;
peoplePreselectValidation = false;
@ -75,6 +75,10 @@ export class PeopleGroupCloudDemoComponent {
this.groupAppName = value;
}
setPeopleGroupsRestriction(value: string): void {
this.groupsRestriction = this.getArrayFromString(value);
}
onChangePeopleMode(event: MatRadioChange): void {
this.peopleMode = event.value;
}

View File

@ -23,6 +23,6 @@ export class PeopleComponent extends BaseComponent {
super(page, rootElement);
}
public getUserLocator = (userName: 'userName1' | 'userName2') => this.getChild(`[data-automation-id="adf-people-cloud-chip-${userName}"]`);
public getUserLocator = (userName: string) => this.getChild(`[data-automation-id="adf-people-cloud-chip-${userName}"]`);
}

View File

@ -13,11 +13,8 @@ test.describe.configure({ mode: 'parallel' });
test.describe('Groups component stories tests', () => {
test('Valid Preselected Groups', async ({ processServicesCloud, groupComponent }) => {
const expectedUsersName = `
Mock Group 1 cancel
Mock Group 2 cancel
Mock Group 3 cancel
Mock Group 4 cancel
Mock Group 5
Vegetable Aubergine cancel
Meat Chicken cancel
`;
await processServicesCloud.navigateTo({ componentName: 'group', story: 'valid-preselected-groups' });
@ -27,20 +24,19 @@ test.describe('Groups component stories tests', () => {
test('Mandatory Preselected Groups', async ({ processServicesCloud, groupComponent }) => {
const expectedUsersName = `
Mock Group 1
Mock Group 2 cancel
Mock Group 3
Vegetable Aubergine cancel
Meat Chicken
`;
await processServicesCloud.navigateTo({ componentName: 'group', story: 'mandatory-preselected-groups' });
await expect.soft(groupComponent.groupNaming).toContainText(expectedUsersName);
await groupComponent.getUserLocator('Mock Group 1').hover();
await groupComponent.getUserLocator('Meat Chicken').hover();
await expect(groupComponent.tooltip.content).toContainText('Mandatory');
});
test('Invalid Preselected Groups', async ({ processServicesCloud, groupComponent }) => {
const expectedWarningMessage = 'No group found with the name invalid groups';
const expectedWarningMessage = 'No group found with the name Invalid Group';
const expectedWarningIcon = 'error_outline';
await processServicesCloud.navigateTo({ componentName: 'group', story: 'invalid-preselected-groups' });

View File

@ -13,11 +13,9 @@ test.describe.configure({ mode: 'parallel' });
test.describe('People component stories tests', () => {
test('Valid Preselected Users', async ({ processServicesCloud, peopleComponent }) => {
const expectedUsersName = `
first-name-1 last-name-1 cancel
first-name-2 last-name-2 cancel
first-name-3 last-name-3 cancel
first-name-4 last-name-4 cancel
first-name-5 last-name-5
Yorkshire Pudding cancel
Shepherds Pie cancel
Kielbasa Sausage cancel
`;
await processServicesCloud.navigateTo({ componentName: 'people', story: 'valid-preselected-users' });
@ -27,19 +25,19 @@ test.describe('People component stories tests', () => {
test('Mandatory Preselected Users', async ({ processServicesCloud, peopleComponent }) => {
const expectedUsersName = `
first-name-1 last-name-1
first-name-2 last-name-2 cancel
Kielbasa Sausage
Shepherds Pie cancel
`;
await processServicesCloud.navigateTo({ componentName: 'people', story: 'mandatory-preselected-users' });
await peopleComponent.getUserLocator('userName1').hover();
await peopleComponent.getUserLocator('Kielbasa Sausage').hover();
await expect.soft(peopleComponent.usersNaming).toContainText(expectedUsersName);
await expect(peopleComponent.tooltip.content).toContainText('Mandatory');
});
test('Invalid Preselected Users', async ({ processServicesCloud, peopleComponent }) => {
const expectedWarningMessage = 'No user found with the username invalid user';
const expectedWarningMessage = 'No user found with the username Invalid User';
const expectedWarningIcon = 'error_outline';
await processServicesCloud.navigateTo({ componentName: 'people', story: 'invalid-preselected-users' });
@ -49,8 +47,8 @@ test.describe('People component stories tests', () => {
test('Excluded Users', async ({ processServicesCloud, peopleComponent }) => {
const expectedExcludedUsers = `
mocked-user-id-2
mocked-user-id-3
kielbasa
yorkshire
`;
await processServicesCloud.navigateTo({ componentName: 'people', story: 'excluded-users' });

View File

@ -24,10 +24,8 @@ export class PeopleGroupCloudComponentPage {
peopleCloudMultipleSelectionChecked = $('mat-radio-button[data-automation-id="app-people-multiple-mode"][class*="mat-radio-checked"]');
peopleCloudSingleSelection = $('mat-radio-button[data-automation-id="app-people-single-mode"]');
peopleCloudMultipleSelection = $('mat-radio-button[data-automation-id="app-people-multiple-mode"]');
peopleCloudFilterRole = $('mat-radio-button[data-automation-id="app-people-filter-role"]');
groupCloudSingleSelection = $('mat-radio-button[data-automation-id="app-group-single-mode"]');
groupCloudMultipleSelection = $('mat-radio-button[data-automation-id="app-group-multiple-mode"]');
groupCloudFilterRole = $('mat-radio-button[data-automation-id="app-group-filter-role"]');
peopleRoleInput = $('input[data-automation-id="app-people-roles-input"]');
peopleAppInput = $('input[data-automation-id="app-people-app-input"]');
peoplePreselect = $('input[data-automation-id="app-people-preselect-input"]');
@ -37,8 +35,6 @@ export class PeopleGroupCloudComponentPage {
groupCloudComponentTitle = element(by.cssContainingText('mat-card-title', 'Groups Cloud Component'));
preselectValidation = $$('mat-checkbox.app-preselect-value').first();
preselectValidationStatus = $$('mat-checkbox.app-preselect-value label input').first();
peopleFilterByAppName = $('.app-people-control-options mat-radio-button[value="appName"]');
groupFilterByAppName = $('.app-groups-control-options mat-radio-button[value="appName"]');
async navigateTo() {
await browser.get('#/cloud/people-group-cloud');
@ -69,18 +65,6 @@ export class PeopleGroupCloudComponentPage {
await BrowserVisibility.waitUntilElementIsVisible(this.peopleCloudMultipleSelectionChecked);
}
async checkPeopleCloudFilterRole(): Promise<void> {
await BrowserVisibility.waitUntilElementIsVisible(this.peopleCloudFilterRole);
}
async clickPeopleCloudFilterRole(): Promise<void> {
await BrowserActions.click(this.peopleCloudFilterRole);
}
async clickGroupCloudFilterRole(): Promise<void> {
await BrowserActions.click(this.groupCloudFilterRole);
}
async enterPeopleRoles(roles: string): Promise<void> {
await BrowserActions.clearSendKeys(this.peopleRoleInput, roles);
}
@ -113,14 +97,6 @@ export class PeopleGroupCloudComponentPage {
return BrowserActions.getAttribute(this.preselectValidationStatus, 'aria-checked');
}
async clickPeopleFilerByApp(): Promise<void> {
await BrowserActions.click(this.peopleFilterByAppName);
}
async clickGroupFilerByApp(): Promise<void> {
await BrowserActions.click(this.groupFilterByAppName);
}
async enterPeopleAppName(appName: string): Promise<void> {
await BrowserActions.clearSendKeys(this.peopleAppInput, appName);
}

View File

@ -29,224 +29,220 @@ import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
describe('People Groups Cloud Component', () => {
describe('People Groups Cloud Component', () => {
const loginSSOPage = new LoginPage();
const navigationBarPage = new NavigationBarPage();
const peopleGroupCloudComponentPage = new PeopleGroupCloudComponentPage();
const peopleCloudComponent = new PeopleCloudComponentPage();
const groupCloudComponentPage = new GroupCloudComponentPage();
const loginSSOPage = new LoginPage();
const navigationBarPage = new NavigationBarPage();
const peopleGroupCloudComponentPage = new PeopleGroupCloudComponentPage();
const peopleCloudComponent = new PeopleCloudComponentPage();
const groupCloudComponentPage = new GroupCloudComponentPage();
const apiService = createApiService();
const identityService = new IdentityService(apiService);
const rolesService = new RolesService(apiService);
const groupIdentityService = new GroupIdentityService(apiService);
const apiService = createApiService();
const identityService = new IdentityService(apiService);
const rolesService = new RolesService(apiService);
const groupIdentityService = new GroupIdentityService(apiService);
let apsUser;
let testUser;
let devopsUser;
let activitiUser;
let multipleRolesUser;
let noRoleUser;
let groupUser;
let groupAdmin;
let groupNoRole;
let groupMultipleRoles;
let apsUserRoleId: string;
let apsAdminRoleId: string;
let users = [];
let groups = [];
let apsUser;
let testUser;
let devopsUser;
let activitiUser;
let noRoleUser;
let groupUser;
let groupAdmin;
let groupNoRole;
let apsUserRoleId: string;
let apsAdminRoleId: string;
let users = [];
let groups = [];
beforeAll(async () => {
await apiService.loginWithProfile('identityAdmin');
beforeAll(async () => {
await apiService.loginWithProfile('identityAdmin');
testUser = await identityService.createIdentityUserWithRole([identityService.ROLES.ACTIVITI_USER]);
apsUser = await identityService.createIdentityUserWithRole([identityService.ROLES.ACTIVITI_USER]);
activitiUser = await identityService.createIdentityUserWithRole([identityService.ROLES.ACTIVITI_USER]);
devopsUser = await identityService.createIdentityUserWithRole([identityService.ROLES.ACTIVITI_DEVOPS]);
multipleRolesUser = await identityService.createIdentityUserWithRole([identityService.ROLES.ACTIVITI_USER, identityService.ROLES.ACTIVITI_ADMIN]);
noRoleUser = await identityService.createIdentityUser();
testUser = await identityService.createIdentityUserWithRole([identityService.ROLES.ACTIVITI_USER]);
apsUser = await identityService.createIdentityUserWithRole([identityService.ROLES.ACTIVITI_USER]);
activitiUser = await identityService.createIdentityUserWithRole([identityService.ROLES.ACTIVITI_USER]);
devopsUser = await identityService.createIdentityUserWithRole([identityService.ROLES.ACTIVITI_DEVOPS]);
noRoleUser = await identityService.createIdentityUser();
apsAdminRoleId = await rolesService.getRoleIdByRoleName(identityService.ROLES.ACTIVITI_ADMIN);
apsUserRoleId = await rolesService.getRoleIdByRoleName(identityService.ROLES.ACTIVITI_USER);
apsAdminRoleId = await rolesService.getRoleIdByRoleName(identityService.ROLES.ACTIVITI_ADMIN);
apsUserRoleId = await rolesService.getRoleIdByRoleName(identityService.ROLES.ACTIVITI_USER);
groupUser = await groupIdentityService.createIdentityGroup();
await groupIdentityService.assignRole(groupUser.id, apsUserRoleId, identityService.ROLES.ACTIVITI_USER);
groupUser = await groupIdentityService.createIdentityGroup();
await groupIdentityService.assignRole(groupUser.id, apsUserRoleId, identityService.ROLES.ACTIVITI_USER);
groupAdmin = await groupIdentityService.createIdentityGroup();
await groupIdentityService.assignRole(groupAdmin.id, apsAdminRoleId, identityService.ROLES.ACTIVITI_ADMIN);
groupAdmin = await groupIdentityService.createIdentityGroup();
await groupIdentityService.assignRole(groupAdmin.id, apsAdminRoleId, identityService.ROLES.ACTIVITI_ADMIN);
groupMultipleRoles = await groupIdentityService.createIdentityGroup();
await groupIdentityService.assignRole(groupMultipleRoles.id, apsAdminRoleId, identityService.ROLES.ACTIVITI_ADMIN);
await groupIdentityService.assignRole(groupMultipleRoles.id, apsUserRoleId, identityService.ROLES.ACTIVITI_USER);
groupNoRole = await groupIdentityService.createIdentityGroup();
groupNoRole = await groupIdentityService.createIdentityGroup();
users = [`${apsUser.idIdentityService}`, `${activitiUser.idIdentityService}`, `${noRoleUser.idIdentityService}`,
`${testUser.idIdentityService}`, `${devopsUser.idIdentityService}`];
groups = [`${groupUser.id}`, `${groupAdmin.id}`, `${groupNoRole.id}`];
users = [`${apsUser.idIdentityService}`, `${activitiUser.idIdentityService}`, `${noRoleUser.idIdentityService}`,
`${testUser.idIdentityService}`, `${devopsUser.idIdentityService}`, `${multipleRolesUser.idIdentityService}`];
groups = [`${groupUser.id}`, `${groupAdmin.id}`, `${groupNoRole.id}`, `${groupMultipleRoles.id}`];
await loginSSOPage.login(testUser.username, testUser.password);
});
await loginSSOPage.login(testUser.username, testUser.password);
});
afterAll(async () => {
await apiService.loginWithProfile('identityAdmin');
for (const user of users) {
await identityService.deleteIdentityUser(user);
}
for (const group of groups) {
await groupIdentityService.deleteIdentityGroup(group);
}
});
afterAll(async () => {
await apiService.loginWithProfile('identityAdmin');
for (const user of users) {
await identityService.deleteIdentityUser(user);
}
for (const group of groups) {
await groupIdentityService.deleteIdentityGroup(group);
}
});
beforeEach(async () => {
await navigationBarPage.navigateToPeopleGroupCloudPage();
await peopleGroupCloudComponentPage.checkGroupsCloudComponentTitleIsDisplayed();
await peopleGroupCloudComponentPage.checkPeopleCloudComponentTitleIsDisplayed();
});
afterEach(async () => {
await browser.refresh();
});
describe('[C297674] Should be able to add filtering to People Cloud Component', () => {
beforeEach(async () => {
await navigationBarPage.navigateToPeopleGroupCloudPage();
await peopleGroupCloudComponentPage.checkGroupsCloudComponentTitleIsDisplayed();
await peopleGroupCloudComponentPage.checkPeopleCloudComponentTitleIsDisplayed();
});
afterEach(async () => {
await browser.refresh();
});
describe('[C297674] Should be able to add filtering to People Cloud Component', () => {
beforeEach(async () => {
await peopleGroupCloudComponentPage.clickPeopleCloudMultipleSelection();
await peopleGroupCloudComponentPage.checkPeopleCloudMultipleSelectionIsSelected();
await peopleGroupCloudComponentPage.clickPeopleCloudFilterRole();
await peopleGroupCloudComponentPage.checkPeopleCloudFilterRole();
});
it('No role filtering', async () => {
await peopleCloudComponent.searchAssignee(noRoleUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${noRoleUser.firstName} ${noRoleUser.lastName}`);
await peopleCloudComponent.searchAssignee(apsUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.searchAssignee(testUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${testUser.firstName} ${testUser.lastName}`);
});
it('One role filtering', async () => {
await peopleGroupCloudComponentPage.enterPeopleRoles(`["${identityService.ROLES.ACTIVITI_USER}"]`);
await peopleCloudComponent.searchAssignee(apsUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.searchAssignee(devopsUser.lastName);
await peopleCloudComponent.checkNoResultsFoundError();
await peopleCloudComponent.searchAssignee(noRoleUser.lastName);
await peopleCloudComponent.checkNoResultsFoundError();
});
it('Multiple roles filtering', async () => {
await peopleGroupCloudComponentPage.enterPeopleRoles(`["${identityService.ROLES.ACTIVITI_USER}", "${identityService.ROLES.ACTIVITI_USER}"]`);
await peopleCloudComponent.searchAssignee(apsUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.searchAssignee(testUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${testUser.firstName} ${testUser.lastName}`);
await peopleCloudComponent.searchAssignee(noRoleUser.lastName);
await peopleCloudComponent.checkNoResultsFoundError();
});
});
describe('[C309674] Should be able to add filtering to Group Cloud Component', () => {
beforeEach(async () => {
await peopleGroupCloudComponentPage.clickGroupCloudMultipleSelection();
await peopleGroupCloudComponentPage.clickGroupCloudFilterRole();
});
it('No role filtering', async () => {
await peopleGroupCloudComponentPage.clearField(peopleGroupCloudComponentPage.groupRoleInput);
await groupCloudComponentPage.searchGroups(groupNoRole.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupNoRole.name);
await groupCloudComponentPage.searchGroups(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupAdmin.name);
await groupCloudComponentPage.searchGroups(groupUser.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupUser.name);
});
it('One role filtering', async () => {
await peopleGroupCloudComponentPage.enterGroupRoles(`["${identityService.ROLES.ACTIVITI_ADMIN}"]`);
await groupCloudComponentPage.searchGroups(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupAdmin.name);
await groupCloudComponentPage.searchGroups(groupUser.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupUser.name);
await groupCloudComponentPage.searchGroups(groupNoRole.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupNoRole.name);
});
it('[C309996] Should be able to filter groups based on composite roles ACTIVITI_USER', async () => {
await peopleGroupCloudComponentPage.enterGroupRoles(`["${identityService.ROLES.ACTIVITI_USER}"]`);
await groupCloudComponentPage.searchGroups(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupAdmin.name);
await groupCloudComponentPage.searchGroups(groupNoRole.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupNoRole.name);
await groupCloudComponentPage.searchGroups(groupUser.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupUser.name);
});
it('Multiple roles filtering', async () => {
await peopleGroupCloudComponentPage.enterGroupRoles(`["${identityService.ROLES.ACTIVITI_ADMIN}", "${identityService.ROLES.ACTIVITI_USER}"]`);
await groupCloudComponentPage.searchGroups(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupAdmin.name);
await groupCloudComponentPage.searchGroups(groupUser.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupUser.name);
await groupCloudComponentPage.searchGroups(groupNoRole.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupNoRole.name);
});
});
it('[C305033] Should fetch the preselect users based on the Validate flag set to True in Single mode selection', async () => {
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":"${noRoleUser.idIdentityService}"}]`);
await expect(await peopleCloudComponent.checkSelectedPeople(`${noRoleUser.firstName} ${noRoleUser.lastName}`));
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"email":"${apsUser.email}"}]`);
await expect(await peopleCloudComponent.checkSelectedPeople(`${apsUser.firstName} ${apsUser.lastName}`));
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"username":"${testUser.username}"}]`);
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 () => {
await peopleGroupCloudComponentPage.clickPeopleCloudMultipleSelection();
await peopleGroupCloudComponentPage.checkPeopleCloudMultipleSelectionIsSelected();
await peopleGroupCloudComponentPage.clickPreselectValidation();
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true');
});
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"id":"${apsUser.idIdentityService}"},{"id":"${testUser.idIdentityService}"},` +
`{"id":"${noRoleUser.idIdentityService}"}]`);
await peopleCloudComponent.checkSelectedPeople(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${noRoleUser.firstName} ${noRoleUser.lastName}`);
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"email":"${apsUser.email}"},{"email":"${testUser.email}"},{"email":"${noRoleUser.email}"}]`);
await peopleCloudComponent.checkSelectedPeople(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${noRoleUser.firstName} ${noRoleUser.lastName}`);
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"username":"${apsUser.username}"},{"username":"${testUser.username}"},` +
`{"username":"${noRoleUser.username}"}]`);
await peopleCloudComponent.checkSelectedPeople(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${noRoleUser.firstName} ${noRoleUser.lastName}`);
it('No role filtering', async () => {
await peopleCloudComponent.searchAssignee(noRoleUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${noRoleUser.firstName} ${noRoleUser.lastName}`);
await peopleCloudComponent.searchAssignee(apsUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.searchAssignee(testUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${testUser.firstName} ${testUser.lastName}`);
});
it('One role filtering', async () => {
await peopleGroupCloudComponentPage.enterPeopleRoles(`["${identityService.ROLES.ACTIVITI_USER}"]`);
await peopleCloudComponent.searchAssignee(apsUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.searchAssignee(devopsUser.lastName);
await peopleCloudComponent.checkNoResultsFoundError();
await peopleCloudComponent.searchAssignee(noRoleUser.lastName);
await peopleCloudComponent.checkNoResultsFoundError();
});
it('[C309677] Should populate the Users without any validation when the Preselect flag is set to false', async () => {
await peopleGroupCloudComponentPage.clickPeopleCloudMultipleSelection();
await peopleGroupCloudComponentPage.checkPeopleCloudMultipleSelectionIsSelected();
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('false');
await peopleGroupCloudComponentPage.enterPeoplePreselect(
`[{"id":"TestId1","firstName":"TestFirstName1","lastName":"TestLastName1"},` +
`{"id":"TestId2","firstName":"TestFirstName2","lastName":"TestLastName2"},` +
`{"id":"TestId3","firstName":"TestFirstName3","lastName":"TestLastName3"}]`);
await peopleCloudComponent.checkSelectedPeople('TestFirstName1 TestLastName1');
await peopleCloudComponent.checkSelectedPeople('TestFirstName2 TestLastName2');
await peopleCloudComponent.checkSelectedPeople('TestFirstName3 TestLastName3');
it('Multiple roles filtering', async () => {
await peopleGroupCloudComponentPage.enterPeopleRoles(`["${identityService.ROLES.ACTIVITI_USER}", "${identityService.ROLES.ACTIVITI_ADMIN}"]`);
await peopleCloudComponent.searchAssignee(multipleRolesUser.lastName);
await peopleCloudComponent.checkUserIsDisplayed(`${multipleRolesUser.firstName} ${multipleRolesUser.lastName}`);
await peopleCloudComponent.searchAssignee(apsUser.lastName);
await peopleCloudComponent.checkUserIsNotDisplayed(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.searchAssignee(testUser.lastName);
await peopleCloudComponent.checkUserIsNotDisplayed(`${testUser.firstName} ${testUser.lastName}`);
await peopleCloudComponent.searchAssignee(noRoleUser.lastName);
await peopleCloudComponent.checkNoResultsFoundError();
});
});
describe('[C309674] Should be able to add filtering to Group Cloud Component', () => {
beforeEach(async () => {
await peopleGroupCloudComponentPage.clickGroupCloudMultipleSelection();
});
it('No role filtering', async () => {
await peopleGroupCloudComponentPage.clearField(peopleGroupCloudComponentPage.groupRoleInput);
await groupCloudComponentPage.searchGroups(groupNoRole.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupNoRole.name);
await groupCloudComponentPage.searchGroups(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupAdmin.name);
await groupCloudComponentPage.searchGroups(groupUser.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupUser.name);
});
it('One role filtering', async () => {
await peopleGroupCloudComponentPage.enterGroupRoles(`["${identityService.ROLES.ACTIVITI_ADMIN}"]`);
await groupCloudComponentPage.searchGroups(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupAdmin.name);
await groupCloudComponentPage.searchGroups(groupUser.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupUser.name);
await groupCloudComponentPage.searchGroups(groupNoRole.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupNoRole.name);
});
it('[C309996] Should be able to filter groups based on composite roles ACTIVITI_USER', async () => {
await peopleGroupCloudComponentPage.enterGroupRoles(`["${identityService.ROLES.ACTIVITI_USER}"]`);
await groupCloudComponentPage.searchGroups(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupAdmin.name);
await groupCloudComponentPage.searchGroups(groupNoRole.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupNoRole.name);
await groupCloudComponentPage.searchGroups(groupUser.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupUser.name);
});
it('Multiple roles filtering', async () => {
await peopleGroupCloudComponentPage.enterGroupRoles(`["${identityService.ROLES.ACTIVITI_ADMIN}", "${identityService.ROLES.ACTIVITI_USER}"]`);
await groupCloudComponentPage.searchGroups(groupMultipleRoles.name);
await groupCloudComponentPage.checkGroupIsDisplayed(groupMultipleRoles.name);
await groupCloudComponentPage.searchGroups(groupAdmin.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupAdmin.name);
await groupCloudComponentPage.searchGroups(groupUser.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupUser.name);
await groupCloudComponentPage.searchGroups(groupNoRole.name);
await groupCloudComponentPage.checkGroupIsNotDisplayed(groupNoRole.name);
});
});
it('[C305033] Should fetch the preselect users based on the Validate flag set to True in Single mode selection', async () => {
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(`[{"email":"${apsUser.email}"}]`);
await expect(await peopleCloudComponent.checkSelectedPeople(`${apsUser.firstName} ${apsUser.lastName}`));
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"username":"${testUser.username}"}]`);
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 () => {
await peopleGroupCloudComponentPage.clickPeopleCloudMultipleSelection();
await peopleGroupCloudComponentPage.checkPeopleCloudMultipleSelectionIsSelected();
await peopleGroupCloudComponentPage.clickPreselectValidation();
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('true');
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"email":"${apsUser.email}"},{"email":"${testUser.email}"},{"email":"${noRoleUser.email}"}]`);
await peopleCloudComponent.checkSelectedPeople(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${noRoleUser.firstName} ${noRoleUser.lastName}`);
await peopleGroupCloudComponentPage.enterPeoplePreselect(`[{"username":"${apsUser.username}"},{"username":"${testUser.username}"},` +
`{"username":"${noRoleUser.username}"}]`);
await peopleCloudComponent.checkSelectedPeople(`${apsUser.firstName} ${apsUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${testUser.firstName} ${testUser.lastName}`);
await peopleCloudComponent.checkSelectedPeople(`${noRoleUser.firstName} ${noRoleUser.lastName}`);
await peopleCloudComponent.searchAssignee(noRoleUser.lastName);
await peopleCloudComponent.checkNoResultsFoundError();
});
it('[C309677] Should populate the Users without any validation when the Preselect flag is set to false', async () => {
await peopleGroupCloudComponentPage.clickPeopleCloudMultipleSelection();
await peopleGroupCloudComponentPage.checkPeopleCloudMultipleSelectionIsSelected();
await expect(await peopleGroupCloudComponentPage.getPreselectValidationStatus()).toBe('false');
await peopleGroupCloudComponentPage.enterPeoplePreselect(
`[{"id":"TestId1","firstName":"TestFirstName1","lastName":"TestLastName1"},` +
`{"id":"TestId2","firstName":"TestFirstName2","lastName":"TestLastName2"},` +
`{"id":"TestId3","firstName":"TestFirstName3","lastName":"TestLastName3"}]`);
await peopleCloudComponent.checkSelectedPeople('TestFirstName1 TestLastName1');
await peopleCloudComponent.checkSelectedPeople('TestFirstName2 TestLastName2');
await peopleCloudComponent.checkSelectedPeople('TestFirstName3 TestLastName3');
});
});

View File

@ -32,7 +32,8 @@ describe('People Groups Cloud Component', () => {
const identityService = new IdentityService(apiService);
const groupIdentityService = new GroupIdentityService(apiService);
let apsUser, testUser;
let apsUser;
let testUser;
let noRoleUser;
let groupNoRole;
let users = [];
@ -73,7 +74,6 @@ describe('People Groups Cloud Component', () => {
it('[C305041] Should filter the People Single Selection with the Application name filter', async () => {
await peopleGroupCloudComponentPage.checkPeopleCloudSingleSelectionIsSelected();
await peopleGroupCloudComponentPage.clickPeopleFilerByApp();
await peopleGroupCloudComponentPage.enterPeopleAppName(browser.params.resources.ACTIVITI_CLOUD_APPS.SIMPLE_APP.name);
await peopleCloudComponent.searchAssignee(testUser.firstName);
@ -85,7 +85,6 @@ describe('People Groups Cloud Component', () => {
it('[C305041] Should filter the People Multiple Selection with the Application name filter', async () => {
await peopleGroupCloudComponentPage.clickPeopleCloudMultipleSelection();
await peopleGroupCloudComponentPage.clickPeopleFilerByApp();
await peopleGroupCloudComponentPage.enterPeopleAppName(browser.params.resources.ACTIVITI_CLOUD_APPS.SIMPLE_APP.name);
await peopleCloudComponent.searchAssignee(testUser.firstName);
await peopleCloudComponent.checkUserIsDisplayed(`${testUser.firstName} ${testUser.lastName}`);
@ -102,7 +101,6 @@ describe('People Groups Cloud Component', () => {
it('[C305041] Should filter the Groups Single Selection with the Application name filter', async () => {
await peopleGroupCloudComponentPage.clickGroupCloudSingleSelection();
await peopleGroupCloudComponentPage.clickGroupFilerByApp();
await peopleGroupCloudComponentPage.enterGroupAppName(browser.params.resources.ACTIVITI_CLOUD_APPS.SIMPLE_APP.name);
await groupCloudComponentPage.searchGroups(hrGroup.name);
await groupCloudComponentPage.checkGroupIsDisplayed(hrGroup.name);
@ -112,7 +110,6 @@ describe('People Groups Cloud Component', () => {
it('[C305041] Should filter the Groups Multiple Selection with the Application name filter', async () => {
await peopleGroupCloudComponentPage.clickGroupCloudMultipleSelection();
await peopleGroupCloudComponentPage.clickGroupFilerByApp();
await peopleGroupCloudComponentPage.enterGroupAppName(browser.params.resources.ACTIVITI_CLOUD_APPS.SIMPLE_APP.name);
await groupCloudComponentPage.searchGroups(testGroup.name);
await groupCloudComponentPage.checkGroupIsDisplayed(testGroup.name);

View File

@ -16,6 +16,7 @@ const PROVIDER = process.env.PROVIDER ? process.env.PROVIDER : 'ALL';
const AUTH_TYPE = process.env.AUTH_TYPE ? process.env.AUTH_TYPE : 'BASIC';
const HOST_SSO = process.env.HOST_SSO || process.env.PROXY_HOST_ADF || HOST || 'oauth';
const IDENTITY_HOST = process.env.IDENTITY_HOST || process.env.HOST_SSO + '/auth/admin/realms/alfresco';
const OAUTH_CLIENT_ID = process.env.OAUTH_CLIENDID || 'alfresco';
const IDENTITY_ADMIN_EMAIL = process.env.IDENTITY_ADMIN_EMAIL || "defaultadmin";
@ -40,7 +41,7 @@ const appConfig = {
"log": E2E_LOG_LEVEL,
"ecmHost": HOST_ECM,
"bpmHost": HOST_BPM,
"identityHost": `${HOST_SSO}/auth/admin/realms/alfresco`,
"identityHost": `${IDENTITY_HOST}`,
"provider": PROVIDER,
"authType": AUTH_TYPE,
"oauth2": {

View File

@ -83,7 +83,7 @@ describe('WebscriptComponent', () => {
component.ngOnChanges().then(() => {
fixture.detectChanges();
expect(jasmine.Ajax.requests.mostRecent().url).toBe('http://localhost:9876/ecm/alfresco/service/sample/folder/Company%20Home');
expect(jasmine.Ajax.requests.mostRecent().url).toContain('/ecm/alfresco/service/sample/folder/Company%20Home');
done();
});

View File

@ -22,15 +22,27 @@ export class FullNamePipe implements PipeTransform {
transform(user: any): string {
let fullName = '';
if (user) {
if (user.firstName && user.firstName !== 'null') {
fullName += user.firstName;
}
if (user.lastName && user.lastName !== 'null') {
fullName += fullName.length > 0 ? ' ' : '';
fullName += user.lastName;
}
if (!fullName) {
fullName += user.username ? user.username : user.email ? user.email : '';
if (user.displayName) {
const firstAndLastName = user.displayName.split('\ (.*)');
if (firstAndLastName[0]) {
fullName += firstAndLastName[0];
}
if (firstAndLastName[1]) {
fullName += fullName.length > 0 ? ' ' : '';
fullName += firstAndLastName[1];
}
} else {
if (user.firstName && user.firstName !== 'null') {
fullName += user.firstName;
}
if (user.lastName && user.lastName !== 'null') {
fullName += fullName.length > 0 ? ' ' : '';
fullName += user.lastName;
}
if (!fullName) {
fullName += user.username ? user.username : user.email ? user.email : '';
}
}
}
return fullName;

View File

@ -131,12 +131,12 @@ describe('UserAccessService', () => {
expect(getAccessFromApiSpy.calls.count()).toEqual(1);
});
it('should the url be composed from identity host of app.config', async () => {
it('should the url be composed from bpm host of app.config', async () => {
const fakeIdentityHost = 'https://fake-identity-host.fake.com';
appConfigService.config.identityHost = fakeIdentityHost;
appConfigService.config.bpmHost = fakeIdentityHost;
await userAccessService.fetchUserAccess();
expect(getAccessFromApiSpy).toHaveBeenCalledWith({ url: `${ fakeIdentityHost }/v1/identity/roles` });
expect(getAccessFromApiSpy).toHaveBeenCalledWith({ url: `${fakeIdentityHost}/modeling-service/v1/identity/roles` });
});
it('should not fetch the access from the API if is not configured with OAUTH', async () => {

View File

@ -22,6 +22,8 @@ import { UserAccessModel } from '../models/user-access.model';
import { AppConfigService } from '../app-config/app-config.service';
import { OAuth2Service } from './oauth2.service';
const IDENTITY_MICRO_SERVICE_INGRESS = 'modeling-service';
@Injectable({
providedIn: 'root'
})
@ -50,7 +52,7 @@ export class UserAccessService {
}
private async fetchAccessFromApi() {
const url = `${ this.identityHost }/v1/identity/roles`;
const url = `${this.identityHost}/${IDENTITY_MICRO_SERVICE_INGRESS}/v1/identity/roles`;
await this.oAuth2Service.get({ url })
.toPromise()
.then((response: UserAccessModel) => {
@ -68,7 +70,7 @@ export class UserAccessService {
}
private get identityHost(): string {
return `${this.appConfigService.get('identityHost')}`;
return `${this.appConfigService.get('bpmHost')}`;
}
private isOauth(): boolean {

View File

@ -65,13 +65,13 @@ describe('GroupCloudWidgetComponent', () => {
expect(asterisk.textContent).toEqual('*');
});
it('should be invalid if no option is selected after interaction', async () => {
it('should be invalid after user interaction without typing', async () => {
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('.adf-invalid')).toBeFalsy();
const cloudGroupInput = element.querySelector('adf-cloud-group');
const cloudGroupInput = element.querySelector('[data-automation-id="adf-cloud-group-search-input"]');
cloudGroupInput.dispatchEvent(new Event('blur'));
fixture.detectChanges();

View File

@ -16,11 +16,12 @@
*/
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { WidgetComponent, IdentityGroupModel, FormService } from '@alfresco/adf-core';
import { WidgetComponent, FormService } from '@alfresco/adf-core';
import { FormControl } from '@angular/forms';
import { filter, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { ComponentSelectionMode } from '../../../../types';
import { IdentityGroupModel } from '../../../../group/models/identity-group.model';
/* eslint-disable @angular-eslint/component-selector */

View File

@ -15,20 +15,20 @@
* limitations under the License.
*/
import { FormFieldModel, FormFieldTypes, FormModel, IdentityUserModel, IdentityUserService, setupTestBed } from '@alfresco/adf-core';
import { FormFieldModel, FormFieldTypes, FormModel, IdentityUserModel, setupTestBed } from '@alfresco/adf-core';
import { TranslateModule } from '@ngx-translate/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PeopleCloudWidgetComponent } from './people-cloud.widget';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
import { IdentityUserService } from '../../../../people/services/identity-user.service';
import { mockShepherdsPie, mockYorkshirePudding } from '../../../../people/mock/people-cloud.mock';
describe('PeopleCloudWidgetComponent', () => {
let fixture: ComponentFixture<PeopleCloudWidgetComponent>;
let widget: PeopleCloudWidgetComponent;
let element: HTMLElement;
let identityUserService: IdentityUserService;
const currentUser = { id: 'id', username: 'user' };
const fakeUser = { id: 'fake-id', username: 'fake' };
setupTestBed({
imports: [
@ -48,20 +48,20 @@ describe('PeopleCloudWidgetComponent', () => {
fixture = TestBed.createComponent(PeopleCloudWidgetComponent);
widget = fixture.componentInstance;
element = fixture.nativeElement;
spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(fakeUser);
spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(mockShepherdsPie);
});
it('should preselect the current user', () => {
widget.field = new FormFieldModel(new FormModel(), { value: null, selectLoggedUser: true });
fixture.detectChanges();
expect(widget.preSelectUsers).toEqual([fakeUser]);
expect(widget.preSelectUsers).toEqual([mockShepherdsPie]);
expect(identityUserService.getCurrentUserInfo).toHaveBeenCalled();
});
it('should not preselect the current user if value exist', () => {
widget.field = new FormFieldModel(new FormModel(), { value: [currentUser], selectLoggedUser: true });
widget.field = new FormFieldModel(new FormModel(), { value: [mockYorkshirePudding], selectLoggedUser: true });
fixture.detectChanges();
expect(widget.preSelectUsers).toEqual([currentUser]);
expect(widget.preSelectUsers).toEqual([mockYorkshirePudding]);
expect(identityUserService.getCurrentUserInfo).not.toHaveBeenCalled();
});
@ -84,13 +84,13 @@ describe('PeopleCloudWidgetComponent', () => {
expect(asterisk.textContent).toEqual('*');
});
it('should be invalid if no option is selected after interaction', async () => {
it('should be invalid after user interaction without typing', async () => {
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('.adf-invalid')).toBeFalsy();
const cloudPeopleInput = element.querySelector('adf-cloud-people');
const cloudPeopleInput = element.querySelector('[data-automation-id="adf-people-cloud-search-input"]');
cloudPeopleInput.dispatchEvent(new Event('blur'));
fixture.detectChanges();

View File

@ -16,11 +16,13 @@
*/
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { WidgetComponent, IdentityUserModel, FormService, IdentityUserService } from '@alfresco/adf-core';
import { WidgetComponent, FormService } from '@alfresco/adf-core';
import { FormControl } from '@angular/forms';
import { filter, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { ComponentSelectionMode } from '../../../../types';
import { IdentityUserModel } from '../../../../people/models/identity-user.model';
import { IdentityUserService } from '../../../../people/services/identity-user.service';
/* eslint-disable @angular-eslint/component-selector */

View File

@ -24,35 +24,54 @@ import { GroupCloudModule } from '../group-cloud.module';
import { GroupCloudComponent } from './group-cloud.component';
import {
setupTestBed,
IdentityGroupService,
mockIdentityGroups,
AlfrescoApiService,
CoreTestingModule
} from '@alfresco/adf-core';
import { SimpleChange } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { IdentityGroupService } from '../services/identity-group.service';
import { mockFoodGroups, mockMeatChicken, mockVegetableAubergine } from '../mock/group-cloud.mock';
describe('GroupCloudComponent', () => {
let component: GroupCloudComponent;
let fixture: ComponentFixture<GroupCloudComponent>;
let element: HTMLElement;
let identityGroupService: IdentityGroupService;
let alfrescoApiService: AlfrescoApiService;
let findGroupsByNameSpy: jasmine.Spy;
const mock: any = {
oauth2Auth: {
callCustomApi: () => Promise.resolve(mockIdentityGroups)
},
isEcmLoggedIn: () => false,
reply: jasmine.createSpy('reply')
};
// eslint-disable-next-line prefer-arrow/prefer-arrow-functions
function getElement<T = HTMLElement>(selector: string): T {
return fixture.nativeElement.querySelector(selector);
}
async function searchGroup(value: string) {
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = value;
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
await fixture.whenStable();
fixture.detectChanges();
}
async function searchGroupsAndBlur(value: string) {
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = value;
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
await fixture.whenStable();
fixture.detectChanges();
input.blur();
fixture.detectChanges();
}
function getGroupListUI() {
return fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-row"]'));
}
setupTestBed({
imports: [
TranslateModule.forRoot(),
@ -68,16 +87,12 @@ describe('GroupCloudComponent', () => {
element = fixture.nativeElement;
identityGroupService = TestBed.inject(IdentityGroupService);
alfrescoApiService = TestBed.inject(AlfrescoApiService);
spyOn(alfrescoApiService, 'getInstance').and.returnValue(mock);
});
it('should populate placeholder when title is present', async () => {
it('should populate placeholder when title is present', () => {
component.title = 'TITLE_KEY';
fixture.detectChanges();
await fixture.whenStable();
const matLabel = element.querySelector<HTMLInputElement>('#adf-group-cloud-title-id');
expect(matLabel.textContent).toEqual('TITLE_KEY');
@ -87,330 +102,88 @@ describe('GroupCloudComponent', () => {
beforeEach(() => {
fixture.detectChanges();
findGroupsByNameSpy = spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of(mockIdentityGroups));
findGroupsByNameSpy = spyOn(identityGroupService, 'search').and.returnValue(of(mockFoodGroups));
});
it('should list the groups as dropdown options if the search term has results', (done) => {
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'Mock';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
it('should list the groups as dropdown options if the search term has results', async () => {
await searchGroup('All');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-row"]')).length).toEqual(5);
expect(findGroupsByNameSpy).toHaveBeenCalled();
done();
});
const groupList = getGroupListUI();
expect(groupList.length).toEqual(2);
});
it('should not be able to search for a group that its name matches one of the preselected groups name', (done) => {
component.preSelectGroups = [{ name: mockIdentityGroups[0].name }];
const changes = new SimpleChange(null, [{ name: mockIdentityGroups[0].name }], false);
it('should not be able to search for a group that its name matches one of the preselected groups name', async () => {
component.preSelectGroups = [{ name: mockVegetableAubergine.name }];
const changes = new SimpleChange(null, [{ name: mockVegetableAubergine.name }], false);
component.ngOnChanges({ preSelectGroups: changes });
fixture.detectChanges();
const inputHTMLElement = element.querySelector<HTMLInputElement>('input');
inputHTMLElement.focus();
inputHTMLElement.value = 'mock-group';
inputHTMLElement.dispatchEvent(new Event('keyup'));
inputHTMLElement.dispatchEvent(new Event('input'));
fixture.detectChanges();
await searchGroup('Aubergine');
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-row"]')).length).toEqual(4);
done();
});
const groupList = getGroupListUI();
expect(groupList.length).toEqual(1);
});
it('should hide result list if input is empty', (done) => {
fixture.detectChanges();
it('should hide result list if input is empty', async () => {
await searchGroup('');
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = '';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(element.querySelector('[data-automation-id="adf-cloud-group-row"]')).toBeNull();
done();
});
expect(element.querySelector('[data-automation-id="adf-cloud-group-row"]')).toBeNull();
});
it('should update selected groups when a group is selected', (done) => {
fixture.detectChanges();
it('should update selected groups when a group is selected', async () => {
const selectEmitSpy = spyOn(component.selectGroup, 'emit');
const changedGroupsSpy = spyOn(component.changedGroups, 'emit');
const group = { name: 'groupname' };
component.onSelect(group);
component.onSelect(mockMeatChicken);
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(selectEmitSpy).toHaveBeenCalledWith(group);
expect(changedGroupsSpy).toHaveBeenCalledWith([group]);
expect(component.selectedGroups).toEqual([group]);
done();
});
await fixture.whenStable();
expect(selectEmitSpy).toHaveBeenCalledWith(mockMeatChicken);
expect(changedGroupsSpy).toHaveBeenCalledWith([mockMeatChicken]);
expect(component.selectedGroups).toEqual([mockMeatChicken]);
});
it('should replace the group in single-selection mode', () => {
component.mode = 'single';
const group1 = { name: 'group1' };
const group2 = { name: 'group2' };
component.onSelect(mockVegetableAubergine);
expect(component.selectedGroups).toEqual([mockVegetableAubergine]);
component.onSelect(group1);
expect(component.selectedGroups).toEqual([group1]);
component.onSelect(group2);
expect(component.selectedGroups).toEqual([group2]);
component.onSelect(mockMeatChicken);
expect(component.selectedGroups).toEqual([mockMeatChicken]);
});
it('should allow multiple groups in multi-selection mode', () => {
component.mode = 'multiple';
const group1 = { name: 'group1' };
const group2 = { name: 'group2' };
component.onSelect(mockVegetableAubergine);
component.onSelect(mockMeatChicken);
component.onSelect(group1);
component.onSelect(group2);
expect(component.selectedGroups).toEqual([group1, group2]);
expect(component.selectedGroups).toEqual([mockVegetableAubergine, mockMeatChicken]);
});
it('should allow only unique groups in multi-selection mode', () => {
component.mode = 'multiple';
const group1 = { name: 'group1' };
const group2 = { name: 'group2' };
component.onSelect(mockVegetableAubergine);
component.onSelect(mockMeatChicken);
component.onSelect(mockMeatChicken);
component.onSelect(mockVegetableAubergine);
component.onSelect(group1);
component.onSelect(group2);
component.onSelect(group1);
component.onSelect(group2);
expect(component.selectedGroups).toEqual([group1, group2]);
expect(component.selectedGroups).toEqual([mockVegetableAubergine, mockMeatChicken]);
});
it('should show an error message if the search result empty', (done) => {
it('should show an error message and icon if the search result empty', async () => {
findGroupsByNameSpy.and.returnValue(of([]));
fixture.detectChanges();
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'ZZZ';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
await searchGroupsAndBlur('INCORRECTVALUE');
fixture.detectChanges();
fixture.whenStable().then(() => {
input.blur();
fixture.detectChanges();
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');
done();
});
});
it('should display proper error icon', (done) => {
findGroupsByNameSpy.and.returnValue(of([]));
fixture.detectChanges();
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'ZZZ';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
input.blur();
fixture.detectChanges();
const errorIcon = element.querySelector('.adf-error-icon').textContent;
expect(errorIcon).toEqual('error_outline');
done();
});
});
});
describe('when application name defined', () => {
let checkGroupHasAnyClientAppRoleSpy: jasmine.Spy;
let checkGroupHasClientAppSpy: jasmine.Spy;
beforeEach(() => {
findGroupsByNameSpy = spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of(mockIdentityGroups));
checkGroupHasAnyClientAppRoleSpy = spyOn(identityGroupService, 'checkGroupHasAnyClientAppRole').and.returnValue(of(true));
checkGroupHasClientAppSpy = spyOn(identityGroupService, 'checkGroupHasClientApp').and.returnValue(of(true));
component.preSelectGroups = [];
component.appName = 'mock-app-name';
fixture.detectChanges();
});
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();
await fixture.whenStable();
expect(getClientIdByApplicationNameSpy).toHaveBeenCalled();
});
it('should list groups who have access to the app when appName is specified', (done) => {
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-row"]')).length).toEqual(5);
done();
});
});
it('should not list groups who do not have access to the app when appName is specified', (done) => {
checkGroupHasClientAppSpy.and.returnValue(of(false));
fixture.detectChanges();
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-row"]')).length).toEqual(0);
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-no-results"]')).length).toEqual(1);
done();
});
});
it('should list groups if given roles mapped with client roles', (done) => {
component.roles = ['MOCK_ROLE_1', 'MOCK_ROLE_1'];
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-row"]')).length).toEqual(5);
expect(checkGroupHasAnyClientAppRoleSpy).toHaveBeenCalled();
done();
});
});
it('should not list groups if roles are not mapping with client roles', (done) => {
checkGroupHasAnyClientAppRoleSpy.and.returnValue(of(false));
component.roles = ['MOCK_ROLE_1', 'MOCK_ROLE_1'];
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-row"]')).length).toEqual(0);
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-no-results"]')).length).toEqual(1);
expect(checkGroupHasAnyClientAppRoleSpy).toHaveBeenCalled();
done();
});
});
it('should not call client role mapping sevice if roles not specified', (done) => {
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(checkGroupHasAnyClientAppRoleSpy).not.toHaveBeenCalled();
done();
});
});
it('should validate access to the app when appName is specified', (done) => {
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(checkGroupHasClientAppSpy).toHaveBeenCalledTimes(5);
done();
});
});
it('should not validate access to the app when appName is not specified', (done) => {
component.appName = '';
fixture.detectChanges();
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(checkGroupHasClientAppSpy).not.toHaveBeenCalled();
done();
});
});
it('should show an error message if the group does not have access', (done) => {
checkGroupHasClientAppSpy.and.returnValue(of(false));
findGroupsByNameSpy.and.returnValue(of([]));
fixture.detectChanges();
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'ZZZ';
input.dispatchEvent(new Event('keyup'));
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
input.blur();
fixture.detectChanges();
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');
done();
});
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');
const errorIcon = element.querySelector('.adf-error-icon').textContent;
expect(errorIcon).toEqual('error_outline');
});
});
@ -436,76 +209,12 @@ describe('GroupCloudComponent', () => {
});
});
describe('When roles defined', () => {
let checkGroupHasRoleSpy: jasmine.Spy;
beforeEach(() => {
component.roles = ['mock-role-1', 'mock-role-2'];
spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of(mockIdentityGroups));
checkGroupHasRoleSpy = spyOn(identityGroupService, 'checkGroupHasRole').and.returnValue(of(true));
fixture.detectChanges();
});
it('should filter if groups has any specified role', (done) => {
fixture.detectChanges();
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-row"]')).length).toEqual(5);
expect(checkGroupHasRoleSpy).toHaveBeenCalledTimes(5);
done();
});
});
it('should not filter groups if group does not have any specified role', (done) => {
fixture.detectChanges();
checkGroupHasRoleSpy.and.returnValue(of(false));
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-row"]')).length).toEqual(0);
expect(fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-group-no-results"]')).length).toEqual(1);
expect(checkGroupHasRoleSpy).toHaveBeenCalled();
done();
});
});
it('should not call checkGroupHasRole service when roles are not specified', (done) => {
component.roles = [];
fixture.detectChanges();
const input = getElement<HTMLInputElement>('input');
input.focus();
input.value = 'M';
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(checkGroupHasRoleSpy).not.toHaveBeenCalled();
done();
});
});
});
describe('Single Mode with pre-selected groups', () => {
const changes = new SimpleChange(null, mockIdentityGroups, false);
const changes = new SimpleChange(null, mockFoodGroups, false);
beforeEach(() => {
component.mode = 'single';
component.preSelectGroups = mockIdentityGroups;
component.preSelectGroups = mockFoodGroups;
component.ngOnChanges({ preSelectGroups: changes });
fixture.detectChanges();
});
@ -513,16 +222,16 @@ describe('GroupCloudComponent', () => {
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}`);
expect(chips[0].attributes['data-automation-id']).toEqual(`adf-cloud-group-chip-${mockVegetableAubergine.name}`);
});
});
describe('Multiple Mode with pre-selected groups', () => {
const change = new SimpleChange(null, mockIdentityGroups, false);
const change = new SimpleChange(null, mockFoodGroups, false);
beforeEach(() => {
component.mode = 'multiple';
component.preSelectGroups = mockIdentityGroups;
component.preSelectGroups = mockFoodGroups;
component.ngOnChanges({ preSelectGroups: change });
fixture.detectChanges();
});
@ -533,196 +242,145 @@ describe('GroupCloudComponent', () => {
component.ngOnChanges({ preSelectGroups: change });
fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
expect(chips.length).toBe(5);
expect(chips.length).toBe(2);
});
it('should removeGroup and changedGroups emit when a selected group is removed', (done) => {
it('should removeGroup and changedGroups emit when a selected group is removed', async () => {
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.selectedGroups.indexOf({
id: groupToRemove.id,
name: groupToRemove.name,
path: groupToRemove.path
})).toEqual(-1);
done();
});
await fixture.whenStable();
expect(removeGroupEmitterSpy).toHaveBeenCalledWith(mockVegetableAubergine);
expect(changedGroupsEmitterSpy).toHaveBeenCalledWith([mockMeatChicken]);
expect(component.selectedGroups.indexOf({
id: mockMeatChicken.id,
name: mockMeatChicken.name
})).toEqual(-1);
});
});
});
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();
component.preSelectGroups = [
{ id: mockIdentityGroups[0].id, name: mockIdentityGroups[0].name, readonly: true },
{ id: mockIdentityGroups[1].id, name: mockIdentityGroups[1].name, readonly: true }
];
const change = new SimpleChange(null, component.preSelectGroups, false);
it('Should not show remove icon for pre-selected groups if readonly property set to true', async () => {
component.mode = 'multiple';
component.ngOnChanges({ preSelectGroups: change });
component.preSelectGroups = [
{ id: mockVegetableAubergine.id, name: mockVegetableAubergine.name, readonly: true },
mockMeatChicken
];
const changes = new SimpleChange(null, [{ name: mockVegetableAubergine.name }], false);
component.ngOnChanges({ preSelectGroups: changes });
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip');
const removeIcon = getElement('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]');
await fixture.whenStable();
expect(chipList.length).toBe(2);
expect(component.preSelectGroups[0].readonly).toBeTruthy();
expect(component.preSelectGroups[1].readonly).toBeTruthy();
expect(removeIcon).toBeNull();
const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip');
expect(chipList.length).toBe(2);
const removeIconAubergine = getElement(`[data-automation-id="adf-cloud-group-chip-remove-icon-${mockVegetableAubergine.name}"]`);
expect(removeIconAubergine).toBeNull();
const removeIconPepper = getElement(`[data-automation-id="adf-cloud-group-chip-remove-icon-${mockMeatChicken.name}"]`);
expect(removeIconPepper).not.toBeNull();
done();
});
});
it('Should be able to remove preselected groups if readonly property set to false', (done) => {
fixture.detectChanges();
component.preSelectGroups = [
{ id: mockIdentityGroups[0].id, name: mockIdentityGroups[0].name, readonly: false },
{ id: mockIdentityGroups[1].id, name: mockIdentityGroups[1].name, readonly: false }
];
it('Should be able to remove preselected groups if readonly property set to false', async () => {
component.mode = 'multiple';
component.preSelectGroups = mockFoodGroups;
const change = new SimpleChange(null, component.preSelectGroups, false);
component.mode = 'multiple';
component.ngOnChanges({ preSelectGroups: change });
const removeGroupSpy = spyOn(component.removeGroup, 'emit');
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
fixture.whenStable();
fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
const removeIcon = getElement('[data-automation-id="adf-cloud-group-chip-remove-icon-Mock Group 1"]');
const chipList = fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip');
expect(chipList.length).toBe(2);
expect(chips.length).toBe(2);
expect(component.preSelectGroups[0].readonly).toBe(false, 'Removable');
expect(component.preSelectGroups[1].readonly).toBe(false, 'Removable');
const removeIcon = getElement(`[data-automation-id="adf-cloud-group-chip-remove-icon-${mockMeatChicken.name}"]`);
removeIcon.click();
fixture.detectChanges();
removeIcon.click();
fixture.detectChanges();
expect(removeGroupSpy).toHaveBeenCalled();
expect(fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip').length).toBe(1);
done();
});
});
describe('Component readonly mode', () => {
const change = new SimpleChange(null, mockIdentityGroups, false);
it('should chip list be disabled and show one single chip - single mode', () => {
component.mode = 'single';
component.readOnly = true;
component.preSelectGroups = mockIdentityGroups;
component.ngOnChanges({ preSelectGroups: change });
fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
const chipList = getElement('mat-chip-list');
expect(chips).toBeDefined();
expect(chipList).toBeDefined();
expect(chips.length).toBe(1);
expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
});
it('should chip list be disabled and show all the chips - multiple mode', () => {
component.mode = 'multiple';
component.readOnly = true;
component.preSelectGroups = mockIdentityGroups;
component.ngOnChanges({ preSelectGroups: change });
fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
const chipList = getElement('mat-chip-list');
expect(chips).toBeDefined();
expect(chipList).toBeDefined();
expect(chips.length).toBe(5);
expect(chipList.attributes['ng-reflect-disabled'].value).toEqual('true');
});
});
describe('Preselected groups and validation enabled', () => {
it('should check validation only for the first group and emit warning when group is invalid - single mode', (done) => {
spyOn(identityGroupService, 'findGroupsByName').and.returnValue(of([]));
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 = [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(of(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 = [mockIdentityGroups[0], mockIdentityGroups[1]];
component.ngOnChanges({
preSelectGroups: new SimpleChange(null, [mockIdentityGroups[0], mockIdentityGroups[1]], false)
});
});
expect(removeGroupSpy).toHaveBeenCalled();
expect(fixture.nativeElement.querySelectorAll('mat-chip-list mat-chip').length).toBe(1);
});
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 }]);
const duplicatedGroups = [ mockMeatChicken, mockMeatChicken];
expect(component.removeDuplicatedGroups(duplicatedGroups)).toEqual([mockMeatChicken]);
});
});
describe('Preselected groups and validation enabled', () => {
beforeEach(() => {
spyOn(identityGroupService, 'search').and.throwError('Invalid group');
component.validate = true;
component.preSelectGroups = mockFoodGroups;
});
it('should check validation only for the first group and emit warning when group is invalid - single mode', async () => {
component.mode = 'single';
component.ngOnChanges({ preSelectGroups: new SimpleChange(null, [mockVegetableAubergine, mockMeatChicken], false) });
fixture.detectChanges();
await fixture.whenStable();
expect(component.invalidGroups.length).toEqual(1);
});
it('should check validation for all the groups and emit warning - multiple mode', async () => {
component.mode = 'multiple';
component.ngOnChanges({ preSelectGroups: new SimpleChange(null, [mockVegetableAubergine, mockMeatChicken], false) });
fixture.detectChanges();
await fixture.whenStable();
expect(component.invalidGroups.length).toEqual(2);
});
});
describe('Component readonly mode', () => {
const change = new SimpleChange(null, mockFoodGroups, false);
it('should chip list be disabled and show one single chip - single mode', () => {
component.mode = 'single';
component.readOnly = true;
component.preSelectGroups = mockFoodGroups;
component.ngOnChanges({ preSelectGroups: change });
fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
const chipList = getElement('mat-chip-list');
expect(chips).toBeDefined();
expect(chipList).toBeDefined();
expect(chips.length).toBe(1);
expect(chipList.attributes['ng-reflect-disabled']?.value).toEqual('true');
});
it('should chip list be disabled and show all the chips - multiple mode', () => {
component.mode = 'multiple';
component.readOnly = true;
component.preSelectGroups = mockFoodGroups;
component.ngOnChanges({ preSelectGroups: change });
fixture.detectChanges();
const chips = fixture.debugElement.queryAll(By.css('mat-chip'));
const chipList = getElement('mat-chip-list');
expect(chips).toBeDefined();
expect(chipList).toBeDefined();
expect(chips.length).toBe(2);
expect(chipList.attributes['ng-reflect-disabled']?.value).toEqual('true');
});
});
});

View File

@ -16,10 +16,16 @@
*/
import { Meta, moduleMetadata, Story } from '@storybook/angular';
import { IdentityGroupService, mockIdentityGroups, IdentityGroupServiceMock } from '@alfresco/adf-core';
import { GroupCloudModule } from '../group-cloud.module';
import { GroupCloudComponent } from './group-cloud.component';
import { ProcessServicesCloudStoryModule } from '../../testing/process-services-cloud-story.module';
import { IdentityGroupService } from '../services/identity-group.service';
import {
IdentityGroupServiceMock,
mockFoodGroups,
mockMeatChicken,
mockVegetableAubergine
} from '../mock/group-cloud.mock';
export default {
component: GroupCloudComponent,
@ -37,15 +43,6 @@ export default {
mode: {
options: ['single', 'multiple'],
control: 'radio'
},
roles: {
options: ['empty', 'user', 'admin'],
control: 'radio',
mapping: {
empty: [],
user: ['MOCK-USER-ROLE'],
admin: ['MOCK-ADMIN-ROLE']
}
}
}
} as Meta;
@ -60,7 +57,6 @@ primary.args = {
mode: 'single',
preSelectGroups: [],
readOnly: false,
roles: [],
title: 'Groups',
validate: false
};
@ -70,7 +66,7 @@ validPreselectedGroups.args = {
...primary.args,
validate: true,
mode: 'multiple',
preSelectGroups: mockIdentityGroups
preSelectGroups: mockFoodGroups
};
export const mandatoryPreselectedGroups = template.bind({});
@ -78,9 +74,7 @@ mandatoryPreselectedGroups.args = {
...primary.args,
validate: true,
mode: 'multiple',
preSelectGroups: [{ id: 'mock-group-id-1', name: 'Mock Group 1', path: '/mock', subGroups: [], readonly: true },
{ id: 'mock-group-id-2', name: 'Mock Group 2', path: '', subGroups: [] },
{ id: 'mock-group-id-3', name: 'Mock Group 3', path: '', subGroups: [], readonly: true }]
preSelectGroups: [mockVegetableAubergine, { ...mockMeatChicken, readonly: true }]
};
export const invalidPreselectedGroups = template.bind({});
@ -88,13 +82,7 @@ invalidPreselectedGroups.args = {
...primary.args,
validate: true,
mode: 'multiple',
preSelectGroups: [{ id: 'invalid-group', name: 'invalid groups' }]
};
export const adminRoleGroups = template.bind({});
adminRoleGroups.args = {
...primary.args,
roles: 'admin'
preSelectGroups: [{ id: 'invalid-group', name: 'Invalid Group' }]
};
export const invalidOrEmptyAppName = template.bind({});

View File

@ -27,14 +27,17 @@ import {
SimpleChanges,
OnChanges,
OnDestroy,
SimpleChange
Inject
} from '@angular/core';
import { FormControl } from '@angular/forms';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
import { distinctUntilChanged, switchMap, mergeMap, filter, tap, map, takeUntil, debounceTime } from 'rxjs/operators';
import { IdentityGroupModel, IdentityGroupService, LogService } from '@alfresco/adf-core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { distinctUntilChanged, switchMap, mergeMap, filter, tap, takeUntil, debounceTime } from 'rxjs/operators';
import { LogService } from '@alfresco/adf-core';
import { ComponentSelectionMode } from '../../types';
import { IdentityGroupModel } from '../models/identity-group.model';
import { IdentityGroupServiceInterface } from '../services/identity-group.service.interface';
import { IDENTITY_GROUP_SERVICE_TOKEN } from '../services/identity-group-service.token';
@Component({
selector: 'adf-cloud-group',
@ -125,7 +128,6 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
searchGroups$ = new BehaviorSubject<IdentityGroupModel[]>(this.searchGroups);
subscriptAnimationState: string = 'enter';
clientId: string;
isFocused: boolean;
touched: boolean = false;
@ -135,12 +137,14 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
validationLoading = false;
searchLoading = false;
typingUniqueValueNotEmpty$: Observable<any>;
constructor(
private identityGroupService: IdentityGroupService,
@Inject(IDENTITY_GROUP_SERVICE_TOKEN)
private identityGroupService: IdentityGroupServiceInterface,
private logService: LogService) {}
ngOnInit(): void {
this.loadClientId();
this.initSearch();
}
@ -157,41 +161,52 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
this.invalidGroups = [];
}
}
if (changes.appName && this.isAppNameChanged(changes.appName)) {
this.loadClientId();
}
}
private isAppNameChanged(change: SimpleChange): boolean {
return change
&& change.previousValue !== change.currentValue
&& this.appName
&& this.appName.length > 0;
private initSearch(): void {
this.initializeStream();
this.typingUniqueValueNotEmpty$.pipe(
switchMap((name: string) =>
this.identityGroupService.search(name, { roles: this.roles, withinApplication: this.appName })
),
mergeMap((groups: IdentityGroupModel[]) => {
this.resetSearchGroups();
this.searchLoading = false;
return groups;
}),
filter(group => !this.isGroupAlreadySelected(group)),
takeUntil(this.onDestroy$)
).subscribe((searchedGroup: IdentityGroupModel) => {
this.searchGroups.push(searchedGroup);
this.searchGroups$.next(this.searchGroups);
});
}
private async loadClientId(): Promise<void> {
this.clientId = await this.identityGroupService.getClientIdByApplicationName(this.appName).toPromise();
private initializeStream() {
const typingValueFromControl$ = this.searchGroupsControl.valueChanges;
if (this.clientId) {
this.searchGroupsControl.enable();
}
}
initSearch(): void {
this.searchGroupsControl.valueChanges.pipe(
filter((value) => {
const typingValueTypeSting$ = typingValueFromControl$.pipe(
filter(value => {
this.searchLoading = true;
return typeof value === 'string';
}),
})
);
const typingValueHandleErrorMessage$ = typingValueTypeSting$.pipe(
tap((value: string) => {
if (value) {
this.setTypingError();
}
}),
})
);
const typingValueDebouncedUnique$ = typingValueHandleErrorMessage$.pipe(
debounceTime(500),
distinctUntilChanged(),
tap((value) => {
distinctUntilChanged()
);
this.typingUniqueValueNotEmpty$ = typingValueDebouncedUnique$.pipe(
tap((value: string) => {
if (value.trim()) {
this.searchedValue = value;
} else {
@ -199,42 +214,8 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
this.searchGroupsControl.markAsUntouched();
}
}),
tap(() => this.resetSearchGroups()),
switchMap((name: string) =>
this.identityGroupService.findGroupsByName({ name: name.trim() })
),
mergeMap((groups) => {
this.resetSearchGroups();
this.searchLoading = false;
return groups;
}),
filter(group => !this.isGroupAlreadySelected(group)),
mergeMap(group => {
if (this.appName) {
return this.checkGroupHasAccess(group.id).pipe(
mergeMap(
hasRole => hasRole ? of(group) : of()
)
);
} else if (this.hasRoles()) {
return this.filterGroupsByRoles(group);
} else {
return of(group);
}
}),
takeUntil(this.onDestroy$)
).subscribe(searchedGroup => {
this.searchGroups.push(searchedGroup);
this.searchGroups$.next(this.searchGroups);
});
}
checkGroupHasAccess(groupId: string): Observable<boolean> {
if (this.hasRoles()) {
return this.identityGroupService.checkGroupHasAnyClientAppRole(groupId, this.clientId, this.roles);
} else {
return this.identityGroupService.checkGroupHasClientApp(groupId, this.clientId);
}
tap(() => this.resetSearchGroups())
);
}
private isGroupAlreadySelected(group: IdentityGroupModel): boolean {
@ -246,8 +227,8 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
return false;
}
async searchGroup(name: string): Promise<IdentityGroupModel> {
return (await this.identityGroupService.findGroupsByName({ name }).toPromise())[0];
private async searchGroup(name: string): Promise<IdentityGroupModel> {
return (await this.identityGroupService.search(name).toPromise())[0];
}
private getPreselectedGroups(): IdentityGroupModel[] {
@ -258,7 +239,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
}
}
async validatePreselectGroups(): Promise<any> {
private async validatePreselectGroups(): Promise<any> {
this.invalidGroups = [];
for (const group of this.getPreselectedGroups()) {
@ -276,7 +257,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
this.checkPreselectValidationErrors();
}
checkPreselectValidationErrors(): void {
private checkPreselectValidationErrors(): void {
this.invalidGroups = this.removeDuplicatedGroups(this.invalidGroups);
if (this.invalidGroups.length > 0) {
@ -289,7 +270,7 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
});
}
generateInvalidGroupsMessage(): void {
private generateInvalidGroupsMessage(): void {
this.validateGroupsMessage = '';
this.invalidGroups.forEach((invalidGroup: IdentityGroupModel, index) => {
@ -317,13 +298,6 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
}
}
filterGroupsByRoles(group: IdentityGroupModel): Observable<IdentityGroupModel> {
return this.identityGroupService.checkGroupHasRole(group.id, this.roles).pipe(
map((hasRole: boolean) => ({ hasRole, group })),
filter((filteredGroup: { hasRole: boolean; group: IdentityGroupModel }) => filteredGroup.hasRole),
map((filteredGroup: { hasRole: boolean; group: IdentityGroupModel }) => filteredGroup.group));
}
onSelect(group: IdentityGroupModel): void {
if (group) {
this.selectGroup.emit(group);
@ -365,6 +339,19 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
}
}
private isPreselectedGroupInvalid(preselectedGroup: IdentityGroupModel, validatedGroup: IdentityGroupModel): boolean {
if (validatedGroup && validatedGroup.name !== undefined) {
return preselectedGroup.name !== validatedGroup.name;
} else {
return true;
}
}
removeDuplicatedGroups(groups: IdentityGroupModel[]): IdentityGroupModel[] {
return groups.filter((group, index, self) =>
index === self.findIndex((auxGroup) => group.id === auxGroup.id && group.name === auxGroup.name));
}
private groupChipsCtrlValue(value: string) {
this.groupChipsCtrl.setValue(value);
this.groupChipsCtrl.markAsDirty();
@ -392,43 +379,18 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
this.searchGroups$.next(this.searchGroups);
}
isPreselectedGroupInvalid(preselectedGroup: IdentityGroupModel, validatedGroup: IdentityGroupModel): boolean {
if (validatedGroup && validatedGroup.name !== undefined) {
return preselectedGroup.name !== validatedGroup.name;
} else {
return true;
}
}
isSingleMode(): boolean {
return this.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;
private isSingleMode(): boolean {
return this.mode === 'single';
}
isReadonly(): boolean {
return this.readOnly || this.isSingleSelectionReadonly();
}
isMultipleMode(): boolean {
private isMultipleMode(): boolean {
return this.mode === 'multiple';
}
getDisplayName(group: IdentityGroupModel): string {
return group ? group.name : '';
}
removeDuplicatedGroups(groups: IdentityGroupModel[]): IdentityGroupModel[] {
return groups.filter((group, index, self) =>
index === self.findIndex((auxGroup) => group.id === auxGroup.id && group.name === auxGroup.name));
}
private hasPreSelectGroups(): boolean {
return this.preSelectGroups && this.preSelectGroups.length > 0;
}
@ -457,10 +419,6 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
&& changes.preSelectGroups.currentValue.length === 0;
}
private hasRoles(): boolean {
return this.roles && this.roles.length > 0;
}
private setTypingError(): void {
this.searchGroupsControl.setErrors({
searchTypingError: true,
@ -468,6 +426,18 @@ export class GroupCloudComponent implements OnInit, OnChanges, OnDestroy {
});
}
hasPreselectError(): boolean {
return this.invalidGroups && this.invalidGroups.length > 0;
}
isReadonly(): boolean {
return this.readOnly || this.isSingleSelectionReadonly();
}
getDisplayName(group: IdentityGroupModel): string {
return group ? group.name : '';
}
hasError(): boolean {
return !!this.searchGroupsControl.errors;
}

View File

@ -24,6 +24,8 @@ import { CoreModule } from '@alfresco/adf-core';
import { MaterialModule } from '../material.module';
import { GroupCloudComponent } from './components/group-cloud.component';
import { InitialGroupNamePipe } from './pipe/group-initial.pipe';
import { IDENTITY_GROUP_SERVICE_TOKEN } from './services/identity-group-service.token';
import { IdentityGroupService } from './services/identity-group.service';
@NgModule({
imports: [
@ -35,6 +37,9 @@ import { InitialGroupNamePipe } from './pipe/group-initial.pipe';
CoreModule
],
declarations: [GroupCloudComponent, InitialGroupNamePipe],
providers: [
{ provide: IDENTITY_GROUP_SERVICE_TOKEN, useExisting: IdentityGroupService }
],
exports: [GroupCloudComponent, InitialGroupNamePipe]
})
export class GroupCloudModule { }

View File

@ -0,0 +1,47 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Observable, EMPTY, of } from 'rxjs';
import { IdentityGroupModel } from '../models/identity-group.model';
import { IdentityGroupFilterInterface } from '../services/identity-group-filter.interface';
import { IdentityGroupServiceInterface } from '../services/identity-group.service.interface';
export const mockVegetableAubergine: IdentityGroupModel = { id: 'aubergine', name: 'Vegetable Aubergine'};
export const mockMeatChicken: IdentityGroupModel = { id: 'chicken', name: 'Meat Chicken'};
export const mockFoodGroups = [ mockVegetableAubergine, mockMeatChicken ];
export const mockSearchGroupEmptyFilters: IdentityGroupFilterInterface = {
roles: [],
withinApplication: ''
};
@Injectable({
providedIn: 'root'
})
export class IdentityGroupServiceMock implements IdentityGroupServiceInterface {
search(name: string, _filters?: IdentityGroupFilterInterface): Observable<IdentityGroupModel[]> {
if (name.trim() === '') {
return EMPTY;
}
return of(mockFoodGroups.filter(group =>
group.name.toUpperCase().includes(name.toUpperCase())
));
}
}

View File

@ -0,0 +1,57 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HttpErrorResponse } from '@angular/common/http';
import { throwError } from 'rxjs';
import { IdentityGroupModel } from '../models/identity-group.model';
import { IdentityGroupFilterInterface } from '../services/identity-group-filter.interface';
export const mockSearchGroupByRoles: IdentityGroupFilterInterface = {
roles: ['fake-role-1', 'fake-role-2'],
withinApplication: ''
};
export const mockSearchGroupByRolesAndApp: IdentityGroupFilterInterface = {
roles: ['fake-role-1', 'fake-role-2'],
withinApplication: 'fake-app-name'
};
export const mockSearchGroupByApp: IdentityGroupFilterInterface = {
roles: [],
withinApplication: 'fake-app-name'
};
export function oAuthMockApiWithIdentityGroups(groups: IdentityGroupModel[]) {
return {
oauth2Auth: {
callCustomApi: () => Promise.resolve(groups)
},
reply: jasmine.createSpy('reply')
};
}
const errorResponse = new HttpErrorResponse({
error: 'Mock Error',
status: 404, statusText: 'Not Found'
});
export const oAuthMockApiWithError = {
oauth2Auth: {
callCustomApi: () => throwError(errorResponse)
},
reply: jasmine.createSpy('reply')
};

View File

@ -0,0 +1,22 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface IdentityGroupModel {
id?: string;
name: string;
readonly?: boolean;
}

View File

@ -15,8 +15,8 @@
* limitations under the License.
*/
import { IdentityGroupModel } from '../models/identity-group.model';
import { InitialGroupNamePipe } from './group-initial.pipe';
import { IdentityGroupModel } from '@alfresco/adf-core';
describe('InitialGroupNamePipe', () => {

View File

@ -16,7 +16,7 @@
*/
import { Pipe, PipeTransform } from '@angular/core';
import { IdentityGroupModel } from '@alfresco/adf-core';
import { IdentityGroupModel } from '../models/identity-group.model';
@Pipe({
name: 'groupNameInitial'

View File

@ -17,4 +17,7 @@
export * from './components/group-cloud.component';
export * from './pipe/group-initial.pipe';
export * from './models/identity-group.model';
export * from './group-cloud.module';
export * from './services/identity-group.service';
export * from './services/identity-group-service.token';

View File

@ -0,0 +1,21 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface IdentityGroupFilterInterface {
roles?: string[];
withinApplication?: string;
}

View File

@ -0,0 +1,21 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { InjectionToken } from '@angular/core';
import { IdentityGroupServiceInterface } from './identity-group.service.interface';
export const IDENTITY_GROUP_SERVICE_TOKEN = new InjectionToken<IdentityGroupServiceInterface>('IdentityGroup');

View File

@ -0,0 +1,24 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Observable } from 'rxjs';
import { IdentityGroupModel } from '../models/identity-group.model';
import { IdentityGroupFilterInterface } from './identity-group-filter.interface';
export interface IdentityGroupServiceInterface {
search(name: string, filters?: IdentityGroupFilterInterface): Observable<IdentityGroupModel[]>;
}

View File

@ -0,0 +1,181 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { AlfrescoApiService, setupTestBed } from '@alfresco/adf-core';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { IdentityGroupService } from './identity-group.service';
import {
mockSearchGroupByApp,
mockSearchGroupByRoles,
mockSearchGroupByRolesAndApp,
oAuthMockApiWithError,
oAuthMockApiWithIdentityGroups
} from '../mock/identity-group.service.mock';
import { mockFoodGroups } from '../mock/group-cloud.mock';
describe('IdentityGroupService', () => {
let service: IdentityGroupService;
let alfrescoApiService: AlfrescoApiService;
setupTestBed({
imports: [
TranslateModule.forRoot(),
ProcessServiceCloudTestingModule
]
});
beforeEach(() => {
service = TestBed.inject(IdentityGroupService);
alfrescoApiService = TestBed.inject(AlfrescoApiService);
});
describe('Search', () => {
it('should fetch groups', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityGroups(mockFoodGroups));
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake').subscribe(
res => {
expect(res).toBeDefined();
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake'
});
done();
}
);
});
it('should not fetch groups if error occurred', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithError);
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake')
.subscribe(
() => {
fail('expected an error, not groups');
},
(error) => {
expect(searchSpy).toHaveBeenCalled();
expect(error.status).toEqual(404);
expect(error.statusText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
it('should fetch groups by roles', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityGroups(mockFoodGroups));
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake', mockSearchGroupByRoles).subscribe(
res => {
expect(res).toBeDefined();
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake',
role: 'fake-role-1,fake-role-2'
});
done();
}
);
});
it('should not fetch groups by roles if error occurred', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithError);
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake', mockSearchGroupByRoles)
.subscribe(
() => {
fail('expected an error, not groups');
},
(error) => {
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake',
role: 'fake-role-1,fake-role-2'
});
expect(error.status).toEqual(404);
expect(error.statusText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
it('should fetch groups within app', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityGroups(mockFoodGroups));
service.search('fake', mockSearchGroupByApp).subscribe(
res => {
expect(res).toBeDefined();
expect(service.queryParams).toEqual({
search: 'fake',
application: 'fake-app-name'
});
done();
}
);
});
it('should fetch groups within app with roles', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityGroups(mockFoodGroups));
service.search('fake', mockSearchGroupByRolesAndApp).subscribe(
res => {
expect(res).toBeDefined();
expect(service.queryParams).toEqual({
search: 'fake',
application: 'fake-app-name',
role: 'fake-role-1,fake-role-2'
});
done();
}
);
});
it('should not fetch groups within app if error occurred', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithError);
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake', mockSearchGroupByApp)
.subscribe(
() => {
fail('expected an error, not groups');
},
(error) => {
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake',
application: 'fake-app-name'
});
expect(error.status).toEqual(404);
expect(error.statusText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
});
});

View File

@ -0,0 +1,111 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { AppConfigService, OAuth2Service } from '@alfresco/adf-core';
import { EMPTY, Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { IdentityGroupServiceInterface } from './identity-group.service.interface';
import { IdentityGroupFilterInterface } from './identity-group-filter.interface';
import { IdentityGroupModel } from '../models/identity-group.model';
const IDENTITY_MICRO_SERVICE_INGRESS = 'modeling-service';
@Injectable({ providedIn: 'root' })
export class IdentityGroupService implements IdentityGroupServiceInterface {
queryParams: { search: string; application?: string; roles?: string [] };
constructor(
private oAuth2Service: OAuth2Service,
private appConfigService: AppConfigService
) {}
public search(name: string, filters?: IdentityGroupFilterInterface): Observable<IdentityGroupModel[]> {
if (name.trim() === '') {
return EMPTY;
} else if (filters?.withinApplication) {
return this.searchGroupsWithinApp(name, filters.withinApplication, filters?.roles);
} else if (filters?.roles?.length > 0) {
return this.searchGroupsWithGlobalRoles(name, filters.roles);
} else {
return this.searchGroupsByName(name);
}
}
private searchGroupsByName(name: string): Observable<IdentityGroupModel[]> {
this.buildQueryParam(name);
return this.invokeIdentityGroupApi().pipe(
catchError((err) => this.handleError(err))
);
}
private searchGroupsWithGlobalRoles(name: string, roles: string []): Observable<IdentityGroupModel[]> {
this.buildQueryParam(name, roles);
return this.invokeIdentityGroupApi().pipe(
catchError((err) => this.handleError(err))
);
}
private searchGroupsWithinApp(name: string, applicationName: string, roles?: string []): Observable<IdentityGroupModel[]> {
this.buildQueryParam(name, roles, applicationName);
return this.invokeIdentityGroupApi().pipe(
catchError((err) => this.handleError(err))
);
}
private invokeIdentityGroupApi(): Observable<IdentityGroupModel[]> {
const url = `${this.identityHost}/${IDENTITY_MICRO_SERVICE_INGRESS}/v1/identity/groups`;
return this.oAuth2Service.get({ url, queryParams: this.queryParams });
}
private buildQueryParam(name: string, roles?: string [], applicationName?: string) {
this.queryParams = { search: name };
this.addOptionalValueToQueryParam('application', applicationName);
this.addOptionalCommaValueToQueryParam('role', roles);
}
private addOptionalCommaValueToQueryParam(key: string, values: string []) {
if (values?.length > 0) {
const valuesNotEmpty = this.filterOutEmptyValue(values);
if (valuesNotEmpty?.length > 0) {
this.queryParams[key] = valuesNotEmpty.join(',');
}
}
}
private addOptionalValueToQueryParam(key: string, value: string) {
if (value?.trim()) {
this.queryParams[key] = value;
}
}
private filterOutEmptyValue(roles: string []): string [] {
return roles.filter( role => role.trim() ? true : false);
}
private handleError(error: any) {
return throwError(error || 'Server error');
}
private get identityHost(): string {
return `${this.appConfigService.get('bpmHost')}`;
}
}

View File

@ -16,7 +16,8 @@
*/
import { Pagination } from '@alfresco/js-api';
import { IdentityGroupModel, IdentityUserModel } from '@alfresco/adf-core';
import { IdentityGroupModel } from '../group/models/identity-group.model';
import { IdentityUserModel } from '../people/models/identity-user.model';
import { ProcessInstanceVariable } from './process-instance-variable.model';
export class TaskCloudNodePaging {

View File

@ -16,10 +16,17 @@
*/
import { Meta, moduleMetadata, Story } from '@storybook/angular';
import { IdentityUserService, IdentityUserServiceMock, mockIdentityUsers } from '@alfresco/adf-core';
import { PeopleCloudComponent } from './people-cloud.component';
import { PeopleCloudModule } from '../people-cloud.module';
import { ProcessServicesCloudStoryModule } from '../../testing/process-services-cloud-story.module';
import { IdentityUserService } from '../services/identity-user.service';
import {
IdentityUserServiceMock,
mockFoodUsers,
mockKielbasaSausage,
mockShepherdsPie,
mockYorkshirePudding
} from '../mock/people-cloud.mock';
export default {
component: PeopleCloudComponent,
@ -28,7 +35,7 @@ export default {
moduleMetadata({
imports: [ProcessServicesCloudStoryModule, PeopleCloudModule],
providers: [
{ provide: IdentityUserService, useClass: IdentityUserServiceMock }
{ provide: IdentityUserService, useClass: IdentityUserServiceMock}
]
})
],
@ -37,15 +44,6 @@ export default {
mode: {
options: ['single', 'multiple'],
control: 'radio'
},
roles: {
options: ['empty', 'user', 'admin'],
control: 'radio',
mapping: {
empty: [],
user: ['MOCK-USER-ROLE'],
admin: ['MOCK-ADMIN-ROLE']
}
}
}
} as Meta;
@ -61,7 +59,6 @@ primary.args = {
mode: 'single',
preSelectUsers: [],
readOnly: false,
roles: [],
title: 'Users',
validate: false
};
@ -71,7 +68,7 @@ validPreselectedUsers.args = {
...primary.args,
validate: true,
mode: 'multiple',
preSelectUsers: mockIdentityUsers
preSelectUsers: mockFoodUsers
};
export const mandatoryPreselectedUsers = template.bind({});
@ -79,8 +76,7 @@ mandatoryPreselectedUsers.args = {
...primary.args,
validate: true,
mode: 'multiple',
preSelectUsers: [{ id: 'mock-user-id-1', username: 'userName1', firstName: 'first-name-1', lastName: 'last-name-1', email: 'abc@xyz.com', readonly: true },
{ id: 'mock-user-id-2', username: 'userName2', firstName: 'first-name-2', lastName: 'last-name-2', email: 'abcd@xyz.com' }]
preSelectUsers: [{ ...mockKielbasaSausage, readonly: true }, mockShepherdsPie]
};
export const invalidPreselectedUsers = template.bind({});
@ -88,28 +84,22 @@ invalidPreselectedUsers.args = {
...primary.args,
validate: true,
mode: 'multiple',
preSelectUsers: [{ id: 'invalid-user', username: 'invalid user', firstName: 'invalid', lastName: 'user', email: 'invalid@xyz.com' }]
preSelectUsers: [{ id: 'invalid-user', username: 'Invalid User', firstName: 'Invalid', lastName: 'User', email: 'invalid@xyz.com' }]
};
export const excludedUsers = template.bind({});
excludedUsers.args = {
...primary.args,
excludedUsers: [
{ id: 'mock-user-id-2' },
{ id: 'mock-user-id-3' }
mockKielbasaSausage,
mockYorkshirePudding
]
};
export const adminRoleUser = template.bind({});
adminRoleUser.args = {
...primary.args,
roles: 'admin'
};
export const noUsers = template.bind({});
noUsers.args = {
...primary.args,
excludedUsers: mockIdentityUsers
excludedUsers: mockFoodUsers
};
export const invalidOrEmptyAppName = template.bind({});

View File

@ -26,18 +26,21 @@ import {
SimpleChanges,
OnChanges,
OnDestroy,
ViewChild, ElementRef, SimpleChange
ViewChild,
ElementRef,
Inject
} from '@angular/core';
import { Observable, of, BehaviorSubject, Subject } from 'rxjs';
import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, map, takeUntil } from 'rxjs/operators';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { switchMap, debounceTime, distinctUntilChanged, mergeMap, tap, filter, takeUntil } from 'rxjs/operators';
import {
FullNamePipe,
IdentityUserModel,
IdentityUserService,
LogService
} from '@alfresco/adf-core';
import { trigger, state, style, transition, animate } from '@angular/animations';
import { ComponentSelectionMode } from '../../types';
import { IdentityUserModel } from '../models/identity-user.model';
import { IdentityUserServiceInterface } from '../services/identity-user.service.interface';
import { IDENTITY_USER_SERVICE_TOKEN } from '../services/identity-user-service.token';
@Component({
selector: 'adf-cloud-people',
@ -139,15 +142,14 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild('userInput')
private userInput: ElementRef<HTMLInputElement>;
private _searchUsers: IdentityUserModel[] = [];
private searchUsers: IdentityUserModel[] = [];
private onDestroy$ = new Subject<boolean>();
selectedUsers: IdentityUserModel[] = [];
invalidUsers: IdentityUserModel[] = [];
searchUsers$ = new BehaviorSubject<IdentityUserModel[]>(this._searchUsers);
searchUsers$ = new BehaviorSubject<IdentityUserModel[]>(this.searchUsers);
subscriptAnimationState: string = 'enter';
clientId: string;
isFocused: boolean;
touched: boolean = false;
@ -157,20 +159,19 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
validationLoading = false;
searchLoading = false;
typingUniqueValueNotEmpty$: Observable<string>;
constructor(
private identityUserService: IdentityUserService,
@Inject(IDENTITY_USER_SERVICE_TOKEN)
private identityUserService: IdentityUserServiceInterface,
private logService: LogService) {}
ngOnInit(): void {
this.loadClientId();
this.initSearch();
}
ngOnChanges(changes: SimpleChanges): void {
if (this.valueChanged(changes.preSelectUsers)
|| this.valueChanged(changes.mode)
|| this.valueChanged(changes.validate)
) {
if (this.hasPreselectedUsersChanged(changes) || this.hasModeChanged(changes) || this.isValidationChanged(changes)) {
if (this.hasPreSelectUsers()) {
this.loadPreSelectUsers();
} else if (this.hasPreselectedUsersCleared(changes)) {
@ -182,32 +183,51 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
this.invalidUsers = [];
}
}
if (changes.appName && this.isAppNameChanged(changes.appName)) {
this.loadClientId();
}
}
private async loadClientId(): Promise<void> {
this.clientId = await this.identityUserService.getClientIdByApplicationName(this.appName).toPromise();
if (this.clientId) {
this.searchUserCtrl.enable();
}
}
private initSearch(): void {
this.searchUserCtrl.valueChanges.pipe(
filter((value) => {
this.initializeStream();
this.typingUniqueValueNotEmpty$.pipe(
switchMap((name: string) =>
this.identityUserService.search(name, { roles: this.roles, withinApplication: this.appName, groups: this.groupsRestriction })
),
mergeMap((users: IdentityUserModel[]) => {
this.resetSearchUsers();
this.searchLoading = false;
return users;
}),
filter(user => !this.isUserAlreadySelected(user) && !this.isExcludedUser(user)),
takeUntil(this.onDestroy$)
).subscribe((user: IdentityUserModel) => {
this.searchUsers.push(user);
this.searchUsers$.next(this.searchUsers);
});
}
private initializeStream() {
const typingValueFromControl$ = this.searchUserCtrl.valueChanges;
const typingValueTypeSting$ = typingValueFromControl$.pipe(
filter(value => {
this.searchLoading = true;
return typeof value === 'string';
}),
})
);
const typingValueHandleErrorMessage$ = typingValueTypeSting$.pipe(
tap((value: string) => {
if (value) {
this.setTypingError();
}
}),
})
);
const typingValueDebouncedUnique$ = typingValueHandleErrorMessage$.pipe(
debounceTime(500),
distinctUntilChanged(),
distinctUntilChanged()
);
this.typingUniqueValueNotEmpty$ = typingValueDebouncedUnique$.pipe(
tap((value: string) => {
if (value.trim()) {
this.searchedValue = value;
@ -216,87 +236,17 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
this.searchUserCtrl.markAsUntouched();
}
}),
tap(() => {
this.resetSearchUsers();
}),
switchMap((search) =>
this.findUsers(search)
),
mergeMap((users) => {
this.resetSearchUsers();
this.searchLoading = false;
return users;
}),
filter(user => !this.isUserAlreadySelected(user) && !this.isExcludedUser(user)),
mergeMap(user => this.filterUsersByGroupsRestriction(user)),
mergeMap(user => {
if (this.appName) {
return this.checkUserHasAccess(user.id).pipe(
mergeMap(
hasRole => hasRole ? of(user) : of()
)
);
} else if (this.hasRoles()) {
return this.filterUsersByRoles(user);
} else {
return of(user);
}
}),
takeUntil(this.onDestroy$)
).subscribe(user => {
this._searchUsers.push(user);
this.searchUsers$.next(this._searchUsers);
});
tap(() => this.resetSearchUsers())
);
}
ngOnDestroy(): void {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
private isAppNameChanged(change: SimpleChange): boolean {
return change && change.previousValue !== change.currentValue && this.appName && this.appName.length > 0;
}
isValidationEnabled(): boolean {
private isValidationEnabled(): boolean {
return this.validate === true;
}
private checkUserHasAccess(userId: string): Observable<boolean> {
if (this.hasRoles()) {
return this.identityUserService.checkUserHasAnyClientAppRole(userId, this.clientId, this.roles);
} else {
return this.identityUserService.checkUserHasClientApp(userId, this.clientId);
}
}
private hasRoles(): boolean {
return this.roles && this.roles.length > 0;
}
filterUsersByRoles(user: IdentityUserModel): Observable<IdentityUserModel> {
return this.identityUserService.checkUserHasRole(user.id, this.roles).pipe(
map((hasRole: boolean) => ({ hasRole, user })),
filter((filteredUser: { hasRole: boolean; user: IdentityUserModel }) => filteredUser.hasRole),
map((filteredUser: { hasRole: boolean; user: IdentityUserModel }) => filteredUser.user));
}
private filterUsersByGroupsRestriction(user: IdentityUserModel): Observable<IdentityUserModel> {
if (this.groupsRestriction?.length) {
return this.isUserPartOfAllRestrictedGroups(user).pipe(
mergeMap(isPartOfAllGroups => isPartOfAllGroups ? of(user) : of())
);
}
return of(user);
}
private findUsers(search: string): Observable<IdentityUserModel[]> {
return this.identityUserService.findUsersByName(search.trim());
}
private isUserAlreadySelected(searchUser: IdentityUserModel): boolean {
if (this.selectedUsers && this.selectedUsers.length > 0) {
const result = this.selectedUsers.find((selectedUser) => this.compare(selectedUser, searchUser));
const result = this.selectedUsers.find((selectedUser) => this.equalsUsers(selectedUser, searchUser));
return !!result;
}
@ -305,7 +255,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
private isExcludedUser(searchUser: IdentityUserModel): boolean {
if (this.excludedUsers?.length > 0) {
return !!this.excludedUsers.find(excludedUser => this.compare(excludedUser, searchUser));
return !!this.excludedUsers.find(excludedUser => this.equalsUsers(excludedUser, searchUser));
}
return false;
}
@ -334,28 +284,14 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
}
}
async validatePreselectUsers(): Promise<any> {
private async validatePreselectUsers(): Promise<any> {
this.invalidUsers = [];
const validUsers: IdentityUserModel[] = [];
for (const user of this.getPreselectedUsers()) {
try {
const validationResult = await this.searchUser(user);
const validationResult = (await this.identityUserService.search(user.username, { roles: this.roles, withinApplication: this.appName, groups: this.groupsRestriction }).toPromise())[0];
if (this.compare(user, validationResult)) {
validationResult.readonly = user.readonly;
if (this.groupsRestriction?.length) {
const isUserPartOfAllRestrictedGroups = await this.isUserPartOfAllRestrictedGroups(validationResult).toPromise();
if (isUserPartOfAllRestrictedGroups) {
validUsers.push(user);
} else {
this.invalidUsers.push(user);
}
} else {
validUsers.push(validationResult);
}
} else {
if (!this.equalsUsers(user, validationResult)) {
this.invalidUsers.push(user);
}
} catch (error) {
@ -365,10 +301,9 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
}
this.checkPreselectValidationErrors();
this.selectedUsers = validUsers.concat(this.invalidUsers);
}
compare(preselectedUser: IdentityUserModel, identityUser: IdentityUserModel): boolean {
equalsUsers(preselectedUser: IdentityUserModel, identityUser: IdentityUserModel): boolean {
if (preselectedUser && identityUser) {
const uniquePropertyIdentifiers = ['id', 'username', 'email'];
for (const property of Object.keys(preselectedUser)) {
@ -380,33 +315,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
return false;
}
private getSearchKey(user: IdentityUserModel): string {
if (user.id) {
return 'id';
} else if (user.email) {
return 'email';
} else if (user.username) {
return 'username';
} else {
return null;
}
}
async searchUser(user: IdentityUserModel): Promise<IdentityUserModel> {
const key = this.getSearchKey(user);
switch (key) {
case 'id':
return this.identityUserService.findUserById(user[key]).toPromise();
case 'username':
return (await this.identityUserService.findUserByUsername(user[key]).toPromise())[0];
case 'email':
return (await this.identityUserService.findUserByEmail(user[key]).toPromise())[0];
default:
return null;
}
}
removeDuplicatedUsers(users: IdentityUserModel[]): IdentityUserModel[] {
return users.filter((user, index, self) =>
index === self.findIndex(auxUser =>
@ -414,19 +322,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
));
}
checkPreselectValidationErrors(): void {
this.invalidUsers = this.removeDuplicatedUsers(this.invalidUsers);
if (this.invalidUsers.length > 0) {
this.generateInvalidUsersMessage();
}
this.warning.emit({
message: 'INVALID_PRESELECTED_USERS',
users: this.invalidUsers
});
}
onSelect(user: IdentityUserModel): void {
if (user) {
this.selectUser.emit(user);
@ -442,7 +337,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
this.userInput.nativeElement.value = '';
this.searchUserCtrl.setValue('');
this.userChipsCtrlValue(this.selectedUsers[0].username);
this.userChipsControlValue(this.selectedUsers[0].username);
this.changedUsers.emit(this.selectedUsers);
this.resetSearchUsers();
@ -454,10 +349,10 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
this.removeUserFromSelected(userToRemove);
this.changedUsers.emit(this.selectedUsers);
if (this.selectedUsers.length === 0) {
this.userChipsCtrlValue('');
this.userChipsControlValue('');
} else {
this.userChipsCtrlValue(this.selectedUsers[0].username);
this.userChipsControlValue(this.selectedUsers[0].username);
}
this.searchUserCtrl.markAsDirty();
this.searchUserCtrl.markAsTouched();
@ -468,10 +363,17 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
}
}
private userChipsCtrlValue(value: string) {
this.userChipsCtrl.setValue(value);
this.userChipsCtrl.markAsDirty();
this.userChipsCtrl.markAsTouched();
private checkPreselectValidationErrors(): void {
this.invalidUsers = this.removeDuplicatedUsers(this.invalidUsers);
if (this.invalidUsers.length > 0) {
this.generateInvalidUsersMessage();
}
this.warning.emit({
message: 'INVALID_PRESELECTED_USERS',
users: this.invalidUsers
});
}
private removeUserFromSelected({ id, username, email }: IdentityUserModel): void {
@ -494,7 +396,7 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
}
}
generateInvalidUsersMessage(): void {
private generateInvalidUsersMessage(): void {
this.validateUsersMessage = '';
this.invalidUsers.forEach((invalidUser, index) => {
@ -506,13 +408,6 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
});
}
setTypingError(): void {
this.searchUserCtrl.setErrors({
searchTypingError: true,
...this.searchUserCtrl.errors
});
}
hasPreselectError(): boolean {
return this.invalidUsers
&& this.invalidUsers.length > 0;
@ -522,11 +417,11 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
return FullNamePipe.prototype.transform(user);
}
isMultipleMode(): boolean {
private isMultipleMode(): boolean {
return this.mode === 'multiple';
}
isSingleMode(): boolean {
private isSingleMode(): boolean {
return this.mode === 'single';
}
@ -541,9 +436,22 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
&& this.preSelectUsers.length > 0;
}
private valueChanged(change: SimpleChange): boolean {
return change
&& change.currentValue !== change.previousValue;
private hasModeChanged(changes: SimpleChanges): boolean {
return changes
&& changes.mode
&& changes.mode.currentValue !== changes.mode.previousValue;
}
private isValidationChanged(changes: SimpleChanges): boolean {
return changes
&& changes.validate
&& changes.validate.currentValue !== changes.validate.previousValue;
}
private hasPreselectedUsersChanged(changes: SimpleChanges): boolean {
return changes
&& changes.preSelectUsers
&& changes.preSelectUsers.currentValue !== changes.preSelectUsers.previousValue;
}
private hasPreselectedUsersCleared(changes: SimpleChanges): boolean {
@ -554,20 +462,21 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
}
private resetSearchUsers(): void {
this._searchUsers = [];
this.searchUsers$.next(this._searchUsers);
this.searchUsers = [];
this.searchUsers$.next(this.searchUsers);
}
private isUserPartOfAllRestrictedGroups(user: IdentityUserModel): Observable<boolean> {
return this.getUserGroups(user.id).pipe(
map(userGroups => this.groupsRestriction.every(restricted => userGroups.includes(restricted)))
);
private setTypingError(): void {
this.searchUserCtrl.setErrors({
searchTypingError: true,
...this.searchUserCtrl.errors
});
}
private getUserGroups(userId: string): Observable<string[]> {
return this.identityUserService.getInvolvedGroups(userId).pipe(
map(groups => groups.map((group) => group.name))
);
private userChipsControlValue(value: string) {
this.userChipsCtrl.setValue(value);
this.userChipsCtrl.markAsDirty();
this.userChipsCtrl.markAsTouched();
}
getSelectedUsers(): IdentityUserModel[] {
@ -617,4 +526,9 @@ export class PeopleCloudComponent implements OnInit, OnChanges, OnDestroy {
getValidationMinLength(): string {
return this.searchUserCtrl.errors.minlength.requiredLength;
}
ngOnDestroy(): void {
this.onDestroy$.next(true);
this.onDestroy$.complete();
}
}

View File

@ -0,0 +1,90 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HttpErrorResponse } from '@angular/common/http';
import { throwError } from 'rxjs';
import { IdentityUserModel } from '../models/identity-user.model';
import { IdentityUserFilterInterface } from '../services/identity-user-filter.interface';
export const mockSearchUserEmptyFilters: IdentityUserFilterInterface = {
roles: [],
groups: [],
withinApplication: ''
};
export const mockSearchUserByGroups: IdentityUserFilterInterface = {
roles: [],
groups: ['fake-group-1', 'fake-group-2'],
withinApplication: ''
};
export const mockSearchUserByGroupsAndRoles: IdentityUserFilterInterface = {
roles: ['fake-role-1', 'fake-role-2'],
groups: ['fake-group-1', 'fake-group-2'],
withinApplication: ''
};
export const mockSearchUserByGroupsAndRolesAndApp: IdentityUserFilterInterface = {
roles: ['fake-role-1', 'fake-role-2'],
groups: ['fake-group-1', 'fake-group-2'],
withinApplication: 'fake-app-name'
};
export const mockSearchUserByRoles: IdentityUserFilterInterface = {
roles: ['fake-role-1', 'fake-role-2'],
groups: [],
withinApplication: ''
};
export const mockSearchUserByRolesAndApp: IdentityUserFilterInterface = {
roles: ['fake-role-1', 'fake-role-2'],
groups: [],
withinApplication: 'fake-app-name'
};
export const mockSearchUserByApp: IdentityUserFilterInterface = {
roles: [],
groups: [],
withinApplication: 'fake-app-name'
};
export const mockSearchUserByAppAndGroups: IdentityUserFilterInterface = {
roles: [],
groups: ['fake-group-1', 'fake-group-2'],
withinApplication: 'fake-app-name'
};
export function oAuthMockApiWithIdentityUsers(users: IdentityUserModel[]) {
return {
oauth2Auth: {
callCustomApi: () => Promise.resolve(users)
},
reply: jasmine.createSpy('reply')
};
}
const errorResponse = new HttpErrorResponse({
error: 'Mock Error',
status: 404, statusText: 'Not Found'
});
export const oAuthMockApiWithError = {
oauth2Auth: {
callCustomApi: () => throwError(errorResponse)
},
reply: jasmine.createSpy('reply')
};

View File

@ -0,0 +1,55 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { Observable, EMPTY, of } from 'rxjs';
import { IdentityUserModel } from '../models/identity-user.model';
import { IdentityUserFilterInterface } from '../services/identity-user-filter.interface';
import { IdentityUserServiceInterface } from '../services/identity-user.service.interface';
export const mockYorkshirePudding: IdentityUserModel = { id: 'yorkshire', username: 'Yorkshire Pudding', firstName: 'Yorkshire', lastName: 'Pudding', email: 'pudding@food.com' };
export const mockShepherdsPie: IdentityUserModel = { id: 'shepherds', username: 'Shepherds Pie', firstName: 'Shepherds', lastName: 'Pie', email: 'shepherds@food.com'};
export const mockKielbasaSausage: IdentityUserModel = { id: 'kielbasa', username: 'Kielbasa Sausage', firstName: 'Kielbasa', lastName: 'Sausage', email: 'sausage@food.com' };
export const mockFoodUsers: IdentityUserModel[] = [mockYorkshirePudding, mockShepherdsPie, mockKielbasaSausage];
export const mockPreselectedFoodUsers = [
{ ...mockYorkshirePudding, readonly: false },
{ ...mockKielbasaSausage, readonly: false }
];
@Injectable({
providedIn: 'root'
})
export class IdentityUserServiceMock implements IdentityUserServiceInterface {
queryParams: { search: string; application?: string; roles?: string[]; groups?: string[] };
getCurrentUserInfo(): IdentityUserModel {
return mockKielbasaSausage;
}
search(name: string, _filters?: IdentityUserFilterInterface): Observable<IdentityUserModel[]> {
if (name.trim() === '') {
return EMPTY;
}
return of(mockFoodUsers.filter(group =>
group.username.toUpperCase().includes(name.toUpperCase())
));
}
}

View File

@ -1,52 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const mockUsers = [
{ id: 'fake-id-1', username: 'first-name-1 last-name-1', firstName: 'first-name-1', lastName: 'last-name-1', email: 'abc@xyz.com' },
{ id: 'fake-id-2', username: 'first-name-2 last-name-2', firstName: 'first-name-2', lastName: 'last-name-2', email: 'abcd@xyz.com'},
{ id: 'fake-id-3', username: 'first-name-3 last-name-3', firstName: 'first-name-3', lastName: 'last-name-3', email: 'abcde@xyz.com' }
];
export const cloudMockUser = {
id: 'fake-id-1', username: 'AssignedTaskUser', firstName: 'first-name-1', lastName: 'last-name-1', email: 'abc@xyz.com'
};
export const mockRoles = [
{ id: 'id-1', name: 'MOCK-ADMIN-ROLE'},
{ id: 'id-2', name: 'MOCK-USER-ROLE'},
{ id: 'id-3', name: 'MOCK_MODELER-ROLE' },
{ id: 'id-4', name: 'MOCK-ROLE-1' },
{ id: 'id-5', name: 'MOCK-ROLE-2'}
];
export const mockOAuth2: any = {
oauth2Auth: {
callCustomApi: () => Promise.resolve(mockUsers)
},
isEcmLoggedIn: () => false,
reply: jasmine.createSpy('reply')
};
export const mockPreselectedUsers = [
{ id: mockUsers[1].id, username: mockUsers[1].username, readonly: false },
{ id: mockUsers[2].id, username: mockUsers[2].username, readonly: false }
];
export const mockInvolvedGroups = [
{ id: 'mock-group-id-1', name: 'Mock Group 1', path: '/mock', subGroups: [] },
{ id: 'mock-group-id-2', name: 'Mock Group 2', path: '', subGroups: [] }
];

View File

@ -0,0 +1,25 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface IdentityUserModel {
id?: string;
username: string;
firstName?: string;
lastName?: string;
email?: string;
readonly?: boolean;
}

View File

@ -22,6 +22,8 @@ import { MaterialModule } from '../material.module';
import { CoreModule } from '@alfresco/adf-core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IdentityUserService } from './services/identity-user.service';
import { IDENTITY_USER_SERVICE_TOKEN } from './services/identity-user-service.token';
@NgModule({
imports: [
@ -35,6 +37,9 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
declarations: [PeopleCloudComponent],
exports: [
PeopleCloudComponent
],
providers: [
{ provide: IDENTITY_USER_SERVICE_TOKEN, useExisting: IdentityUserService }
]
})
export class PeopleCloudModule {

View File

@ -16,5 +16,7 @@
*/
export * from './components/people-cloud.component';
export * from './people-cloud.module';
export * from './models/identity-user.model';
export * from './services/identity-user.service';
export * from './services/identity-user-service.token';

View File

@ -0,0 +1,22 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface IdentityUserFilterInterface {
roles?: string[];
withinApplication?: string;
groups?: string[];
}

View File

@ -0,0 +1,21 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { InjectionToken } from '@angular/core';
import { IdentityUserServiceInterface } from './identity-user.service.interface';
export const IDENTITY_USER_SERVICE_TOKEN = new InjectionToken<IdentityUserServiceInterface>('identity-user-service-token');

View File

@ -0,0 +1,25 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Observable } from 'rxjs';
import { IdentityUserModel } from '../models/identity-user.model';
import { IdentityUserFilterInterface } from './identity-user-filter.interface';
export interface IdentityUserServiceInterface {
getCurrentUserInfo(): IdentityUserModel;
search(name: string, filters?: IdentityUserFilterInterface): Observable<IdentityUserModel[]>;
}

View File

@ -0,0 +1,293 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { AlfrescoApiService, JwtHelperService, mockToken, setupTestBed } from '@alfresco/adf-core';
import { IdentityUserService } from './identity-user.service';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import {
mockSearchUserByApp,
mockSearchUserByAppAndGroups,
mockSearchUserByGroups,
mockSearchUserByGroupsAndRoles,
mockSearchUserByGroupsAndRolesAndApp,
mockSearchUserByRoles,
mockSearchUserByRolesAndApp,
oAuthMockApiWithError,
oAuthMockApiWithIdentityUsers
} from '../mock/identity-user.service.mock';
import { mockFoodUsers } from '../mock/people-cloud.mock';
describe('IdentityUserService', () => {
let service: IdentityUserService;
let alfrescoApiService: AlfrescoApiService;
setupTestBed({
imports: [
TranslateModule.forRoot(),
ProcessServiceCloudTestingModule
]
});
beforeEach(() => {
service = TestBed.inject(IdentityUserService);
alfrescoApiService = TestBed.inject(AlfrescoApiService);
});
describe('Current user info (JWT token)', () => {
beforeEach(() => {
const store = {};
spyOn(localStorage, 'getItem').and.callFake((key: string): string => store[key] || null);
spyOn(localStorage, 'setItem').and.callFake((key: string, value: string): string => store[key] = value);
});
it('should fetch identity user info from Jwt id token', () => {
localStorage.setItem(JwtHelperService.USER_ID_TOKEN, mockToken);
const user = service.getCurrentUserInfo();
expect(user).toBeDefined();
expect(user.firstName).toEqual('John');
expect(user.lastName).toEqual('Doe');
expect(user.email).toEqual('johnDoe@gmail.com');
expect(user.username).toEqual('johnDoe1');
});
it('should fallback on Jwt access token for identity user info', () => {
localStorage.setItem(JwtHelperService.USER_ACCESS_TOKEN, mockToken);
const user = service.getCurrentUserInfo();
expect(user).toBeDefined();
expect(user.firstName).toEqual('John');
expect(user.lastName).toEqual('Doe');
expect(user.email).toEqual('johnDoe@gmail.com');
expect(user.username).toEqual('johnDoe1');
});
});
describe('Search', () => {
it('should fetch users', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityUsers(mockFoodUsers));
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake').subscribe(
res => {
expect(res).toBeDefined();
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake'
});
done();
}
);
});
it('should not fetch users if error occurred', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithError);
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake')
.subscribe(
() => {
fail('expected an error, not users');
},
(error) => {
expect(searchSpy).toHaveBeenCalled();
expect(error.status).toEqual(404);
expect(error.statusText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
it('should fetch users by roles', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityUsers(mockFoodUsers));
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake', mockSearchUserByRoles).subscribe(
res => {
expect(res).toBeDefined();
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake',
role: 'fake-role-1,fake-role-2'
});
done();
}
);
});
it('should not fetch users by roles if error occurred', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithError);
service.search('fake', mockSearchUserByRoles)
.subscribe(
() => {
fail('expected an error, not users');
},
(error) => {
expect(error.status).toEqual(404);
expect(error.statusText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
it('should fetch users by groups', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityUsers(mockFoodUsers));
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake', mockSearchUserByGroups).subscribe(
res => {
expect(res).toBeDefined();
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake',
group: 'fake-group-1,fake-group-2'
});
done();
}
);
});
it('should fetch users by roles with groups', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityUsers(mockFoodUsers));
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake', mockSearchUserByGroupsAndRoles).subscribe(
res => {
expect(res).toBeDefined();
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake',
role: 'fake-role-1,fake-role-2',
group: 'fake-group-1,fake-group-2'
});
done();
}
);
});
it('should fetch users by roles with groups and appName', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityUsers(mockFoodUsers));
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake', mockSearchUserByGroupsAndRolesAndApp).subscribe(
res => {
expect(res).toBeDefined();
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake',
role: 'fake-role-1,fake-role-2',
application: 'fake-app-name',
group: 'fake-group-1,fake-group-2'
});
done();
}
);
});
it('should not fetch users by groups if error occurred', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithError);
service.search('fake', mockSearchUserByGroups)
.subscribe(
() => {
fail('expected an error, not users');
},
(error) => {
expect(error.status).toEqual(404);
expect(error.statusText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
it('should fetch users within app', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityUsers(mockFoodUsers));
service.search('fake', mockSearchUserByApp).subscribe(
res => {
expect(res).toBeDefined();
expect(service.queryParams).toEqual({
search: 'fake',
application: 'fake-app-name'
});
done();
}
);
});
it('should fetch users within app with roles', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityUsers(mockFoodUsers));
service.search('fake', mockSearchUserByRolesAndApp).subscribe(
res => {
expect(res).toBeDefined();
expect(service.queryParams).toEqual({
search: 'fake',
application: 'fake-app-name',
role: 'fake-role-1,fake-role-2'
});
done();
}
);
});
it('should fetch users within app with groups', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithIdentityUsers(mockFoodUsers));
const searchSpy = spyOn(service, 'search').and.callThrough();
service.search('fake', mockSearchUserByAppAndGroups).subscribe(
res => {
expect(res).toBeDefined();
expect(searchSpy).toHaveBeenCalled();
expect(service.queryParams).toEqual({
search: 'fake',
application: 'fake-app-name',
group: 'fake-group-1,fake-group-2'
});
done();
}
);
});
it('should not fetch users within app if error occurred', (done) => {
spyOn(alfrescoApiService, 'getInstance').and.returnValue(oAuthMockApiWithError);
service.search('fake', mockSearchUserByApp)
.subscribe(
() => {
fail('expected an error, not users');
},
(error) => {
expect(error.status).toEqual(404);
expect(error.statusText).toEqual('Not Found');
expect(error.error).toEqual('Mock Error');
done();
}
);
});
});
});

View File

@ -0,0 +1,150 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import {
AppConfigService,
JwtHelperService,
OAuth2Service
} from '@alfresco/adf-core';
import { EMPTY, Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { IdentityUserServiceInterface } from './identity-user.service.interface';
import { IdentityUserModel } from '../models/identity-user.model';
import { IdentityUserFilterInterface } from './identity-user-filter.interface';
const IDENTITY_MICRO_SERVICE_INGRESS = 'modeling-service';
@Injectable({
providedIn: 'root'
})
export class IdentityUserService implements IdentityUserServiceInterface {
queryParams: { search: string; application?: string; roles?: string[]; groups?: string[] };
constructor(
private jwtHelperService: JwtHelperService,
private oAuth2Service: OAuth2Service,
private appConfigService: AppConfigService) {
}
/**
* Gets the name and other basic details of the current user.
*
* @returns The user's details
*/
public getCurrentUserInfo(): IdentityUserModel {
const familyName = this.jwtHelperService.getValueFromLocalToken<string>(JwtHelperService.FAMILY_NAME);
const givenName = this.jwtHelperService.getValueFromLocalToken<string>(JwtHelperService.GIVEN_NAME);
const email = this.jwtHelperService.getValueFromLocalToken<string>(JwtHelperService.USER_EMAIL);
const username = this.jwtHelperService.getValueFromLocalToken<string>(JwtHelperService.USER_PREFERRED_USERNAME);
return { firstName: givenName, lastName: familyName, email, username };
}
/**
* Search users based on name input and filters.
*
* @param name Search query string
* @param [filters] Search query filters
* @returns List of users
*/
public search(name: string, filters?: IdentityUserFilterInterface): Observable<IdentityUserModel[]> {
if (name.trim() === '') {
return EMPTY;
} else if (filters?.groups?.length > 0) {
return this.searchUsersWithGroups(name, filters);
} else if (filters?.withinApplication) {
return this.searchUsersWithinApp(name, filters.withinApplication, filters?.roles);
} else if (filters?.roles?.length > 0) {
return this.searchUsersWithGlobalRoles(name, filters.roles);
} else {
return this.searchUsersByName(name);
}
}
private searchUsersByName(name: string): Observable<IdentityUserModel[]> {
this.buildQueryParam(name);
return this.invokeIdentityUserApi().pipe(
catchError((err) => this.handleError(err))
);
}
private searchUsersWithGlobalRoles(name: string, roles: string []): Observable<IdentityUserModel[]> {
this.buildQueryParam(name, {roles});
return this.invokeIdentityUserApi().pipe(
catchError((err) => this.handleError(err))
);
}
private searchUsersWithinApp(name: string, withinApplication: string, roles?: string []): Observable<IdentityUserModel[]> {
this.buildQueryParam(name, {roles, withinApplication});
return this.invokeIdentityUserApi().pipe(
catchError((err) => this.handleError(err))
);
}
private searchUsersWithGroups(name: string, filters: IdentityUserFilterInterface): Observable<IdentityUserModel[]> {
this.buildQueryParam(name, filters);
return this.invokeIdentityUserApi().pipe(
catchError((err) => this.handleError(err))
);
}
private invokeIdentityUserApi(): Observable<any> {
const url = `${this.identityHost}/${IDENTITY_MICRO_SERVICE_INGRESS}/v1/identity/users`;
return this.oAuth2Service.get({ url, queryParams: this.queryParams });
}
private buildQueryParam(name: string, filters?: IdentityUserFilterInterface) {
this.queryParams = { search: name };
this.addOptionalValueToQueryParam('application', filters?.withinApplication);
this.addOptionalCommaValueToQueryParam('role', filters?.roles);
this.addOptionalCommaValueToQueryParam('group', filters?.groups);
}
private addOptionalCommaValueToQueryParam(key: string, values: string []) {
if (values?.length > 0) {
const valuesNotEmpty = this.filterOutEmptyValue(values);
if (valuesNotEmpty?.length > 0) {
this.queryParams[key] = valuesNotEmpty.join(',');
}
}
}
private addOptionalValueToQueryParam(key: string, value: string) {
if (value?.trim()) {
this.queryParams[key] = value;
}
}
private filterOutEmptyValue(values: string []): string [] {
return values.filter( value => value.trim() ? true : false);
}
private get identityHost(): string {
return `${this.appConfigService.get('bpmHost')}`;
}
private handleError(error: any) {
return throwError(error || 'Server error');
}
}

View File

@ -17,11 +17,12 @@
import { Component, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IdentityUserService, setupTestBed } from '@alfresco/adf-core';
import { setupTestBed } from '@alfresco/adf-core';
import { CancelProcessDirective } from './cancel-process.directive';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { ProcessInstanceCloud } from '../start-process/models/process-instance-cloud.model';
import { IdentityUserService } from '../../people/services/identity-user.service';
const processDetailsMockRunning: ProcessInstanceCloud = { initiator: 'usermock', status: 'RUNNING' };
const processDetailsMockCompleted: ProcessInstanceCloud = { initiator: 'usermock', status: 'COMPLETED' };

View File

@ -15,11 +15,11 @@
* limitations under the License.
*/
import { Directive, HostListener, Output, EventEmitter, OnInit, OnDestroy, ElementRef } from '@angular/core';
import { IdentityUserService } from '@alfresco/adf-core';
import { ProcessCloudService } from '../services/process-cloud.service';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { ProcessInstanceCloud } from '../start-process/models/process-instance-cloud.model';
import { IdentityUserService } from '../../people/services/identity-user.service';
@Directive({
// eslint-disable-next-line @angular-eslint/directive-selector

View File

@ -25,11 +25,12 @@ import moment from 'moment-es6';
import { Moment } from 'moment';
import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service';
import { ProcessFilterCloudModel, ProcessFilterProperties, ProcessFilterAction, ProcessFilterOptions, ProcessSortFilterProperty } from '../models/process-filter-cloud.model';
import { IdentityUserModel, TranslationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core';
import { TranslationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core';
import { ProcessFilterCloudService } from '../services/process-filter-cloud.service';
import { ProcessFilterDialogCloudComponent } from './process-filter-dialog-cloud.component';
import { ProcessCloudService } from '../../services/process-cloud.service';
import { DateCloudFilterType, DateRangeFilter } from '../../../models/date-cloud-filter.model';
import { IdentityUserModel } from '../../../people/models/identity-user.model';
export const PROCESS_FILTER_ACTION_SAVE = 'save';
export const PROCESS_FILTER_ACTION_SAVE_AS = 'saveAs';

View File

@ -16,7 +16,7 @@
*/
import { TestBed } from '@angular/core/testing';
import { setupTestBed, IdentityUserService } from '@alfresco/adf-core';
import { setupTestBed } from '@alfresco/adf-core';
import { of } from 'rxjs';
import { ProcessFilterCloudService } from './process-filter-cloud.service';
import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
@ -25,6 +25,7 @@ import { ProcessServiceCloudTestingModule } from '../../../testing/process-servi
import { TranslateModule } from '@ngx-translate/core';
import { fakeEmptyProcessCloudFilterEntries, fakeProcessCloudFilterEntries, fakeProcessCloudFilters, fakeProcessCloudFilterWithDifferentEntries, fakeProcessFilter } from '../mock/process-filters-cloud.mock';
import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
import { IdentityUserService } from '../../../people/services/identity-user.service';
describe('ProcessFilterCloudService', () => {
let service: ProcessFilterCloudService;

View File

@ -15,13 +15,13 @@
* limitations under the License.
*/
import { IdentityUserService } from '@alfresco/adf-core';
import { Injectable, Inject } from '@angular/core';
import { Observable, of, BehaviorSubject, throwError } from 'rxjs';
import { ProcessFilterCloudModel } from '../models/process-filter-cloud.model';
import { switchMap, map, catchError } from 'rxjs/operators';
import { PROCESS_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface';
import { IdentityUserService } from '../../../people/services/identity-user.service';
@Injectable({
providedIn: 'root'
})

View File

@ -39,8 +39,7 @@ export class BaseCloudService {
path: '',
httpMethod: '',
contentTypes: ['application/json'],
accepts: ['application/json'],
returnType: Object
accepts: ['application/json']
};
constructor(

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { Directive, Input, HostListener, Output, EventEmitter, OnInit } from '@angular/core';
import { IdentityUserService } from '@alfresco/adf-core';
import { IdentityUserService } from '../../people/services/identity-user.service';
import { TaskCloudService } from '../services/task-cloud.service';
@Directive({

View File

@ -18,8 +18,8 @@
import { Injectable } from '@angular/core';
import { AppConfigService, CardViewArrayItem, LogService } from '@alfresco/adf-core';
import { from, Observable, of, Subject, throwError } from 'rxjs';
import { DEFAULT_TASK_PRIORITIES, TaskPriorityOption, TASK_ASSIGNED_STATE, TASK_CREATED_STATE } from '../models/task.model';
import { TaskDetailsCloudModel } from '../start-task/public-api';
import { DEFAULT_TASK_PRIORITIES, TaskPriorityOption } from '../models/task.model';
import { TaskDetailsCloudModel, TASK_ASSIGNED_STATE, TASK_CREATED_STATE } from '../start-task/models/task-details-cloud.model';
import { taskDetailsContainer } from '../task-header/mocks/task-details-cloud.mock';
import { ProcessDefinitionCloud } from '../../models/process-definition-cloud.model';
import { StartTaskCloudRequestModel } from '../start-task/models/start-task-cloud-request.model';

View File

@ -16,7 +16,7 @@
*/
import { TestBed } from '@angular/core/testing';
import { setupTestBed, IdentityUserService, TranslationService, AlfrescoApiService } from '@alfresco/adf-core';
import { setupTestBed, TranslationService, AlfrescoApiService } from '@alfresco/adf-core';
import { TaskCloudService } from './task-cloud.service';
import { taskCompleteCloudMock } from '../task-header/mocks/fake-complete-task.mock';
import { assignedTaskDetailsCloudMock, createdTaskDetailsCloudMock, emptyOwnerTaskDetailsCloudMock } from '../task-header/mocks/task-details-cloud.mock';
@ -24,6 +24,7 @@ import { fakeTaskDetailsCloud } from '../task-header/mocks/fake-task-details-res
import { cloudMockUser } from '../start-task/mock/user-cloud.mock';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { TranslateModule } from '@ngx-translate/core';
import { IdentityUserService } from '../../people/services/identity-user.service';
describe('Task Cloud Service', () => {

View File

@ -16,9 +16,9 @@
*/
import { Injectable } from '@angular/core';
import { AlfrescoApiService, LogService, AppConfigService, IdentityUserService, CardViewArrayItem, TranslationService } from '@alfresco/adf-core';
import { AlfrescoApiService, LogService, AppConfigService, CardViewArrayItem, TranslationService } from '@alfresco/adf-core';
import { throwError, Observable, of, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { catchError, map } from 'rxjs/operators';
import {
TaskDetailsCloudModel,
StartTaskCloudResponseModel,
@ -35,6 +35,7 @@ import {
TaskPriorityOption
} from '../models/task.model';
import { TaskCloudServiceInterface } from './task-cloud.service.interface';
import { IdentityUserService } from '../../people/services/identity-user.service';
@Injectable({
providedIn: 'root'
@ -242,7 +243,9 @@ export class TaskCloudService extends BaseCloudService implements TaskCloudServi
getCandidateUsers(appName: string, taskId: string): Observable<string[]> {
if ((appName || appName === '') && taskId) {
const queryUrl = `${this.getBasePath(appName)}/query/v1/tasks/${taskId}/candidate-users`;
return this.get<string[]>(queryUrl);
return this.get<string[]>(queryUrl).pipe(
catchError((err) => this.handleError(err))
);
} else {
this.logService.error('AppName and TaskId are mandatory to get candidate user');
return of([]);
@ -320,4 +323,9 @@ export class TaskCloudService extends BaseCloudService implements TaskCloudServi
const currentUser = this.identityUserService.getCurrentUserInfo().username;
return assignee === currentUser;
}
private handleError(error?: any) {
this.logService.error(error);
return throwError(error || 'Server error');
}
}

View File

@ -16,7 +16,7 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed, IdentityUserService, AlfrescoApiService, IdentityUserModel } from '@alfresco/adf-core';
import { setupTestBed, AlfrescoApiService } from '@alfresco/adf-core';
import { StartTaskCloudComponent } from './start-task-cloud.component';
import { of, throwError } from 'rxjs';
import { taskDetailsMock } from '../mock/task-details.mock';
@ -26,6 +26,8 @@ import { FormDefinitionSelectorCloudService } from '../../../form/services/form-
import { TaskCloudService } from '../../services/task-cloud.service';
import { StartTaskCloudRequestModel } from '../models/start-task-cloud-request.model';
import { TranslateModule } from '@ngx-translate/core';
import { IdentityUserService } from '../../../people/services/identity-user.service';
import { IdentityUserModel } from '../../../people/models/identity-user.model';
describe('StartTaskCloudComponent', () => {

View File

@ -24,8 +24,6 @@ import {
MOMENT_DATE_FORMATS, MomentDateAdapter,
LogService,
UserPreferencesService,
IdentityUserService,
IdentityUserModel,
UserPreferenceValues
} from '@alfresco/adf-core';
import { PeopleCloudComponent } from '../../../people/components/people-cloud.component';
@ -34,6 +32,8 @@ import { TaskCloudService } from '../../services/task-cloud.service';
import { StartTaskCloudRequestModel } from '../models/start-task-cloud-request.model';
import { takeUntil } from 'rxjs/operators';
import { TaskPriorityOption } from '../../models/task.model';
import { IdentityUserService } from '../../../people/services/identity-user.service';
import { IdentityUserModel } from '../../../people/models/identity-user.model';
const MAX_NAME_LENGTH = 255;
const DATE_FORMAT: string = 'DD/MM/YYYY';

View File

@ -25,9 +25,11 @@ import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';
import { debounceTime, filter, finalize, switchMap, takeUntil } from 'rxjs/operators';
import { Observable, Subject } from 'rxjs';
import { DateAdapter } from '@angular/material/core';
import { IdentityGroupModel, IdentityUserModel, TranslationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core';
import { TranslationService, UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core';
import { TaskFilterDialogCloudComponent } from '../task-filter-dialog/task-filter-dialog-cloud.component';
import { MatDialog } from '@angular/material/dialog';
import { IdentityUserModel } from '../../../../people/models/identity-user.model';
import { IdentityGroupModel } from '../../../../group/models/identity-group.model';
/* eslint-disable @typescript-eslint/naming-convention */

View File

@ -19,7 +19,7 @@ import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { SimpleChange } from '@angular/core';
import { By } from '@angular/platform-browser';
import { AlfrescoApiService, IdentityUserModel, setupTestBed } from '@alfresco/adf-core';
import { AlfrescoApiService, setupTestBed } from '@alfresco/adf-core';
import { MatDialog } from '@angular/material/dialog';
import { of } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@ -41,6 +41,7 @@ import { TaskFilterCloudModel } from '../../models/filter-cloud.model';
import { PeopleCloudModule } from '../../../../people/people-cloud.module';
import { ProcessDefinitionCloud } from '../../../../models/process-definition-cloud.model';
import { MatIconTestingModule } from '@angular/material/icon/testing';
import { IdentityUserModel } from '../../../../people/models/identity-user.model';
describe('EditTaskFilterCloudComponent', () => {
let component: EditTaskFilterCloudComponent;
@ -537,7 +538,10 @@ describe('EditTaskFilterCloudComponent', () => {
const mockUser: IdentityUserModel[] = [{
id: 'id',
username: 'test'
username: 'test',
firstName: 'first-name',
lastName: 'last-name',
email: 'email@fake.com'
}];
const startedDateTypeControl: AbstractControl = component.editTaskFilterForm.get('completedBy');

View File

@ -16,13 +16,14 @@
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { setupTestBed, IdentityUserService, TranslationService, TranslationMock } from '@alfresco/adf-core';
import { setupTestBed, TranslationService, TranslationMock } from '@alfresco/adf-core';
import { TranslateModule } from '@ngx-translate/core';
import { TaskAssignmentFilterCloudComponent } from './task-assignment-filter.component';
import { GroupCloudModule } from 'process-services-cloud/src/lib/group/public-api';
import { TaskFiltersCloudModule } from '../../task-filters-cloud.module';
import { AssignmentType } from '../../models/filter-cloud.model';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { IdentityUserService } from '../../../../people/services/identity-user.service';
describe('EditTaskFilterCloudComponent', () => {
let component: TaskAssignmentFilterCloudComponent;

View File

@ -17,8 +17,10 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { IdentityGroupModel, IdentityUserModel, IdentityUserService } from '@alfresco/adf-core';
import { AssignmentType, TaskFilterProperties } from '../../models/filter-cloud.model';
import { IdentityUserModel } from '../../../../people/models/identity-user.model';
import { IdentityUserService } from '../../../../people/services/identity-user.service';
import { IdentityGroupModel } from '../../../../group/models/identity-group.model';
@Component({
selector: 'adf-cloud-task-assignment-filter',

View File

@ -22,7 +22,8 @@
import { DateCloudFilterType } from '../../../models/date-cloud-filter.model';
import { DateRangeFilterService } from '../../../common/date-range-filter/date-range-filter.service';
import { ComponentSelectionMode } from '../../../types';
import { IdentityUserModel, IdentityGroupModel } from '@alfresco/adf-core';
import { IdentityGroupModel } from '../../../group/models/identity-group.model';
import { IdentityUserModel } from '../../../people/models/identity-user.model';
export class TaskFilterCloudModel {
id: string;

View File

@ -15,13 +15,13 @@
* limitations under the License.
*/
import { IdentityUserService } from '@alfresco/adf-core';
import { Injectable, Inject } from '@angular/core';
import { Observable, of, BehaviorSubject } from 'rxjs';
import { ServiceTaskFilterCloudModel } from '../models/filter-cloud.model';
import { switchMap, map } from 'rxjs/operators';
import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface';
import { TASK_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
import { IdentityUserService } from '../../../people/services/identity-user.service';
@Injectable({
providedIn: 'root'

View File

@ -16,7 +16,7 @@
*/
import { TestBed } from '@angular/core/testing';
import { IdentityUserService, setupTestBed } from '@alfresco/adf-core';
import { setupTestBed } from '@alfresco/adf-core';
import { of } from 'rxjs';
import { TASK_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service';
import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service';
@ -36,6 +36,7 @@ import { TaskFilterCloudModel } from '../models/filter-cloud.model';
import { NotificationCloudService } from '../../../services/notification-cloud.service';
import { TaskCloudEngineEvent } from './../../../models/engine-event-cloud.model';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { IdentityUserService } from '../../../people/services/identity-user.service';
describe('TaskFilterCloudService', () => {
let service: TaskFilterCloudService;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { AlfrescoApiService, AppConfigService, IdentityUserService } from '@alfresco/adf-core';
import { AlfrescoApiService, AppConfigService } from '@alfresco/adf-core';
import { Injectable, Inject } from '@angular/core';
import { Observable, of, BehaviorSubject, throwError } from 'rxjs';
import { TaskFilterCloudModel } from '../models/filter-cloud.model';
@ -26,6 +26,7 @@ import { TASK_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.servic
import { TaskCloudNodePaging } from '../../../models/task-cloud.model';
import { NotificationCloudService } from '../../../services/notification-cloud.service';
import { TaskCloudEngineEvent } from '../../../models/engine-event-cloud.model';
import { IdentityUserService } from '../../../people/services/identity-user.service';
const TASK_EVENT_SUBSCRIPTION_QUERY = `
subscription {

View File

@ -19,7 +19,7 @@ import { DebugElement, SimpleChange } from '@angular/core';
import { By } from '@angular/platform-browser';
import { of } from 'rxjs';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IdentityUserService, setupTestBed } from '@alfresco/adf-core';
import { setupTestBed } from '@alfresco/adf-core';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { TaskFormCloudComponent } from './task-form-cloud.component';
import {
@ -32,6 +32,7 @@ import {
} from '../../start-task/models/task-details-cloud.model';
import { TaskCloudService } from '../../services/task-cloud.service';
import { TranslateModule } from '@ngx-translate/core';
import { IdentityUserService } from '../../../people/services/identity-user.service';
const taskDetails: TaskDetailsCloudModel = {
appName: 'simple-app',

View File

@ -25,11 +25,7 @@ import {
AppConfigServiceMock,
TranslationService,
TranslationMock,
CoreModule,
IdentityUserService,
IdentityUserServiceMock,
IdentityGroupService,
IdentityGroupServiceMock
CoreModule
} from '@alfresco/adf-core';
import { TranslateModule } from '@ngx-translate/core';
import { ProcessServicesCloudModule } from '../process-services-cloud.module';
@ -47,9 +43,7 @@ import { RouterTestingModule } from '@angular/router/testing';
providers: [
{ provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock },
{ provide: AppConfigService, useClass: AppConfigServiceMock },
{ provide: TranslationService, useClass: TranslationMock },
{ provide: IdentityUserService, useClass: IdentityUserServiceMock },
{ provide: IdentityGroupService, useClass: IdentityGroupServiceMock }
{ provide: TranslationService, useClass: TranslationMock }
],
exports: [
NoopAnimationsModule,