This is an automated email from the ASF dual-hosted git repository. hshpak pushed a commit to branch fix/DATALAB-2863/group-name-validation in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git
commit 1a6f310ba6e733286c08b82c7071b23b3d0440d1 Author: Hennadii_Shpak <[email protected]> AuthorDate: Tue Aug 23 12:50:52 2022 +0300 added directive for validating --- .../app/administration/administration.module.ts | 5 +- .../webapp/src/app/administration/roles/index.ts | 14 +++--- .../app/administration/roles/roles.component.html | 2 + .../app/administration/roles/roles.component.ts | 30 +++++++----- .../webapp/src/app/core/directives/index.ts | 19 ++++++-- .../directives/is-group-name-unique.directive.ts | 54 ++++++++++++++++++++++ .../models/role.model.ts} | 35 ++++++++------ .../app/core/services/rolesManagement.service.ts | 11 +---- 8 files changed, 126 insertions(+), 44 deletions(-) diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts b/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts index dfbbbf7fd..73bed3c3a 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts @@ -25,10 +25,11 @@ import { ProjectModule } from './project'; import { RolesModule } from './roles'; import {ConfigurationModule} from './configuration'; import {OdahuModule} from './odahu'; +import { DirectivesModule } from '../core/directives'; @NgModule({ - imports: [CommonModule, ManagenementModule, ProjectModule, RolesModule, ConfigurationModule, OdahuModule], + imports: [ CommonModule, ManagenementModule, ProjectModule, RolesModule, ConfigurationModule, OdahuModule ], declarations: [], - exports: [ManagenementModule, ProjectModule, RolesModule, ConfigurationModule, OdahuModule] + exports: [ ManagenementModule, ProjectModule, RolesModule, ConfigurationModule, OdahuModule ] }) export class AdministrationModule { } diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/roles/index.ts b/services/self-service/src/main/resources/webapp/src/app/administration/roles/index.ts index 28c16e255..a3e3984a7 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/roles/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/roles/index.ts @@ -26,15 +26,17 @@ import { FormControlsModule } from '../../shared/form-controls'; import { RolesComponent, ConfirmDeleteUserAccountDialogComponent } from './roles.component'; import { GroupNameValidationDirective } from './group-name-validarion.directive'; import {InformMessageModule} from '../../shared/inform-message'; +import { DirectivesModule } from '../../core/directives'; @NgModule({ imports: [ - CommonModule, - FormsModule, - ReactiveFormsModule, - MaterialModule, - FormControlsModule, - InformMessageModule + CommonModule, + FormsModule, + ReactiveFormsModule, + MaterialModule, + FormControlsModule, + InformMessageModule, + DirectivesModule ], declarations: [RolesComponent, ConfirmDeleteUserAccountDialogComponent, GroupNameValidationDirective], entryComponents: [ConfirmDeleteUserAccountDialogComponent], diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html index 451085bb0..c7dd44dbb 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.html @@ -42,10 +42,12 @@ <div class="inner-step mat-reset"> <input [validator]="groupValidation()" + isGroupNameUnique type="text" placeholder="Enter group name" [(ngModel)]="setupGroup" #setupGroupName="ngModel" + #groupName /> <div class="error" *ngIf="setupGroupName.errors?.patterns && setupGroupName.dirty"> Group name can only contain letters, numbers, hyphens and '_'. diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts index 9739eaf86..573e8266d 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/roles/roles.component.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Component, OnInit, Output, EventEmitter, Inject } from '@angular/core'; -import { ValidatorFn, FormControl } from '@angular/forms'; +import { Component, OnInit, Output, EventEmitter, Inject, ViewChild, AfterViewInit, ElementRef } from '@angular/core'; +import { ValidatorFn, FormControl, NgModel } from '@angular/forms'; import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { ToastrService } from 'ngx-toastr'; @@ -27,16 +27,21 @@ import { CheckUtils, SortUtils } from '../../core/util'; import { DICTIONARY } from '../../../dictionary/global.dictionary'; import { ProgressBarService } from '../../core/services/progress-bar.service'; import {ConfirmationDialogComponent, ConfirmationDialogType} from '../../shared'; +import { debounceTime, map, switchMap, take, tap } from 'rxjs/operators'; +import { fromEvent } from 'rxjs'; +import { GroupModel, Role } from '../../core/models/role.model'; @Component({ selector: 'datalab-roles', templateUrl: './roles.component.html', styleUrls: ['../../resources/resources-grid/resources-grid.component.scss', './roles.component.scss'] }) -export class RolesComponent implements OnInit { +export class RolesComponent implements OnInit, AfterViewInit { readonly DICTIONARY = DICTIONARY; @Output() manageRolesGroupAction: EventEmitter<{}> = new EventEmitter(); + @ViewChild('groupName') qwe: ElementRef; + @ViewChild('setupGroupName') setupGroupName: NgModel; private startedGroups: Array<any>; @@ -53,6 +58,7 @@ export class RolesComponent implements OnInit { public groupnamePattern = new RegExp(/^[a-zA-Z0-9_\-]+$/); public noPermissionMessage: string = 'You have not permissions for groups which are not assigned to your projects.'; public maxGroupLength: number = 25; + public isGroupNameDuplicated: boolean = false; stepperView: boolean = false; displayedColumns: string[] = ['name', 'roles', 'users', 'actions']; @@ -71,6 +77,14 @@ export class RolesComponent implements OnInit { this.getEnvironmentHealthStatus(); } + ngAfterViewInit() { + if (this.qwe) { + fromEvent(this.qwe.nativeElement, 'keyup').pipe( + debounceTime(500) + ).subscribe(({target}) => console.log(target.value)); + } + } + openManageRolesDialog() { this.progressBarService.startProgressBar(); this.rolesService.getGroupsData().subscribe(groups => { @@ -240,11 +254,11 @@ export class RolesComponent implements OnInit { } public extractIds(sourceList, target) { - const map = new Map(); + const mapObj = new Map(); const mapped = sourceList.reduce((acc, item) => { target.includes(item.description) && acc.set(item._id, item.description); return acc; - }, map); + }, mapObj); return this.mapToObj(mapped); } @@ -264,16 +278,10 @@ export class RolesComponent implements OnInit { } public groupValidation(): ValidatorFn { - const duplicateList: any = this.groupsData.map(item => item.group.toLowerCase()); return <ValidatorFn>((control: FormControl) => { if (control.value && control.value.length > this.maxGroupLength) { return { long: true }; } - - if (control.value && duplicateList.includes(CheckUtils.delimitersFiltering(control.value.toLowerCase()))) { - return { duplicate: true }; - } - if (control.value && !this.groupnamePattern.test(control.value)) return { patterns: true }; diff --git a/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts index 40b54483b..57f767a92 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts @@ -24,11 +24,24 @@ import { ClickOutsideDirective } from './click-outside.directive'; import { ScrollDirective } from './scrollTo.directive'; import { IsEndpointsActiveDirective } from './is-endpoint-active.directive'; import { ClickedOutsideMatSelectDirective } from './click-outside-with-material-select.directive'; +import { IsGroupNameUniqueDirective } from './is-group-name-unique.directive'; @NgModule({ - imports: [CommonModule], - declarations: [ClickOutsideDirective, ScrollDirective, IsEndpointsActiveDirective, ClickedOutsideMatSelectDirective], - exports: [ClickOutsideDirective, ScrollDirective, IsEndpointsActiveDirective, ClickedOutsideMatSelectDirective] + imports: [ CommonModule ], + declarations: [ + ClickOutsideDirective, + ScrollDirective, + IsEndpointsActiveDirective, + ClickedOutsideMatSelectDirective, + IsGroupNameUniqueDirective + ], + exports: [ + ClickOutsideDirective, + ScrollDirective, + IsEndpointsActiveDirective, + ClickedOutsideMatSelectDirective, + IsGroupNameUniqueDirective + ] }) export class DirectivesModule { } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/directives/is-group-name-unique.directive.ts b/services/self-service/src/main/resources/webapp/src/app/core/directives/is-group-name-unique.directive.ts new file mode 100644 index 000000000..84f15aca0 --- /dev/null +++ b/services/self-service/src/main/resources/webapp/src/app/core/directives/is-group-name-unique.directive.ts @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 { Directive } from '@angular/core'; +import { AbstractControl, NG_ASYNC_VALIDATORS, ValidationErrors, Validator } from '@angular/forms'; +import { Observable, of } from 'rxjs'; +import { catchError, map, switchMap, take } from 'rxjs/operators'; +import { RolesGroupsService } from '../services'; +import { GroupModel } from '../models/role.model'; +import 'rxjs-compat/add/observable/timer'; + + +@Directive({ + selector: '[isGroupNameUnique]', + providers: [{ provide: NG_ASYNC_VALIDATORS, useExisting: IsGroupNameUniqueDirective, multi: true }] +}) +export class IsGroupNameUniqueDirective implements Validator { + constructor( private rolesService: RolesGroupsService ) { + } + validate(control: AbstractControl): Observable<ValidationErrors | null> { + return Observable.timer(300).pipe( + switchMap( () => { + return this.rolesService.getGroupsData().pipe( + map(res => this.isGroupExist(res, control.value)), + map(isGroupExist => (isGroupExist ? { duplicate: true } : null)), + catchError(() => of(null)), + take(1) + ); + }) + ); + } + + private isGroupExist(groupList: GroupModel[], comparedValue: string): boolean { + return groupList.some(({group}) => { + return group.toLowerCase() === comparedValue; + }); + } +} diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts b/services/self-service/src/main/resources/webapp/src/app/core/models/role.model.ts similarity index 57% copy from services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts copy to services/self-service/src/main/resources/webapp/src/app/core/models/role.model.ts index dfbbbf7fd..b2249ae5e 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/administration.module.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/models/role.model.ts @@ -17,18 +17,27 @@ * under the License. */ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ManagenementModule } from './management'; -import { ProjectModule } from './project'; -import { RolesModule } from './roles'; -import {ConfigurationModule} from './configuration'; -import {OdahuModule} from './odahu'; +export interface GroupModel { + group: string; + roles: Role[]; + users: string[]; +} -@NgModule({ - imports: [CommonModule, ManagenementModule, ProjectModule, RolesModule, ConfigurationModule, OdahuModule], - declarations: [], - exports: [ManagenementModule, ProjectModule, RolesModule, ConfigurationModule, OdahuModule] -}) -export class AdministrationModule { } +export interface Role { + id: string; + description: string; + type: EntityType; + cloud: CloudType; + pages: string[]; + computationals: string[]; + exploratories: string[]; + exploratory_shapes: string[]; + groups: string[]; + images: string[]; +} + + +export type EntityType = 'NOTEBOOK' | 'COMPUTATIONAL' | 'IMAGE' | 'NOTEBOOK_SHAPE' | 'COMPUTATIONAL_SHAPE' | 'BILLING' | 'BUCKET_BROWSER' | 'ADMINISTRATION'; + +export type CloudType = 'AWS' | 'AZURE' | 'GSP' | 'GENERAL'; diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/rolesManagement.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/rolesManagement.service.ts index 17766914c..b82e9cf27 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/rolesManagement.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/rolesManagement.service.ts @@ -23,16 +23,16 @@ import { catchError, map } from 'rxjs/operators'; import { ApplicationServiceFacade } from './applicationServiceFacade.service'; import { ErrorUtils } from '../util'; +import { GroupModel } from '../models/role.model'; @Injectable() export class RolesGroupsService { constructor(private applicationServiceFacade: ApplicationServiceFacade) { } - public getGroupsData(): Observable<{}> { + public getGroupsData(): Observable<GroupModel[]> { return this.applicationServiceFacade .buildGetGroupsData() .pipe( - map(response => response), catchError(ErrorUtils.handleServiceError)); } @@ -40,7 +40,6 @@ export class RolesGroupsService { return this.applicationServiceFacade .buildGetRolesData() .pipe( - map(response => response), catchError(ErrorUtils.handleServiceError)); } @@ -48,7 +47,6 @@ export class RolesGroupsService { return this.applicationServiceFacade .buildSetupNewGroup(data) .pipe( - map(response => response), catchError(ErrorUtils.handleServiceError)); } @@ -56,7 +54,6 @@ export class RolesGroupsService { return this.applicationServiceFacade .buildUpdateGroupData(data) .pipe( - map(response => response), catchError(ErrorUtils.handleServiceError)); } @@ -64,7 +61,6 @@ export class RolesGroupsService { return this.applicationServiceFacade .buildSetupRolesForGroup(data) .pipe( - map(response => response), catchError(ErrorUtils.handleServiceError)); } @@ -72,7 +68,6 @@ export class RolesGroupsService { return this.applicationServiceFacade .buildSetupUsersForGroup(data) .pipe( - map(response => response), catchError(ErrorUtils.handleServiceError)); } @@ -82,7 +77,6 @@ export class RolesGroupsService { return this.applicationServiceFacade .buildRemoveUsersForGroup(JSON.stringify(url)) .pipe( - map(response => response), catchError(ErrorUtils.handleServiceError)); } @@ -92,7 +86,6 @@ export class RolesGroupsService { return this.applicationServiceFacade .buildRemoveGroupById(JSON.stringify(url)) .pipe( - map(response => response), catchError(ErrorUtils.handleServiceError)); } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
