TSK-1216: Storing access items management states in NGXS store (#1198)

* TSK-1216: Initalized access items management store

* TSK-1216: Implemented logic in state

* TSK-1216: update row height, fixed access items groups not showing up

* TSK-1216: Initalized access items management store

* TSK-1216: Implemented logic in state

* TSK-1216: update row height, fixed access items groups not showing up

* TSK-1216: Optimized code for revoking access
This commit is contained in:
Chi Nguyen 2020-07-28 11:04:56 +02:00 committed by GitHub
parent 04729b86f8
commit 1bc7490b87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 344 additions and 188 deletions

View File

@ -6,131 +6,149 @@
<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>
<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>
<!--No Id Placeholder-->
<!-- 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>
<!--Content-->
<!-- CONTENT -->
<div *ngIf="accessItemsForm" class="row col-xs-12">
<form [formGroup]="accessItemsForm">
<!--Table With AccessIds-->
<!-- TABLE WITH ACCESS ID -->
<table id="table-access-items" class="table table-striped table-center">
<thead>
<tr>
<th>
<taskana-shared-sort [sortingFields]="sortingFields" (performSorting)="sorting($event)" menuPosition="left" [defaultSortBy]="accessItemDefaultSortBy"></taskana-shared-sort>
<!-- TABLE FIRST ROW -->
<tr>
<th>
<taskana-shared-sort [sortingFields]="sortingFields"
(performSorting)="sorting($event)"
menuPosition="left">
</taskana-shared-sort>
</th>
<th class="text-align min-width">Workbasket Key</th>
<th colspan="2" class="text-align">Access Id</th>
<th>Read</th>
<th>Open</th>
<th>Append</th>
<th>Transfer</th>
<th>Distribute</th>
<ng-container *ngFor="let customField of customFields$ | async">
<th *ngIf="customField.visible">
{{customField.field}}
</th>
<th class="text-align min-width">Workbasket Key</th>
<th colspan="2" class="text-align">Access Id</th>
<th>Read</th>
<th>Open</th>
<th>Append</th>
<th>Transfer</th>
<th>Distribute</th>
<ng-container *ngFor="let customField of customFields$ | async">
<th *ngIf="customField.visible">
{{customField.field}}
</th>
</ng-container>
</tr>
<tr>
<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">
<span class="material-icons md-20 blue">search</span>
</button>
</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</ng-container>
</tr>
<!-- TABLE SECOND ROW -->
<tr>
<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">
<span class="material-icons md-20 blue">search</span>
</button>
</th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<!-- ACCESS ITEMS GROUPS -->
<tbody formArrayName="accessItemsGroups">
<tr *ngFor="let accessItem of accessItemsGroups.controls; let index = index;" [formGroupName]="index.toString()">
<td colspan="2">
<label class="wrap">{{accessItem.value.workbasketKey}}</label>
</td>
<td *ngIf="(accessItemsCustomization$ | async)?.accessId.lookupField else accessIdInput" colspan="2" class="text-align text-width taskana-type-ahead">
<tr class="table__access-item-groups" *ngFor="let accessItem of accessItemsGroups.controls; let index = index;"
[formGroupName]="index.toString()">
<td colspan="2">
<label class="wrap">{{accessItem.value.workbasketKey}}</label>
</td>
<td *ngIf="(accessItemsCustomization$ | async)?.accessId.lookupField else accessIdInput" colspan="2"
class="text-align text-width taskana-type-ahead" style="padding-top: 0;">
<div>
<taskana-shared-type-ahead formControlName="accessId" placeHolderMessage="* Access id is required"
[validationValue]="toggleValidationAccessIdMap.get(index)"
[displayError]="!isFieldValid('accessItem.value.accessId', index)"
[disable]=true></taskana-shared-type-ahead>
</div>
</td>
<ng-template #accessIdInput>
<td colspan="2" class="text-align text-width">
<div>
<taskana-shared-type-ahead formControlName="accessId" placeHolderMessage="* Access id is required"
[validationValue]="toggleValidationAccessIdMap.get(index)" [displayError]="!isFieldValid('accessItem.value.accessId', index)"
[disable]=true></taskana-shared-type-ahead>
<label>
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}">
</label>
</div>
</td>
<ng-template #accessIdInput>
<td colspan="2" class="text-align text-width">
<div>
<label>
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}">
</label>
</div>
</td>
</ng-template>
<td>
<input id="checkbox-{{index}}-0" type="checkbox" formControlName="permRead" class="regular-checkbox">
<label for="checkbox-{{index}}-0"></label>
</ng-template>
<td>
<input id="checkbox-{{index}}-0" type="checkbox" formControlName="permRead" class="regular-checkbox">
<label for="checkbox-{{index}}-0"></label>
</td>
<td>
<input id="checkbox-{{index}}-1" type="checkbox" formControlName="permOpen">
<label for="checkbox-{{index}}-1"></label>
</td>
<td>
<input id="checkbox-{{index}}-2" type="checkbox" formControlName="permAppend">
<label for="checkbox-{{index}}-2"></label>
</td>
<td>
<input id="checkbox-{{index}}-3" type="checkbox" formControlName="permTransfer">
<label for="checkbox-{{index}}-3"></label>
</td>
<td>
<input id="checkbox-{{index}}-4" type="checkbox" formControlName="permDistribute">
<label for="checkbox-{{index}}-4"></label>
</td>
<ng-container *ngFor="let customField of customFields$ | async; let customIndex = index">
<td *ngIf="customField.visible">
<input id="checkbox-{{index}}-{{customIndex + 5}}" type="checkbox"
formControlName="permCustom{{customIndex + 1}}">
<label for="checkbox-{{index}}-{{customIndex + 5}}"></label>
</td>
<td>
<input id="checkbox-{{index}}-1" type="checkbox" formControlName="permOpen">
<label for="checkbox-{{index}}-1"></label>
</td>
<td>
<input id="checkbox-{{index}}-2" type="checkbox" formControlName="permAppend">
<label for="checkbox-{{index}}-2"></label>
</td>
<td>
<input id="checkbox-{{index}}-3" type="checkbox" formControlName="permTransfer">
<label for="checkbox-{{index}}-3"></label>
</td>
<td>
<input id="checkbox-{{index}}-4" type="checkbox" formControlName="permDistribute">
<label for="checkbox-{{index}}-4"></label>
</td>
<ng-container *ngFor="let customField of customFields$ | async; let customIndex = index">
<td *ngIf="customField.visible">
<input id="checkbox-{{index}}-{{customIndex + 5}}" type="checkbox" formControlName="permCustom{{customIndex + 1}}">
<label for="checkbox-{{index}}-{{customIndex + 5}}"></label>
</td>
</ng-container>
</tr>
</ng-container>
</tr>
</tbody>
</table>
<!--Belonging Groups Button-->
<!-- BELONGING GROUPS BUTTONS -->
<button class="btn btn-primary pull-left btn-group" type="button"
data-toggle="modal"
data-target="#myModal">
Belonging groups
</button>
<!--Revoke Access Button-->
<!-- REVOKE ACCESS BUTTON -->
<div class="pull-right btn-group">
<button *ngIf="accessItemsForm" type="button" (click)="revokeAccess()" class="btn btn-default" data-toggle="tooltip"
<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>
@ -152,7 +170,8 @@
<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>
any groups
</ng-template>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>

