TSK-1328: Fixed accessItemManagement, which did not show the searched ids accessItems

This commit is contained in:
Tristan Eisermann 2020-07-13 11:59:09 +02:00 committed by Tristan2357
parent abced36ecd
commit 0e83073202
7 changed files with 139 additions and 145 deletions

View File

@ -1,18 +1,23 @@
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-header">Access items management</h4>
</div>
<div class="panel-body">
<div class="col-md-6 col-md-offset-3 margin">
<taskana-shared-type-ahead #accesId="ngModel" name="accessIdSelected" [(ngModel)]="accessIdSelected" placeHolderMessage="Search for access id..."
(selectedItem)="onSelectAccessId($event)" displayError=true></taskana-shared-type-ahead>
</div>
<div *ngIf="!AccessItemsForm" class="center-block no-detail col-xs-12">
<!--No Id Placeholder-->
<div *ngIf="!accessItemsForm" class="center-block no-detail col-xs-12">
<h3 class="grey">Select an access id</h3>
<svg-icon class="empty-icon" src="./assets/icons/users.svg"></svg-icon>
</div>
<div *ngIf="AccessItemsForm" class="row col-xs-12">
<form [formGroup]="AccessItemsForm">
<!--Content-->
<div *ngIf="accessItemsForm" class="row col-xs-12">
<form [formGroup]="accessItemsForm">
<!--Table With AccessIds-->
<table id="table-access-items" class="table table-striped table-center">
<thead>
<tr>
@ -33,10 +38,14 @@
</ng-container>
</tr>
<tr>
<th colspan="2" class="text-align"><input type="text" formControlName="workbasketKeyFilter" (keyup.enter)="searchForAccessItemsWorkbaskets()"
class="form-control" placeholder="Workbasket filter"></th>
<th class="text-align"><input type="text" formControlName="accessIdFilter" (keyup.enter)="searchForAccessItemsWorkbaskets()"
class="form-control" placeholder="Access id filter"></th>
<th colspan="2" class="text-align"><label>
<input type="text" formControlName="workbasketKeyFilter" (keyup.enter)="searchForAccessItemsWorkbaskets()"
class="form-control" placeholder="Workbasket filter">
</label></th>
<th class="text-align"><label>
<input type="text" formControlName="accessIdFilter" (keyup.enter)="searchForAccessItemsWorkbaskets()"
class="form-control" placeholder="Access id filter">
</label></th>
<th>
<button type="button" (click)="searchForAccessItemsWorkbaskets()" class="btn btn-default" data-toggle="tooltip"
title="Search">
@ -63,7 +72,7 @@
</tr>
</thead>
<tbody formArrayName="accessItemsGroups">
<tr *ngFor="let accessItem of accessItemsGroups.controls; let index = index;" [formGroupName]="index">
<tr *ngFor="let accessItem of accessItemsGroups.controls; let index = index;" [formGroupName]="index.toString()">
<td colspan="2">
<label class="wrap">{{accessItem.value.workbasketKey}}</label>
</td>
@ -77,8 +86,10 @@
<ng-template #accessIdInput>
<td colspan="2" class="text-align text-width">
<div>
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}">
<label>
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}">
</label>
</div>
</td>
</ng-template>
@ -111,35 +122,16 @@
</tr>
</tbody>
</table>
<button *ngIf="!isGroup" class="btn btn-primary pull-left btn-group" type="button"
<!--Belonging Groups Button-->
<button class="btn btn-primary pull-left btn-group" type="button"
data-toggle="modal"
data-target="#myModal">
Belonging groups
</button>
<div class="modal" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Belonging groups</h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<ul *ngIf="belongingGroups && belongingGroups.length > 0 " class="list-group">
<li *ngFor="let group of belongingGroups" class="list-group-item">{{group.name}}</li>
</ul>
<p *ngIf="!belongingGroups">The user is not associated to
any groups</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!--Revoke Access Button-->
<div class="pull-right btn-group">
<button *ngIf="AccessItemsForm" type="button" (click)="revokeAccess()" class="btn btn-default" data-toggle="tooltip"
title="Revoke access" [disabled]=isGroup>
<button *ngIf="accessItemsForm" type="button" (click)="revokeAccess()" class="btn btn-default" data-toggle="tooltip"
title="Revoke access" [disabled]=isGroup>
<span class="material-icons md-20 red">clear</span>
</button>
</div>
@ -147,3 +139,24 @@
</div>
</div>
</div>
<div class="modal" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Belonging groups</h4>
<button type="button" class="close" data-dismiss="modal">&times;</button>
</div>
<div class="modal-body">
<ul *ngIf="groups && groups.length > 0; else no" class="list-group">
<li *ngFor="let group of groups" class="list-group-item">{{group.name}}</li>
</ul>
<ng-template #no>The user is not associated to
any groups</ng-template>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

