mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-26 17:24:56 +00:00
[ADF-3919] Add roles filtering to adf-cloud-group (#4238)
* [ADF-3919] Split group roles permission service into two * [ADF-3909] Added roles filtering title input * [ADF-3909] Added candate users and groups to task models * [ADF-3909] Used group component in start task * [ADF-3909] Updated docs * [ADF-3909] Added disabling for multi selection * [ADF-3909] Improved task creation logic * [ADF-3909] Changed assignee selection mechanism * * Added role filtering to groups* Updated unit tests to the recent changes* Created getGroupDetailsById and checkGroupHasGivenRole in groupService* Updated unit test to the groupService * * After rebase * * Changed method name checkGroupHasGivenRole to checkGroupHasRole * * After rebase
This commit is contained in:
parent
08cdb2f7c3
commit
3b455524b9
@ -5,7 +5,7 @@ Status: Experimental
|
||||
Last reviewed: 2019-01-15
|
||||
---
|
||||
|
||||
# [Group Cloud component](../../process-services-cloud/src/lib/group/components/group-cloud.component.ts "Defined in group-cloud.component.ts")
|
||||
# [Group Cloud component](../../lib/process-services-cloud/src/lib/group/components/group-cloud.component.ts "Defined in group-cloud.component.ts")
|
||||
|
||||
Searches Groups.
|
||||
|
||||
@ -28,14 +28,16 @@ Searches Groups.
|
||||
| ---- | ---- | ------------- | ----------- |
|
||||
| appName | `string` | | Name of the application. If specified this shows the users who have access to the app. |
|
||||
| mode | `string` | | User selection mode (single/multiple). |
|
||||
| preSelectGroups | [`GroupModel`](../../process-services-cloud/src/lib/group/models/group.model.ts)`[]` | \[] | Array of users to be pre-selected. This pre-selects all users in multi selection mode and only the first user of the array in single selection mode. |
|
||||
| preSelectGroups | [`GroupModel`](../../lib/process-services-cloud/src/lib/group/models/group.model.ts)`[]` | \[] | Array of users to be pre-selected. This pre-selects all users in multi selection mode and only the first user of the array in single selection mode. |
|
||||
| roles | `string[]` | | Role names of the groups to be listed. |
|
||||
| title | `string` | | Title of the field |
|
||||
|
||||
### Events
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| removeGroup | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`GroupModel`](../../process-services-cloud/src/lib/group/models/group.model.ts)`>` | Emitted when a group is removed. |
|
||||
| selectGroup | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`GroupModel`](../../process-services-cloud/src/lib/group/models/group.model.ts)`>` | Emitted when a group is selected. |
|
||||
| removeGroup | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`GroupModel`](../../lib/process-services-cloud/src/lib/group/models/group.model.ts)`>` | Emitted when a group is removed. |
|
||||
| selectGroup | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`GroupModel`](../../lib/process-services-cloud/src/lib/group/models/group.model.ts)`>` | Emitted when a group is selected. |
|
||||
|
||||
## Details
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<div class="adf-cloud-group">
|
||||
<mat-form-field>
|
||||
<mat-chip-list #groupChipList *ngIf="isMultipleMode()" data-automation-id="adf-cloud-group-chip-list" class="apa-group-chip-list">
|
||||
<mat-label>{{ (title || 'ADF_CLOUD_GROUPS.SEARCH-GROUP') | translate }}</mat-label>
|
||||
<mat-chip-list #groupChipList *ngIf="isMultipleMode()" [disabled]="isDisabled" data-automation-id="adf-cloud-group-chip-list" class="apa-group-chip-list">
|
||||
<mat-chip
|
||||
*ngFor="let group of selectedGroups$ | async"
|
||||
(removed)="onRemove(group)"
|
||||
@ -8,23 +9,26 @@
|
||||
{{group.name}}
|
||||
<mat-icon matChipRemove>cancel</mat-icon>
|
||||
</mat-chip>
|
||||
<input
|
||||
placeholder="{{ 'ADF_CLOUD_GROUPS.SEARCH-GROUP' | translate }}"
|
||||
<input matInput
|
||||
[formControl]="searchGroupsControl"
|
||||
class="adf-group-input"
|
||||
id="group-name"
|
||||
[disabled]="isDisabled"
|
||||
data-automation-id="adf-cloud-group-search-input"
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputFor]="groupChipList" #groupInput>
|
||||
</mat-chip-list>
|
||||
|
||||
<input *ngIf="!isMultipleMode()"
|
||||
matInput
|
||||
placeholder="{{ 'ADF_CLOUD_GROUPS.SEARCH-GROUP' | translate }}"
|
||||
class="adf-group-input"
|
||||
data-automation-id="adf-cloud-group-search-input"
|
||||
(focus)="setFocus(true)"
|
||||
(blur)="setFocus(false)"
|
||||
[formControl]="searchGroupsControl"
|
||||
[matAutocomplete]="auto" #groupInput>
|
||||
[matAutocomplete]="auto">
|
||||
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
@ -40,7 +44,7 @@
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
</mat-form-field>
|
||||
<div class="adf-cloud-group-error" *ngIf="hasError()">
|
||||
<div class="adf-cloud-group-error" *ngIf="hasErrorMessage()">
|
||||
<div fxLayout="row" fxLayoutAlign="start start" [@transitionMessages]="_subscriptAnimationState">
|
||||
<div class="adf-cloud-group-error-message">
|
||||
{{ 'ADF_CLOUD_GROUPS.ERROR.NOT_FOUND' | translate : { groupName : searchedValue } }}
|
||||
|
@ -34,7 +34,8 @@ describe('GroupCloudComponent', () => {
|
||||
let service: GroupCloudService;
|
||||
let findGroupsByNameSpy: jasmine.Spy;
|
||||
let getClientIdByApplicationNameSpy: jasmine.Spy;
|
||||
let checkGroupHasClientRoleMappingSpy: jasmine.Spy;
|
||||
let checkGroupHasAccessSpy: jasmine.Spy;
|
||||
let checkGroupHasGivenRoleSpy: jasmine.Spy;
|
||||
|
||||
setupTestBed({
|
||||
imports: [ProcessServiceCloudTestingModule, GroupCloudModule],
|
||||
@ -48,7 +49,8 @@ describe('GroupCloudComponent', () => {
|
||||
service = TestBed.get(GroupCloudService);
|
||||
findGroupsByNameSpy = spyOn(service, 'findGroupsByName').and.returnValue(of(mockGroups));
|
||||
getClientIdByApplicationNameSpy = spyOn(service, 'getClientIdByApplicationName').and.returnValue(of('mock-client-id'));
|
||||
checkGroupHasClientRoleMappingSpy = spyOn(service, 'checkGroupHasClientRoleMapping').and.returnValue(of(true));
|
||||
checkGroupHasAccessSpy = spyOn(service, 'checkGroupHasClientApp').and.returnValue(of(true));
|
||||
checkGroupHasGivenRoleSpy = spyOn(service, 'checkGroupHasRole').and.returnValue(of(true));
|
||||
component.appName = 'mock-app-name';
|
||||
});
|
||||
|
||||
@ -105,7 +107,7 @@ describe('GroupCloudComponent', () => {
|
||||
|
||||
it('should show an error message if the group is invalid', async(() => {
|
||||
fixture.detectChanges();
|
||||
checkGroupHasClientRoleMappingSpy.and.returnValue(of(false));
|
||||
checkGroupHasAccessSpy.and.returnValue(of(false));
|
||||
findGroupsByNameSpy.and.returnValue(of([]));
|
||||
fixture.detectChanges();
|
||||
const inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||
@ -212,7 +214,7 @@ describe('GroupCloudComponent', () => {
|
||||
}));
|
||||
|
||||
it('should not list groups who do not have access to the app when appName is specified', async(() => {
|
||||
checkGroupHasClientRoleMappingSpy.and.returnValue(of(false));
|
||||
checkGroupHasAccessSpy.and.returnValue(of(false));
|
||||
component.appName = 'sample-app';
|
||||
|
||||
fixture.detectChanges();
|
||||
@ -228,9 +230,43 @@ describe('GroupCloudComponent', () => {
|
||||
});
|
||||
}));
|
||||
|
||||
it('should filter groups when roles are specified', async(() => {
|
||||
checkGroupHasGivenRoleSpy.and.returnValue(of(true));
|
||||
component.roles = ['mock-role-1', 'mock-role-2'];
|
||||
fixture.detectChanges();
|
||||
let inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||
inputHTMLElement.focus();
|
||||
inputHTMLElement.value = 'M';
|
||||
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const groupsList = fixture.debugElement.queryAll(By.css('mat-option'));
|
||||
expect(groupsList.length).toBe(mockGroups.length);
|
||||
expect(checkGroupHasGivenRoleSpy).toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should return groups when roles are not specified', async(() => {
|
||||
checkGroupHasGivenRoleSpy.and.returnValue(of(false));
|
||||
component.roles = [];
|
||||
fixture.detectChanges();
|
||||
let inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||
inputHTMLElement.focus();
|
||||
inputHTMLElement.value = 'M';
|
||||
inputHTMLElement.dispatchEvent(new Event('input'));
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
const groupsList = fixture.debugElement.queryAll(By.css('mat-option'));
|
||||
expect(groupsList.length).toBe(mockGroups.length);
|
||||
expect(checkGroupHasGivenRoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should validate access to the app when appName is specified', async(() => {
|
||||
findGroupsByNameSpy.and.returnValue(of(mockGroups));
|
||||
checkGroupHasClientRoleMappingSpy.and.returnValue(of(true));
|
||||
checkGroupHasAccessSpy.and.returnValue(of(true));
|
||||
fixture.detectChanges();
|
||||
let inputHTMLElement: HTMLInputElement = <HTMLInputElement> element.querySelector('input');
|
||||
inputHTMLElement.focus();
|
||||
@ -239,7 +275,7 @@ describe('GroupCloudComponent', () => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(checkGroupHasClientRoleMappingSpy).toHaveBeenCalledTimes(mockGroups.length);
|
||||
expect(checkGroupHasAccessSpy).toHaveBeenCalledTimes(mockGroups.length);
|
||||
});
|
||||
}));
|
||||
|
||||
@ -252,7 +288,7 @@ describe('GroupCloudComponent', () => {
|
||||
fixture.detectChanges();
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
expect(checkGroupHasClientRoleMappingSpy).not.toHaveBeenCalled();
|
||||
expect(checkGroupHasAccessSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
@ -22,7 +22,7 @@ import { Observable, of, BehaviorSubject } from 'rxjs';
|
||||
import { GroupModel, GroupSearchParam } from '../models/group.model';
|
||||
import { GroupCloudService } from '../services/group-cloud.service';
|
||||
import { debounceTime } from 'rxjs/internal/operators/debounceTime';
|
||||
import { distinctUntilChanged, switchMap, flatMap, mergeMap, filter, tap } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, switchMap, mergeMap, filter, tap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-cloud-group',
|
||||
@ -44,12 +44,14 @@ export class GroupCloudComponent implements OnInit {
|
||||
static MODE_SINGLE = 'single';
|
||||
static MODE_MULTIPLE = 'multiple';
|
||||
|
||||
@ViewChild('groupInput') groupInput: ElementRef<HTMLInputElement>;
|
||||
|
||||
/** Name of the application. If specified this shows the users who have access to the app. */
|
||||
@Input()
|
||||
appName: string;
|
||||
|
||||
/** Title of the field */
|
||||
@Input()
|
||||
title: string;
|
||||
|
||||
/** User selection mode (single/multiple). */
|
||||
@Input()
|
||||
mode: string = GroupCloudComponent.MODE_SINGLE;
|
||||
@ -58,6 +60,10 @@ export class GroupCloudComponent implements OnInit {
|
||||
@Input()
|
||||
preSelectGroups: GroupModel[] = [];
|
||||
|
||||
/** Role names of the groups to be listed. */
|
||||
@Input()
|
||||
roles: string[] = [];
|
||||
|
||||
/** Emitted when a group is selected. */
|
||||
@Output()
|
||||
selectGroup: EventEmitter<GroupModel> = new EventEmitter<GroupModel>();
|
||||
@ -66,6 +72,9 @@ export class GroupCloudComponent implements OnInit {
|
||||
@Output()
|
||||
removeGroup: EventEmitter<GroupModel> = new EventEmitter<GroupModel>();
|
||||
|
||||
@ViewChild('groupInput')
|
||||
private groupInput: ElementRef<HTMLInputElement>;
|
||||
|
||||
private selectedGroups: GroupModel[] = [];
|
||||
|
||||
private searchGroups: GroupModel[] = [];
|
||||
@ -86,6 +95,10 @@ export class GroupCloudComponent implements OnInit {
|
||||
|
||||
searchedValue = '';
|
||||
|
||||
isFocused: boolean;
|
||||
|
||||
isDisabled: boolean;
|
||||
|
||||
constructor(private groupService: GroupCloudService) {
|
||||
this.selectedGroupsSubject = new BehaviorSubject<GroupModel[]>(this.selectedGroups);
|
||||
this.searchGroupsSubject = new BehaviorSubject<GroupModel[]>(this.searchGroups);
|
||||
@ -94,7 +107,10 @@ export class GroupCloudComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadPreSelectGroups();
|
||||
if (this.hasPreSelectGroups()) {
|
||||
this.loadPreSelectGroups();
|
||||
}
|
||||
|
||||
this.initSearch();
|
||||
|
||||
if (this.appName) {
|
||||
@ -112,6 +128,17 @@ export class GroupCloudComponent implements OnInit {
|
||||
|
||||
initSearch() {
|
||||
this.searchGroupsControl.valueChanges.pipe(
|
||||
filter((value) => {
|
||||
return typeof value === 'string';
|
||||
}),
|
||||
tap((value) => {
|
||||
this.searchedValue = value;
|
||||
if (value) {
|
||||
this.setError();
|
||||
} else {
|
||||
this.clearError();
|
||||
}
|
||||
}),
|
||||
debounceTime(500),
|
||||
distinctUntilChanged(),
|
||||
tap(() => {
|
||||
@ -119,75 +146,68 @@ export class GroupCloudComponent implements OnInit {
|
||||
}),
|
||||
switchMap((inputValue) => {
|
||||
const queryParams = this.createSearchParam(inputValue);
|
||||
return this.findGroupsByName(queryParams);
|
||||
return this.groupService.findGroupsByName(queryParams);
|
||||
}),
|
||||
mergeMap((groups) => {
|
||||
return groups;
|
||||
}),
|
||||
filter((group: any) => {
|
||||
return !this.isGroupAlreadySelected(group);
|
||||
}),
|
||||
mergeMap((group: any) => {
|
||||
if (this.clientId) {
|
||||
return this.checkGroupHasClientRoleMapping(group);
|
||||
if (this.appName) {
|
||||
return this.checkGroupHasAccess(group.id).pipe(
|
||||
mergeMap((hasRole) => {
|
||||
return hasRole ? of(group) : of();
|
||||
})
|
||||
);
|
||||
} else if (this.hasRoles()) {
|
||||
return this.filterGroupsByRoles(group);
|
||||
} else {
|
||||
return of(group);
|
||||
}
|
||||
})
|
||||
).subscribe((searchedGroup) => {
|
||||
this.searchGroups.push(searchedGroup);
|
||||
this.clearError();
|
||||
this.searchGroupsSubject.next(this.searchGroups);
|
||||
});
|
||||
}
|
||||
|
||||
findGroupsByName(searchParam: GroupSearchParam): Observable<GroupModel> {
|
||||
return this.groupService.findGroupsByName(searchParam).pipe(
|
||||
flatMap((groups: GroupModel[]) => {
|
||||
this.searchedValue = searchParam.name;
|
||||
if (this.searchedValue && !this.hasGroups(groups)) {
|
||||
this.setError();
|
||||
}
|
||||
return groups;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
checkGroupHasClientRoleMapping(group: GroupModel): Observable<GroupModel> {
|
||||
return this.groupService.checkGroupHasClientRoleMapping(group.id, this.clientId).pipe(
|
||||
mergeMap((hasRole: boolean) => {
|
||||
if (hasRole) {
|
||||
return of(group);
|
||||
} else {
|
||||
this.setError();
|
||||
return of();
|
||||
}
|
||||
})
|
||||
);
|
||||
checkGroupHasAccess(groupId: string): Observable<boolean> {
|
||||
if (this.hasRoles()) {
|
||||
return this.groupService.checkGroupHasAnyClientAppRole(groupId, this.clientId, this.roles);
|
||||
} else {
|
||||
return this.groupService.checkGroupHasClientApp(groupId, this.clientId);
|
||||
}
|
||||
}
|
||||
|
||||
isGroupAlreadySelected(group: GroupModel): boolean {
|
||||
if (this.hasGroups(this.selectedGroups)) {
|
||||
const result = this.selectedGroups.filter((selectedGroup: GroupModel) => {
|
||||
return selectedGroup.id === group.id;
|
||||
});
|
||||
if (this.hasGroups(result)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
const result = this.selectedGroups.find((selectedGroup: GroupModel) => {
|
||||
return selectedGroup.id === group.id;
|
||||
});
|
||||
|
||||
return !!result;
|
||||
}
|
||||
|
||||
private loadPreSelectGroups() {
|
||||
if (this.hasGroups(this.preSelectGroups)) {
|
||||
if (this.isMultipleMode()) {
|
||||
this.preSelectGroups.forEach((group: GroupModel) => {
|
||||
this.selectedGroups.push(group);
|
||||
});
|
||||
} else {
|
||||
this.searchGroupsControl.setValue(this.preSelectGroups[0]);
|
||||
this.onSelect(this.preSelectGroups[0]);
|
||||
}
|
||||
if (this.isMultipleMode()) {
|
||||
this.preSelectGroups.forEach((group: GroupModel) => {
|
||||
this.selectedGroups.push(group);
|
||||
});
|
||||
} else {
|
||||
this.searchGroupsControl.setValue(this.preSelectGroups[0]);
|
||||
this.onSelect(this.preSelectGroups[0]);
|
||||
}
|
||||
}
|
||||
|
||||
filterGroupsByRoles(group: GroupModel): Observable<GroupModel> {
|
||||
return this.groupService.checkGroupHasRole(group.id, this.roles).pipe(
|
||||
mergeMap((hasRole) => {
|
||||
return hasRole ? of(group) : of();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
onSelect(selectedGroup: GroupModel) {
|
||||
if (this.isMultipleMode()) {
|
||||
if (!this.isGroupAlreadySelected(selectedGroup)) {
|
||||
@ -226,42 +246,31 @@ export class GroupCloudComponent implements OnInit {
|
||||
return group ? group.name : '';
|
||||
}
|
||||
|
||||
private hasGroups(groups: GroupModel[]): boolean {
|
||||
return groups && groups.length > 0;
|
||||
private hasPreSelectGroups(): boolean {
|
||||
return this.preSelectGroups && this.preSelectGroups.length > 0;
|
||||
}
|
||||
|
||||
createSearchParam(value: any): GroupSearchParam {
|
||||
let queryParams: GroupSearchParam = { name: '' };
|
||||
if (this.isString(value)) {
|
||||
queryParams.name = value.trim();
|
||||
} else {
|
||||
queryParams.name = value.name.trim();
|
||||
}
|
||||
private createSearchParam(value: string): GroupSearchParam {
|
||||
const queryParams: GroupSearchParam = { name: value };
|
||||
return queryParams;
|
||||
}
|
||||
|
||||
isString(value: any): boolean {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
setValidationError() {
|
||||
if (this.hasGroups(this.searchGroups)) {
|
||||
this.clearError();
|
||||
} else {
|
||||
this.setError();
|
||||
}
|
||||
private hasRoles(): boolean {
|
||||
return this.roles && this.roles.length > 0;
|
||||
}
|
||||
|
||||
private disableSearch() {
|
||||
this.searchGroupsControl.disable();
|
||||
this.isDisabled = true;
|
||||
}
|
||||
|
||||
private enableSearch() {
|
||||
this.searchGroupsControl.enable();
|
||||
this.isDisabled = false;
|
||||
}
|
||||
|
||||
private setError() {
|
||||
this.searchGroupsControl.setErrors({invalid: true});
|
||||
this.searchGroupsControl.setErrors({ invalid: true });
|
||||
}
|
||||
|
||||
private clearError() {
|
||||
@ -271,4 +280,12 @@ export class GroupCloudComponent implements OnInit {
|
||||
hasError(): boolean {
|
||||
return this.searchGroupsControl && this.searchGroupsControl.errors && this.searchGroupsControl.errors.invalid;
|
||||
}
|
||||
|
||||
setFocus(isFocused: boolean) {
|
||||
this.isFocused = isFocused;
|
||||
}
|
||||
|
||||
hasErrorMessage(): boolean {
|
||||
return !this.isFocused && this.hasError();
|
||||
}
|
||||
}
|
||||
|
@ -102,3 +102,9 @@ export let applicationDetailsMockApi = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export let mockGroup = new GroupModel({
|
||||
id: 'mock-id', name: 'Mock Group', path: '/mock', realmRoles: ['MOCK-ADMIN-ROLE', 'MOCK-USER-ROLE', 'MOCK-ROLE-1']
|
||||
});
|
||||
|
||||
export let clientRoles = [ 'MOCK-ADMIN-ROLE', 'MOCK-USER-ROLE'];
|
||||
|
@ -33,9 +33,13 @@ import {
|
||||
mockApiError,
|
||||
mockError,
|
||||
roleMappingApi,
|
||||
noRoleMappingApi
|
||||
noRoleMappingApi,
|
||||
mockGroup,
|
||||
clientRoles
|
||||
} from '../mock/group-cloud.mock';
|
||||
import { GroupSearchParam } from '../models/group.model';
|
||||
import { GroupSearchParam, GroupModel } from '../models/group.model';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { throwError, of } from 'rxjs';
|
||||
|
||||
describe('GroupCloudService', () => {
|
||||
let service: GroupCloudService;
|
||||
@ -73,7 +77,7 @@ describe('GroupCloudService', () => {
|
||||
|
||||
it('should return true if group has client role mapping', (done) => {
|
||||
spyOn(apiService, 'getInstance').and.returnValue(roleMappingApi);
|
||||
service.checkGroupHasClientRoleMapping('mock-group-id', 'mock-app-id').subscribe((hasRole) => {
|
||||
service.checkGroupHasClientApp('mock-group-id', 'mock-app-id').subscribe((hasRole) => {
|
||||
expect(hasRole).toBeDefined();
|
||||
expect(hasRole).toBe(true);
|
||||
done();
|
||||
@ -82,13 +86,147 @@ describe('GroupCloudService', () => {
|
||||
|
||||
it('should return false if group does not have client role mapping', (done) => {
|
||||
spyOn(apiService, 'getInstance').and.returnValue(noRoleMappingApi);
|
||||
service.checkGroupHasClientRoleMapping('mock-group-id', 'mock-app-id').subscribe((hasRole) => {
|
||||
service.checkGroupHasClientApp('mock-group-id', 'mock-app-id').subscribe((hasRole) => {
|
||||
expect(hasRole).toBeDefined();
|
||||
expect(hasRole).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fetch group by userId', (done) => {
|
||||
spyOn(service, 'getGroupDetailsById').and.returnValue(of(mockGroup));
|
||||
service.getGroupDetailsById('mock-group-id').subscribe(
|
||||
(res: GroupModel) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.name).toEqual('Mock Group');
|
||||
expect(res.realmRoles).toEqual(mockGroup.realmRoles);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('Should not fetch group if error occurred', (done) => {
|
||||
const errorResponse = new HttpErrorResponse({
|
||||
error: 'Mock Error',
|
||||
status: 404, statusText: 'Not Found'
|
||||
});
|
||||
|
||||
spyOn(service, 'getGroupDetailsById').and.returnValue(throwError(errorResponse));
|
||||
|
||||
service.getGroupDetailsById('mock-group-id')
|
||||
.subscribe(
|
||||
() => {
|
||||
fail('expected an error, not group');
|
||||
},
|
||||
(error) => {
|
||||
expect(error.status).toEqual(404);
|
||||
expect(error.statusText).toEqual('Not Found');
|
||||
expect(error.error).toEqual('Mock Error');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true if group has given role', (done) => {
|
||||
spyOn(service, 'getGroupDetailsById').and.returnValue(of(mockGroup));
|
||||
service.checkGroupHasRole('mock-group-id', ['MOCK-ADMIN-ROLE']).subscribe(
|
||||
(res: boolean) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false if group does not have given role', (done) => {
|
||||
spyOn(service, 'getGroupDetailsById').and.returnValue(of(mockGroup));
|
||||
service.checkGroupHasRole('mock-group-id', ['MOCK-ADMIN-MODELER']).subscribe(
|
||||
(res: boolean) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res).toBeFalsy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should fetch client roles by groupId and clientId', (done) => {
|
||||
spyOn(service, 'getClientRoles').and.returnValue(of(clientRoles));
|
||||
service.getClientRoles('mock-group-id', 'mock-client-id').subscribe(
|
||||
(res: any) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res.length).toEqual(2);
|
||||
expect(res).toEqual(clientRoles);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('Should not fetch client roles if error occurred', (done) => {
|
||||
const errorResponse = new HttpErrorResponse({
|
||||
error: 'Mock Error',
|
||||
status: 404, statusText: 'Not Found'
|
||||
});
|
||||
|
||||
spyOn(service, 'getClientRoles').and.returnValue(throwError(errorResponse));
|
||||
|
||||
service.getClientRoles('mock-group-id', 'mock-client-id')
|
||||
.subscribe(
|
||||
() => {
|
||||
fail('expected an error, not client roles');
|
||||
},
|
||||
(error) => {
|
||||
expect(error.status).toEqual(404);
|
||||
expect(error.statusText).toEqual('Not Found');
|
||||
expect(error.error).toEqual('Mock Error');
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true if group has client access', (done) => {
|
||||
spyOn(service, 'getClientRoles').and.returnValue(of(clientRoles));
|
||||
service.checkGroupHasClientApp('mock-group-id', 'mock-client-id').subscribe(
|
||||
(res: boolean) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false if group does not have client access', (done) => {
|
||||
spyOn(service, 'getClientRoles').and.returnValue(of([]));
|
||||
service.checkGroupHasClientApp('mock-group-id', 'mock-client-id').subscribe(
|
||||
(res: boolean) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res).toBeFalsy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true if group has any client role', (done) => {
|
||||
spyOn(service, 'checkGroupHasAnyClientAppRole').and.returnValue(of(true));
|
||||
service.checkGroupHasAnyClientAppRole('mock-group-id', 'mock-client-id', ['MOCK-USER-ROLE']).subscribe(
|
||||
(res: boolean) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res).toBeTruthy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false if group does not have any client role', (done) => {
|
||||
spyOn(service, 'getClientRoles').and.returnValue(of([]));
|
||||
service.checkGroupHasAnyClientAppRole('mock-group-id', 'mock-client-id', ['MOCK-ADMIN-MODELER']).subscribe(
|
||||
(res: boolean) => {
|
||||
expect(res).toBeDefined();
|
||||
expect(res).toBeFalsy();
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should append to the call all the parameters', (done) => {
|
||||
spyOn(apiService, 'getInstance').and.returnValue(returnCallQueryParameters);
|
||||
service.findGroupsByName(<GroupSearchParam> {name: 'mock'}).subscribe((res) => {
|
||||
|
@ -20,7 +20,7 @@ import { from, of, Observable, throwError } from 'rxjs';
|
||||
import { map, catchError } from 'rxjs/operators';
|
||||
|
||||
import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core';
|
||||
import { GroupSearchParam } from '../models/group.model';
|
||||
import { GroupSearchParam, GroupModel } from '../models/group.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -50,6 +50,40 @@ export class GroupCloudService {
|
||||
);
|
||||
}
|
||||
|
||||
getGroupDetailsById(groupId: string) {
|
||||
const url = this.getGroupsApi() + '/' + groupId;
|
||||
const httpMethod = 'GET', pathParams = {}, queryParams = {}, bodyParam = {}, headerParams = {},
|
||||
formParams = {}, contentTypes = ['application/json'], accepts = ['application/json'];
|
||||
|
||||
return (from(this.apiService.getInstance().oauth2Auth.callCustomApi(
|
||||
url, httpMethod, pathParams, queryParams,
|
||||
headerParams, formParams, bodyParam,
|
||||
contentTypes, accepts, Object, null, null)
|
||||
)).pipe(
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
checkGroupHasRole(groupId: string, roleNames: string[]): Observable<boolean> {
|
||||
return this.getGroupDetailsById(groupId).pipe(map((response: GroupModel) => {
|
||||
let availableGroupRoles = [];
|
||||
let hasRole = false;
|
||||
availableGroupRoles = response.realmRoles;
|
||||
if (availableGroupRoles && availableGroupRoles.length > 0) {
|
||||
roleNames.forEach((roleName: string) => {
|
||||
const role = availableGroupRoles.find((availableRole) => {
|
||||
return roleName === availableRole;
|
||||
});
|
||||
if (role) {
|
||||
hasRole = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
return hasRole;
|
||||
}));
|
||||
}
|
||||
|
||||
getClientIdByApplicationName(applicationName: string): Observable<string> {
|
||||
const url = this.getApplicationIdApi();
|
||||
const httpMethod = 'GET', pathParams = {}, queryParams = {clientId: applicationName}, bodyParam = {}, headerParams = {}, formParams = {},
|
||||
@ -67,7 +101,7 @@ export class GroupCloudService {
|
||||
);
|
||||
}
|
||||
|
||||
checkGroupHasClientRoleMapping(groupId: string, clientId: string): Observable<any> {
|
||||
getClientRoles(groupId: string, clientId: string): Observable<any[]> {
|
||||
const url = this.groupClientRoleMappingApi(groupId, clientId);
|
||||
const httpMethod = 'GET', pathParams = {}, queryParams = {}, bodyParam = {}, headerParams = {},
|
||||
formParams = {}, contentTypes = ['application/json'], accepts = ['application/json'];
|
||||
@ -76,7 +110,11 @@ export class GroupCloudService {
|
||||
url, httpMethod, pathParams, queryParams,
|
||||
headerParams, formParams, bodyParam,
|
||||
contentTypes, accepts, Object, null, null)
|
||||
).pipe(
|
||||
);
|
||||
}
|
||||
|
||||
checkGroupHasClientApp(groupId: string, clientId: string): Observable<boolean> {
|
||||
return this.getClientRoles(groupId, clientId).pipe(
|
||||
map((response: any[]) => {
|
||||
if (response && response.length > 0) {
|
||||
return true;
|
||||
@ -87,6 +125,28 @@ export class GroupCloudService {
|
||||
);
|
||||
}
|
||||
|
||||
checkGroupHasAnyClientAppRole(groupId: string, clientId: string, roleNames: string[]): Observable<boolean> {
|
||||
return this.getClientRoles(groupId, clientId).pipe(
|
||||
map((clientRoles: any[]) => {
|
||||
let hasRole = false;
|
||||
if (clientRoles.length > 0) {
|
||||
roleNames.forEach((roleName) => {
|
||||
const role = clientRoles.find((availableRole) => {
|
||||
return availableRole.name === roleName;
|
||||
});
|
||||
|
||||
if (role) {
|
||||
hasRole = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
return hasRole;
|
||||
}),
|
||||
catchError((err) => this.handleError(err))
|
||||
);
|
||||
}
|
||||
|
||||
private groupClientRoleMappingApi(groupId: string, clientId: string): any {
|
||||
return `${this.appConfigService.get('identityHost')}/groups/${groupId}/role-mappings/clients/${clientId}`;
|
||||
}
|
||||
|
@ -49,6 +49,7 @@
|
||||
"DESCRIPTION": "Description",
|
||||
"ATTACHFORM": "Attach Form",
|
||||
"ASSIGNEE": "Assignee",
|
||||
"CANDIDATE_GROUP": "Candidate Group",
|
||||
"FORM": "Form",
|
||||
"DATE": "Choose Date"
|
||||
},
|
||||
|
@ -60,11 +60,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</mat-form-field>
|
||||
<adf-cloud-people fxFlex #peopleInput *ngIf="currentUser" [appName]="appName" [preSelectUsers]="[currentUser]" (selectUser)="onAssigneeSelect($event)" (removeUser)="onRemoveUser()"></adf-cloud-people>
|
||||
<adf-cloud-people fxFlex #peopleInput *ngIf="currentUser"
|
||||
[appName]="appName"
|
||||
[preSelectUsers]="[currentUser]"
|
||||
(selectUser)="onAssigneeSelect($event)"
|
||||
(removeUser)="onAssigneeRemove()"></adf-cloud-people>
|
||||
</div>
|
||||
|
||||
<div fxLayout="row" fxLayout.lt-md="column" fxLayoutGap="20px" fxLayoutGap.lt-md="0px">
|
||||
<adf-cloud-group fxFlex #groupInput *ngIf="currentUser" [mode]="'multiple'" [appName]="appName" (selectGroup)="onSelectGroup($event)" (removeGroup)="onRemoveGroup($event)"></adf-cloud-group>
|
||||
<div class="input-row" fxLayout="row" fxLayout.lt-md="column" fxLayoutGap="20px" fxLayoutGap.lt-md="0px">
|
||||
<adf-cloud-group fxFlex #groupInput *ngIf="currentUser"
|
||||
[mode]="'multiple'"
|
||||
[title]="'ADF_CLOUD_TASK_LIST.START_TASK.FORM.LABEL.CANDIDATE_GROUP'"
|
||||
[appName]="appName"
|
||||
(selectGroup)="onCandiateGroupSelect($event)"
|
||||
(removeGroup)="onCandiateGroupRemove($event)"></adf-cloud-group>
|
||||
<div fxFlex></div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
@ -80,7 +89,7 @@
|
||||
</button>
|
||||
<button
|
||||
color="primary"
|
||||
type="submit" [disabled]="dateError || !taskForm.valid || submitted || assignee.hasError()"
|
||||
type="submit" [disabled]="dateError || !taskForm.valid || submitted || assignee.hasError() || candidateGroups.hasError()"
|
||||
mat-button
|
||||
id="button-start">
|
||||
{{'ADF_CLOUD_TASK_LIST.START_TASK.FORM.ACTION.START'|translate}}
|
||||
|
@ -31,6 +31,7 @@ import {
|
||||
UserPreferenceValues
|
||||
} from '@alfresco/adf-core';
|
||||
import { PeopleCloudComponent } from './people-cloud/people-cloud.component';
|
||||
import { GroupCloudComponent } from '../../../../lib/group/public-api';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-cloud-start-task',
|
||||
@ -75,6 +76,9 @@ export class StartTaskCloudComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('peopleInput')
|
||||
assignee: PeopleCloudComponent;
|
||||
|
||||
@ViewChild('groupInput')
|
||||
candidateGroups: GroupCloudComponent;
|
||||
|
||||
users$: Observable<any[]>;
|
||||
|
||||
taskId: string;
|
||||
@ -84,7 +88,8 @@ export class StartTaskCloudComponent implements OnInit, OnDestroy {
|
||||
submitted = false;
|
||||
|
||||
assigneeName: string;
|
||||
candidateGroups: string [] = [];
|
||||
|
||||
candidateGroupNames: string[] = [];
|
||||
|
||||
dateError: boolean;
|
||||
|
||||
@ -143,11 +148,10 @@ export class StartTaskCloudComponent implements OnInit, OnDestroy {
|
||||
public saveTask() {
|
||||
this.submitted = true;
|
||||
const newTask = Object.assign(this.taskForm.value);
|
||||
newTask.appName = this.getAppName();
|
||||
newTask.dueDate = this.getDueDate();
|
||||
newTask.appName = this.appName;
|
||||
newTask.dueDate = this.dueDate;
|
||||
newTask.assignee = this.assigneeName;
|
||||
|
||||
newTask.candidateGroups = this.candidateGroups;
|
||||
newTask.candidateGroups = this.candidateGroupNames;
|
||||
this.createNewTask(new TaskDetailsCloudModel(newTask));
|
||||
}
|
||||
|
||||
@ -169,14 +173,6 @@ export class StartTaskCloudComponent implements OnInit, OnDestroy {
|
||||
this.cancel.emit();
|
||||
}
|
||||
|
||||
private getDueDate(): Date {
|
||||
return this.dueDate;
|
||||
}
|
||||
|
||||
private getAppName(): string {
|
||||
return this.appName ? this.appName : '';
|
||||
}
|
||||
|
||||
onDateChanged(newDateValue) {
|
||||
this.dateError = false;
|
||||
|
||||
@ -192,18 +188,20 @@ export class StartTaskCloudComponent implements OnInit, OnDestroy {
|
||||
this.assigneeName = assignee ? assignee.username : '';
|
||||
}
|
||||
|
||||
onRemoveUser() {
|
||||
this.assigneeName = null;
|
||||
onAssigneeRemove() {
|
||||
this.assigneeName = '';
|
||||
}
|
||||
|
||||
onSelectGroup(group) {
|
||||
this.candidateGroups.push(group.name);
|
||||
onCandiateGroupSelect(candidateGroup: any) {
|
||||
if (candidateGroup.name) {
|
||||
this.candidateGroupNames.push(candidateGroup.name);
|
||||
}
|
||||
}
|
||||
|
||||
onRemoveGroup(group) {
|
||||
this.candidateGroups = this.candidateGroups.filter( (name) => {
|
||||
return name !== group.name;
|
||||
});
|
||||
onCandiateGroupRemove(candidateGroup: any) {
|
||||
if (candidateGroup.name) {
|
||||
this.candidateGroupNames = this.candidateGroupNames.filter((name: string) => { return name !== candidateGroup.name; });
|
||||
}
|
||||
}
|
||||
|
||||
public whitespaceValidator(control: FormControl) {
|
||||
|
@ -19,9 +19,10 @@ export class StartTaskCloudRequestModel {
|
||||
name: string;
|
||||
description: string;
|
||||
assignee: string;
|
||||
candidateGroups: string [];
|
||||
priority: string;
|
||||
dueDate: Date;
|
||||
candidateUsers: string[];
|
||||
candidateGroups: string[];
|
||||
payloadType: string;
|
||||
|
||||
constructor(obj?: any) {
|
||||
@ -31,6 +32,7 @@ export class StartTaskCloudRequestModel {
|
||||
this.assignee = obj.assignee || null;
|
||||
this.priority = obj.priority || null;
|
||||
this.dueDate = obj.dueDate || null;
|
||||
this.candidateUsers = obj.candidateUsers || null;
|
||||
this.candidateGroups = obj.candidateGroups || null;
|
||||
this.payloadType = 'CreateTaskPayload';
|
||||
}
|
||||
|
@ -38,7 +38,8 @@ export class TaskDetailsCloudModel {
|
||||
processInstanceId: string;
|
||||
status: string;
|
||||
standAlone: boolean;
|
||||
candidateGroups: string [];
|
||||
candidateUsers: string[];
|
||||
candidateGroups: string[];
|
||||
managerOfCandidateGroup: boolean;
|
||||
memberOfCandidateGroup: boolean;
|
||||
memberOfCandidateUsers: boolean;
|
||||
@ -65,6 +66,7 @@ export class TaskDetailsCloudModel {
|
||||
this.processInstanceId = obj.processInstanceId || null;
|
||||
this.status = obj.status || null;
|
||||
this.standAlone = obj.standAlone || null;
|
||||
this.candidateUsers = obj.candidateUsers || null;
|
||||
this.candidateGroups = obj.candidateGroups || null;
|
||||
this.managerOfCandidateGroup = obj.managerOfCandidateGroup || null;
|
||||
this.memberOfCandidateGroup = obj.memberOfCandidateGroup || null;
|
||||
|
@ -43,7 +43,8 @@ import { GroupCloudModule } from '../../group/group-cloud.module';
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
GroupCloudModule,
|
||||
FormModule
|
||||
FormModule,
|
||||
GroupCloudModule
|
||||
],
|
||||
declarations: [StartTaskCloudComponent, PeopleCloudComponent],
|
||||
providers: [
|
||||
|
Loading…
x
Reference in New Issue
Block a user