View File

@ -37,3 +37,7 @@ td {
font-weight: bold;
display: inline;
}
.table__access-item-groups {
height: 80px;
}

View File

@ -1,23 +1,25 @@
import { Component, OnInit } from '@angular/core';
import { Select } from '@ngxs/store';
import { Select, Store } from '@ngxs/store';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import { Observable, Subject } 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 { 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 { takeUntil } 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 { customFieldCount } from '../../../shared/models/workbasket-access-items';
import {
GetAccessItems,
GetGroupsByAccessId,
RemoveAccessItemsPermissions
} from '../../../shared/store/access-items-management-store/access-items-management.actions';
import { AccessItemsManagementSelector } from '../../../shared/store/access-items-management-store/access-items-management.selector';
@Component({
selector: 'taskana-administration-access-items-management',
@ -25,8 +27,8 @@ import { customFieldCount } from '../../../shared/models/workbasket-access-items
styleUrls: ['./access-items-management.component.scss']
})
export class AccessItemsManagementComponent implements OnInit {
accessIdSelected;
accessIdPrevious;
accessIdSelected: string;
accessIdPrevious: string;
accessItemsForm: FormGroup;
toggleValidationAccessIdMap = new Map<number, boolean>();
@ -42,22 +44,57 @@ export class AccessItemsManagementComponent implements OnInit {
@Select(EngineConfigurationSelectors.accessItemsCustomisation) accessItemsCustomization$: Observable<
AccessItemsCustomisation
>;
@Select(AccessItemsManagementSelector.groups) groups$: Observable<AccessIdDefinition[]>;
customFields$: Observable<CustomField[]>;
destroy$ = new Subject<void>();
constructor(
private formBuilder: FormBuilder,
private accessIdsService: AccessIdsService,
private formsValidatorService: FormsValidatorService,
private requestInProgressService: RequestInProgressService,
private notificationService: NotificationService
private notificationService: NotificationService,
private store: Store
) {}
get accessItemsGroups(): FormArray {
return this.accessItemsForm ? (this.accessItemsForm.get('accessItemsGroups') as FormArray) : null;
}
ngOnInit() {
this.customFields$ = this.accessItemsCustomization$.pipe(getCustomFields(customFieldCount));
this.groups$.pipe(takeUntil(this.destroy$)).subscribe((groups) => {
this.groups = groups;
});
}
onSelectAccessId(selected: AccessIdDefinition) {
if (selected) {
this.accessId = selected;
if (this.accessIdPrevious !== selected.accessId) {
this.accessIdPrevious = selected.accessId;
this.store.dispatch(new GetGroupsByAccessId(selected.accessId)).subscribe(() => {
this.searchForAccessItemsWorkbaskets();
});
}
} else {
this.accessItemsForm = null;
}
}
searchForAccessItemsWorkbaskets() {
this.store
.dispatch(
new GetAccessItems(
[this.accessId, ...this.groups],
this.accessItemsForm ? this.accessItemsForm.value.accessIdFilter : undefined,
this.accessItemsForm ? this.accessItemsForm.value.workbasketKeyFilter : undefined,
this.sortModel
)
)
.subscribe((state) => {
this.setAccessItemsGroups(
state['accessItemsManagement'].accessItemsResource
? state['accessItemsManagement'].accessItemsResource.accessItems
: []
);
});
}
setAccessItemsGroups(accessItems: Array<AccessItemWorkbasket>) {
@ -81,30 +118,19 @@ export class AccessItemsManagementComponent implements OnInit {
}
}
onSelectAccessId(selected: AccessIdDefinition) {
if (!selected) {
this.accessItemsForm = null;
return;
}
revokeAccess() {
this.notificationService.showDialog(
`You are going to delete all access related: ${this.accessIdSelected}. Can you confirm this action?`,
() => {
this.store.dispatch(new RemoveAccessItemsPermissions(this.accessIdSelected)).subscribe(() => {
this.searchForAccessItemsWorkbaskets();
});
}
);
}
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.notificationService.triggerError(NOTIFICATION_TYPES.FETCH_ERR, error);
}
);
}
get accessItemsGroups(): FormArray {
return this.accessItemsForm ? (this.accessItemsForm.get('accessItemsGroups') as FormArray) : null;
}
isFieldValid(field: string, index: number): boolean {
@ -116,53 +142,8 @@ export class AccessItemsManagementComponent implements OnInit {
this.searchForAccessItemsWorkbaskets();
}
searchForAccessItemsWorkbaskets() {
this.requestInProgressService.setRequestInProgress(true);
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() {
this.notificationService.showDialog(
`You are going to delete all access related: ${this.accessIdSelected}. Can you confirm this action?`,
this.onRemoveConfirmed.bind(this)
);
}
private onRemoveConfirmed() {
this.requestInProgressService.setRequestInProgress(true);
this.accessIdsService
.removeAccessItemsPermissions(this.accessIdSelected)
.pipe(take(1))
.subscribe(
() => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT,
new Map<string, string>([['accessId', this.accessIdSelected]])
);
this.searchForAccessItemsWorkbaskets();
},
(error) => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationService.triggerError(NOTIFICATION_TYPES.DELETE_ERR, error);
}
);
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -0,0 +1,27 @@
import { AccessIdDefinition } from '../../models/access-id';
import { Sorting } from '../../models/sorting';
export class SelectAccessId {
static readonly type = '[Access Items Management] Select access ID';
constructor(public accessIdDefinition: AccessIdDefinition) {}
}
export class GetGroupsByAccessId {
static readonly type = '[Access Items Management] Get groups by access ID';
constructor(public accessId: string) {}
}
export class GetAccessItems {
static readonly type = '[Access Items Management] Get access items';
constructor(
public accessIds: AccessIdDefinition[],
public accessIdLike?: string,
public workbasketKeyLike?: string,
public sortModel: Sorting = new Sorting('workbasket-key')
) {}
}
export class RemoveAccessItemsPermissions {
static readonly type = "[Access Items Management] Remove access items' permissions";
constructor(public accessId: string) {}
}

View File

@ -0,0 +1,10 @@
import { Selector } from '@ngxs/store';
import { AccessItemsManagementState, AccessItemsManagementStateModel } from './access-items-management.state';
import { AccessIdDefinition } from '../../models/access-id';
export class AccessItemsManagementSelector {
@Selector([AccessItemsManagementState])
static groups(state: AccessItemsManagementStateModel): AccessIdDefinition[] {
return state.groups;
}
}

View File

@ -0,0 +1,114 @@
import { Action, NgxsAfterBootstrap, State, StateContext } from '@ngxs/store';
import {
GetAccessItems,
GetGroupsByAccessId,
RemoveAccessItemsPermissions,
SelectAccessId
} from './access-items-management.actions';
import { Observable, of } from 'rxjs';
import { AccessIdsService } from '../../services/access-ids/access-ids.service';
import { take, tap } from 'rxjs/operators';
import { AccessIdDefinition } from '../../models/access-id';
import { NOTIFICATION_TYPES } from '../../models/notifications';
import { NotificationService } from '../../services/notifications/notification.service';
import { AccessItemWorkbasketResource } from '../../models/access-item-workbasket-resource';
import { RequestInProgressService } from '../../services/request-in-progress/request-in-progress.service';
class InitializeStore {
static readonly type = '[Access Items Management] Initializing state';
}
@State<AccessItemsManagementStateModel>({ name: 'accessItemsManagement' })
export class AccessItemsManagementState implements NgxsAfterBootstrap {
constructor(
private accessIdsService: AccessIdsService,
private notificationService: NotificationService,
private requestInProgressService: RequestInProgressService
) {}
@Action(SelectAccessId)
selectAccessId(ctx: StateContext<AccessItemsManagementStateModel>, action: SelectAccessId): Observable<any> {
const selectedAccessId = action.accessIdDefinition;
ctx.patchState({
selectedAccessId
});
return of(null);
}
@Action(GetGroupsByAccessId)
getGroupsByAccessId(
ctx: StateContext<AccessItemsManagementStateModel>,
action: GetGroupsByAccessId
): Observable<any> {
return this.accessIdsService.getGroupsByAccessId(action.accessId).pipe(
take(1),
tap(
(groups: AccessIdDefinition[]) => {
ctx.patchState({
groups
});
},
(error) => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationService.triggerError(NOTIFICATION_TYPES.FETCH_ERR, error);
}
)
);
}
@Action(GetAccessItems)
getAccessItems(ctx: StateContext<AccessItemsManagementStateModel>, action: GetAccessItems): Observable<any> {
this.requestInProgressService.setRequestInProgress(true);
return this.accessIdsService
.getAccessItems(action.accessIds, action.accessIdLike, action.workbasketKeyLike, action.sortModel)
.pipe(
take(1),
tap(
(accessItemsResource: AccessItemWorkbasketResource) => {
this.requestInProgressService.setRequestInProgress(false);
ctx.patchState({
accessItemsResource
});
},
(error) => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationService.triggerError(NOTIFICATION_TYPES.FETCH_ERR_2, error);
}
)
);
}
@Action(RemoveAccessItemsPermissions)
removeAccessItemsPermissions(
ctx: StateContext<AccessItemsManagementStateModel>,
action: RemoveAccessItemsPermissions
): Observable<any> {
this.requestInProgressService.setRequestInProgress(true);
return this.accessIdsService.removeAccessItemsPermissions(action.accessId).pipe(
take(1),
tap(
() => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT,
new Map<string, string>([['accessId', action.accessId]])
);
},
(error) => {
this.requestInProgressService.setRequestInProgress(false);
this.notificationService.triggerError(NOTIFICATION_TYPES.DELETE_ERR, error);
}
)
);
}
ngxsAfterBootstrap(ctx?: StateContext<any>): void {
ctx.dispatch(new InitializeStore());
}
}
export interface AccessItemsManagementStateModel {
accessItemsResource: AccessItemWorkbasketResource;
selectedAccessId: AccessIdDefinition;
groups: AccessIdDefinition[];
}

View File

@ -1,5 +1,6 @@
import { EngineConfigurationState } from './engine-configuration-store/engine-configuration.state';
import { ClassificationState } from './classification-store/classification.state';
import { WorkbasketState } from './workbasket-store/workbasket.state';
import { AccessItemsManagementState } from './access-items-management-store/access-items-management.state';
export const STATES = [EngineConfigurationState, ClassificationState, WorkbasketState];
export const STATES = [EngineConfigurationState, ClassificationState, WorkbasketState, AccessItemsManagementState];