View File

@ -27,8 +27,9 @@ describe('AccessItemsManagementComponent', () => {
fixture = testBed.createComponent(AccessItemsManagementComponent);
component = fixture.componentInstance;
accessIdsService = testBed.get(AccessIdsService);
spyOn(accessIdsService, 'getAccessItemsPermissions').and.returnValue(of(new Array<AccessIdDefinition>()));
spyOn(accessIdsService, 'getAccessItemsInformation').and.returnValue(of(new AccessItemWorkbasketResource()));
spyOn(accessIdsService, 'getAccessItems').and.returnValue(of(new Array<AccessIdDefinition>()));
spyOn(accessIdsService, 'searchForAccessId').and.returnValue(of(new AccessItemWorkbasketResource()));
spyOn(accessIdsService, 'getGroupsByAccessId').and.returnValue(of(new AccessItemWorkbasketResource()));
fixture.detectChanges();
done();
});

View File

@ -1,21 +1,24 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { Select } from '@ngxs/store';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable, Subscription } from 'rxjs';
import { Observable } from 'rxjs';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
import { AccessItemWorkbasketResource } from 'app/shared/models/access-item-workbasket-resource';
import { AccessItemWorkbasket } from 'app/shared/models/access-item-workbasket';
import { Sorting } from 'app/shared/models/sorting';
import { Direction, Sorting } from 'app/shared/models/sorting';
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
import { take } from 'rxjs/operators';
import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service';
import { AccessIdsService } from '../../../shared/services/access-ids/access-ids.service';
import { AccessIdDefinition } from '../../../shared/models/access-id';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { AccessItemsCustomisation, CustomField, getCustomFields } from '../../../shared/models/customisation';
import { AccessItemsCustomisation,
CustomField,
getCustomFields } from '../../../shared/models/customisation';
import { customFieldCount } from '../../../shared/models/workbasket-access-items';
@Component({
@ -23,21 +26,17 @@ import { customFieldCount } from '../../../shared/models/workbasket-access-items
templateUrl: './access-items-management.component.html',
styleUrls: ['./access-items-management.component.scss']
})
export class AccessItemsManagementComponent implements OnInit, OnDestroy {
export class AccessItemsManagementComponent implements OnInit {
accessIdSelected;
accessIdPrevious;
AccessItemsForm: FormGroup;
toogleValidationAccessIdMap = new Map<number, boolean>();
accessItemPermissionsSubscription: Subscription;
accessItemInformationsubscription: Subscription;
accessIdsWithGroups: Array<AccessIdDefinition>;
belongingGroups: Array<AccessIdDefinition>;
sortingFields = new Map([['workbasket-key', 'Workbasket Key'], ['access-id', 'Access id']]);
accessItemDefaultSortBy: string = 'workbasket-key';
sortModel: Sorting = new Sorting(this.accessItemDefaultSortBy);
isGroup: boolean;
groupsKey = 'ou=groups';
accessItemsForm: FormGroup;
toggleValidationAccessIdMap = new Map<number, boolean>();
accessId: AccessIdDefinition;
groups: AccessIdDefinition[];
sortingFields = new Map([['access-id', 'Access id'], ['workbasket-key', 'Workbasket Key']]);
sortModel: Sorting = new Sorting('access-id', Direction.DESC);
isGroup: boolean = false;
@Select(EngineConfigurationSelectors.accessItemsCustomisation) accessItemsCustomization$: Observable<AccessItemsCustomisation>;
customFields$: Observable<CustomField[]>;
@ -46,18 +45,11 @@ export class AccessItemsManagementComponent implements OnInit, OnDestroy {
private accessIdsService: AccessIdsService,
private formsValidatorService: FormsValidatorService,
private requestInProgressService: RequestInProgressService,
private notificationService: NotificationService,
private notificationsService: NotificationService) {
private notificationService: NotificationService) {
}
get accessItemsGroups(): FormArray {
return this.AccessItemsForm ? this.AccessItemsForm.get('accessItemsGroups') as FormArray : null;
}
private static unSubscribe(subscription: Subscription): void {
if (subscription) {
subscription.unsubscribe();
}
return this.accessItemsForm ? this.accessItemsForm.get('accessItemsGroups') as FormArray : null;
}
ngOnInit() {
@ -73,37 +65,36 @@ export class AccessItemsManagementComponent implements OnInit, OnDestroy {
});
});
const AccessItemsFormArray = this.formBuilder.array(AccessItemsFormGroups);
if (!this.AccessItemsForm) {
this.AccessItemsForm = this.formBuilder.group({});
if (!this.accessItemsForm) {
this.accessItemsForm = this.formBuilder.group({});
}
this.AccessItemsForm.setControl('accessItemsGroups', AccessItemsFormArray);
if (!this.AccessItemsForm.value.workbasketKeyFilter) {
this.AccessItemsForm.addControl('workbasketKeyFilter', new FormControl());
this.accessItemsForm.setControl('accessItemsGroups', AccessItemsFormArray);
if (!this.accessItemsForm.value.workbasketKeyFilter) {
this.accessItemsForm.addControl('workbasketKeyFilter', new FormControl());
}
if (!this.AccessItemsForm.value.accessIdFilter) {
this.AccessItemsForm.addControl('accessIdFilter', new FormControl());
if (!this.accessItemsForm.value.accessIdFilter) {
this.accessItemsForm.addControl('accessIdFilter', new FormControl());
}
}
onSelectAccessId(selected: AccessIdDefinition) {
if (!selected) {
this.AccessItemsForm = null;
this.accessItemsForm = null;
return;
}
if (!this.AccessItemsForm || this.accessIdPrevious !== selected.accessId) {
this.accessIdPrevious = selected.accessId;
this.isGroup = selected.accessId.includes(this.groupsKey);
AccessItemsManagementComponent.unSubscribe(this.accessItemInformationsubscription);
this.accessItemInformationsubscription = this.accessIdsService.getAccessItemsInformation(selected.accessId, true)
.subscribe((accessIdsWithGroups: Array<AccessIdDefinition>) => {
this.accessIdsWithGroups = accessIdsWithGroups;
this.belongingGroups = accessIdsWithGroups.filter(item => item.accessId.includes(this.groupsKey));
if (this.accessIdPrevious !== selected.accessId) {
this.accessIdPrevious = selected.accessId;
this.accessIdsService.getGroupsByAccessId(selected.accessId)
.pipe(take(1)).subscribe((groups: AccessIdDefinition[]) => {
this.accessId = selected;
this.groups = groups;
this.searchForAccessItemsWorkbaskets();
},
error => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationsService.triggerError(NOTIFICATION_TYPES.FETCH_ERR, error);
this.notificationService.triggerError(NOTIFICATION_TYPES.FETCH_ERR, error);
});
}
}
@ -119,22 +110,18 @@ export class AccessItemsManagementComponent implements OnInit, OnDestroy {
searchForAccessItemsWorkbaskets() {
this.requestInProgressService.setRequestInProgress(true);
AccessItemsManagementComponent.unSubscribe(this.accessItemPermissionsSubscription);
this.accessItemPermissionsSubscription = this.accessIdsService.getAccessItemsPermissions(
this.accessIdsWithGroups,
this.AccessItemsForm ? this.AccessItemsForm.value.accessIdFilter : undefined,
this.AccessItemsForm ? this.AccessItemsForm.value.workbasketKeyFilter : undefined,
this.sortModel,
true
)
.subscribe((accessItemsResource: AccessItemWorkbasketResource) => {
this.setAccessItemsGroups(accessItemsResource ? accessItemsResource.accessItems : []);
this.requestInProgressService.setRequestInProgress(false);
},
error => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationsService.triggerError(NOTIFICATION_TYPES.FETCH_ERR_2, error);
});
this.accessIdsService.getAccessItems(
[this.accessId, ...this.groups],
this.accessItemsForm ? this.accessItemsForm.value.accessIdFilter : undefined,
this.accessItemsForm ? this.accessItemsForm.value.workbasketKeyFilter : undefined,
this.sortModel
).pipe(take(1)).subscribe((accessItemsResource: AccessItemWorkbasketResource) => {
this.setAccessItemsGroups(accessItemsResource ? accessItemsResource.accessItems : []);
this.requestInProgressService.setRequestInProgress(false);
}, error => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationService.triggerError(NOTIFICATION_TYPES.FETCH_ERR_2, error);
});
}
revokeAccess() {
@ -146,25 +133,20 @@ export class AccessItemsManagementComponent implements OnInit, OnDestroy {
);
}
ngOnDestroy(): void {
AccessItemsManagementComponent.unSubscribe(this.accessItemPermissionsSubscription);
AccessItemsManagementComponent.unSubscribe(this.accessItemInformationsubscription);
}
private onRemoveConfirmed() {
this.requestInProgressService.setRequestInProgress(true);
this.accessIdsService.removeAccessItemsPermissions(this.accessIdSelected)
.subscribe(
response => {
.pipe(take(1)).subscribe(
() => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationsService.showToast(
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT, new Map<string, string>([['accessId', this.accessIdSelected]])
);
this.searchForAccessItemsWorkbaskets();
},
error => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationsService.triggerError(NOTIFICATION_TYPES.DELETE_ERR, error);
this.notificationService.triggerError(NOTIFICATION_TYPES.DELETE_ERR, error);
}
);
}

View File

@ -21,6 +21,7 @@ import { EngineConfigurationSelectors } from 'app/shared/store/engine-configurat
import { WorkbasketAccessItemsComponent } from './workbasket-access-items.component';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { AccessItemWorkbasketResource } from '../../../shared/models/access-item-workbasket-resource';
describe('WorkbasketAccessItemsComponent', () => {
let component: WorkbasketAccessItemsComponent;
@ -78,9 +79,8 @@ describe('WorkbasketAccessItemsComponent', () => {
spyOn(notificationsService, 'showToast').and.returnValue(of(true));
debugElement = fixture.debugElement.nativeElement;
accessIdsService = testBed.get(AccessIdsService);
spyOn(accessIdsService, 'getAccessItemsInformation').and.returnValue(of(new Array<string>(
'accessID1', 'accessID2'
)));
spyOn(accessIdsService, 'searchForAccessId').and.returnValue(of(['accessID1', 'accessID2']));
spyOn(accessIdsService, 'getGroupsByAccessId').and.returnValue(of(['accessID1', 'accessID2']));
formsValidatorService = testBed.get(FormsValidatorService);
component.ngOnChanges({
active: new SimpleChange(undefined, 'accessItems', true)

View File

@ -1,11 +1,10 @@
import { Component,
OnInit,
Input,
ViewChild,
forwardRef,
Output,
EventEmitter,
OnChanges, ElementRef, AfterViewInit } from '@angular/core';
ElementRef, AfterViewInit } from '@angular/core';
import { Observable } from 'rxjs';
import { TypeaheadMatch } from 'ngx-bootstrap/typeahead';
@ -114,15 +113,15 @@ export class TypeAheadComponent implements AfterViewInit, ControlValueAccessor {
this.dataSource = new Observable((observer: any) => {
observer.next(this.value);
}).pipe(mergeMap((token: string) => this.getUsersAsObservable(token)));
this.accessIdsService.getAccessItemsInformation(this.value).subscribe(items => {
this.accessIdsService.searchForAccessId(this.value).subscribe(items => {
if (items.length > 0) {
this.dataSource.selected = items.find(item => item.accessId.toLowerCase() === this.value.toLowerCase());
}
});
}
getUsersAsObservable(token: string): Observable<any> {
return this.accessIdsService.getAccessItemsInformation(token);
getUsersAsObservable(accessId: string): Observable<any> {
return this.accessIdsService.searchForAccessId(accessId);
}
typeaheadOnSelect(event: TypeaheadMatch): void {

View File

@ -13,39 +13,38 @@ import { QueryParameters } from 'app/shared/models/query-parameters';
})
export class AccessIdsService {
private url = `${environment.taskanaRestUrl}/v1/access-ids`;
private accessItemsRef: Observable<AccessItemWorkbasketResource> = new Observable();
constructor(
private httpClient: HttpClient
) { }
getAccessItemsInformation(token: string, searchInGroups = false): Observable<Array<AccessIdDefinition>> {
if (!token || token.length < 3) {
searchForAccessId(accessId: string): Observable<AccessIdDefinition[]> {
if (!accessId || accessId.length < 3) {
return of([]);
}
if (searchInGroups) {
return this.httpClient.get<Array<AccessIdDefinition>>(`${this.url}/groups?access-id=${token}`);
}
return this.httpClient.get<Array<AccessIdDefinition>>(`${this.url}?search-for=${token}`);
return this.httpClient.get<AccessIdDefinition[]>(`${this.url}?search-for=${accessId}`);
}
getAccessItemsPermissions(
accessIds: Array<AccessIdDefinition>,
getGroupsByAccessId(accessId: string): Observable<AccessIdDefinition[]> {
if (!accessId || accessId.length < 3) {
return of([]);
}
return this.httpClient.get<AccessIdDefinition[]>(`${this.url}/groups?access-id=${accessId}`);
}
getAccessItems(
accessIds: AccessIdDefinition[],
accessIdLike?: string,
workbasketKeyLike?: string,
sortModel: Sorting = new Sorting('workbasket-key'),
forceRequest: boolean = false
sortModel: Sorting = new Sorting('workbasket-key')
): Observable<AccessItemWorkbasketResource> {
if (forceRequest || !this.accessItemsRef) {
this.accessItemsRef = this.httpClient.get<AccessItemWorkbasketResource>(encodeURI(
`${environment.taskanaRestUrl}/v1/workbasket-access-items/${TaskanaQueryParameters.getQueryParameters(
this.accessIdsParameters(sortModel,
accessIds,
accessIdLike,
workbasketKeyLike)
)}`
));
}
return this.accessItemsRef;
return this.httpClient.get<AccessItemWorkbasketResource>(encodeURI(
`${environment.taskanaRestUrl}/v1/workbasket-access-items/${TaskanaQueryParameters.getQueryParameters(
AccessIdsService.accessIdsParameters(sortModel,
accessIds,
accessIdLike,
workbasketKeyLike)
)}`
));
}
removeAccessItemsPermissions(accessId: string) {
@ -53,12 +52,12 @@ export class AccessIdsService {
.delete<AccessItemWorkbasketResource>(`${environment.taskanaRestUrl}/v1/workbasket-access-items/?access-id=${accessId}`);
}
private accessIdsParameters(
private static accessIdsParameters(
sortModel: Sorting,
accessIds: Array<AccessIdDefinition>,
accessIds: AccessIdDefinition[],
accessIdLike?: string,
workbasketKeyLike?: string
): QueryParameters {
): QueryParameters { // TODO extend this query for support of multiple sortbys
const parameters = new QueryParameters();
parameters.SORTBY = sortModel.sortBy;
parameters.SORTDIRECTION = sortModel.sortDirection;

View File

@ -20,7 +20,7 @@ export class FormsValidatorService {
if (!form) {
return false;
}
const forFieldsPromise = new Promise((resolve, reject) => {
const forFieldsPromise = new Promise(resolve => {
Object.keys(form.form.controls).forEach(control => {
if (control.indexOf('owner') === -1 && form.form.controls[control].invalid) {
const validationState = toogleValidationMap.get(control);
@ -31,10 +31,10 @@ export class FormsValidatorService {
resolve(validSync);
});
const ownerPromise = new Promise((resolve, reject) => {
const ownerPromise = new Promise(resolve => {
const ownerString = 'owner';
if (form.form.controls[this.workbasketOwner]) {
this.accessIdsService.getAccessItemsInformation(form.form.controls[this.workbasketOwner].value).subscribe(items => {
this.accessIdsService.searchForAccessId(form.form.controls[this.workbasketOwner].value).subscribe(items => {
const validationState = toogleValidationMap.get(this.workbasketOwner);
toogleValidationMap.set(this.workbasketOwner, !validationState);
const valid = items.find(item => item.accessId === form.form.controls[this.workbasketOwner].value);
@ -66,10 +66,10 @@ export class FormsValidatorService {
const ownerPromise: Array<Promise<boolean>> = new Array<Promise<boolean>>();
for (let i = 0; i < form.length; i++) {
ownerPromise.push(new Promise((resolve, reject) => {
ownerPromise.push(new Promise(resolve => {
const validationState = toogleValidationAccessIdMap.get(i);
toogleValidationAccessIdMap.set(i, !validationState);
this.accessIdsService.getAccessItemsInformation(form.controls[i].value.accessId).subscribe(items => {
this.accessIdsService.searchForAccessId(form.controls[i].value.accessId).subscribe(items => {
resolve(new ResponseOwner({ valid: items.length > 0, field: 'access id' }));
});
}));
@ -85,7 +85,7 @@ export class FormsValidatorService {
if (!result) {
this.notificationsService.showToast(
NOTIFICATION_TYPES.WARNING_ALERT_2,
new Map<string, string>([['owner', responseOwner.field]])
new Map<string, string>([['owner', responseOwner ? responseOwner.field : 'owner']])
);
}
return result;
@ -103,7 +103,7 @@ export class FormsValidatorService {
}
}
function ResponseOwner(obj) {
this.valid = obj.valid;
this.field = obj.field;
function ResponseOwner(owner) {
this.valid = owner.valid;
this.field = owner.field;
}