From 86b62ebe765329b2acd10ce6d0cb99c2b7b2ae40 Mon Sep 17 00:00:00 2001 From: Pavlo Tkach <3469726+ptkach@users.noreply.github.com> Date: Wed, 14 Jun 2023 16:51:54 -0400 Subject: [PATCH] Add registrar selection functionality (#2054) --- console-webapp/dev-proxy.config.json | 6 +- console-webapp/src/app/app-routing.module.ts | 10 ++- console-webapp/src/app/app.component.html | 3 + console-webapp/src/app/app.module.ts | 5 +- .../app/registrar/registrar.component.html | 17 +++++ .../app/registrar/registrar.component.less | 11 ++++ .../app/registrar/registrar.component.spec.ts | 44 +++++++++++++ .../src/app/registrar/registrar.component.ts | 45 +++++++++++++ .../src/app/registrar/registrar.guard.spec.ts | 64 +++++++++++++++++++ .../src/app/registrar/registrar.guard.ts | 34 ++++++++++ .../app/registrar/registrar.service.spec.ts | 35 ++++++++++ .../src/app/registrar/registrar.service.ts | 29 +++++++++ .../contact/contact-details.component.html | 15 ++--- .../contact/contact.component.spec.ts | 8 ++- .../app/settings/contact/contact.component.ts | 12 +++- .../app/settings/contact/contact.service.ts | 37 ++++++----- .../registrars/registrars.component.spec.ts | 2 +- .../security/security.component.spec.ts | 2 +- .../settings/users/users.component.spec.ts | 2 +- .../settings/whois/whois.component.spec.ts | 2 +- .../app/shared/services/backend.service.ts | 32 ++-------- 21 files changed, 350 insertions(+), 65 deletions(-) create mode 100644 console-webapp/src/app/registrar/registrar.component.html create mode 100644 console-webapp/src/app/registrar/registrar.component.less create mode 100644 console-webapp/src/app/registrar/registrar.component.spec.ts create mode 100644 console-webapp/src/app/registrar/registrar.component.ts create mode 100644 console-webapp/src/app/registrar/registrar.guard.spec.ts create mode 100644 console-webapp/src/app/registrar/registrar.guard.ts create mode 100644 console-webapp/src/app/registrar/registrar.service.spec.ts create mode 100644 console-webapp/src/app/registrar/registrar.service.ts diff --git a/console-webapp/dev-proxy.config.json b/console-webapp/dev-proxy.config.json index 511bcccbc..7c6e2782d 100644 --- a/console-webapp/dev-proxy.config.json +++ b/console-webapp/dev-proxy.config.json @@ -1,7 +1,7 @@ { - "/registrar": + "/console-api": { - "target": "http://localhost:8080/registrar", - "secure": false + "target": "http://localhost:8080", + "secure": true } } diff --git a/console-webapp/src/app/app-routing.module.ts b/console-webapp/src/app/app-routing.module.ts index 15e276128..a31dd94ff 100644 --- a/console-webapp/src/app/app-routing.module.ts +++ b/console-webapp/src/app/app-routing.module.ts @@ -22,14 +22,18 @@ import SettingsRegistrarsComponent from './settings/registrars/registrars.compon import SettingsWhoisComponent from './settings/whois/whois.component'; import SettingsUsersComponent from './settings/users/users.component'; import SettingsSecurityComponent from './settings/security/security.component'; +import { RegistrarGuard } from './registrar/registrar.guard'; +import { RegistrarComponent } from './registrar/registrar.component'; const routes: Routes = [ - { path: '', redirectTo: '/settings/contact', pathMatch: 'full' }, - { path: 'home', component: HomeComponent }, - { path: 'tlds', component: TldsComponent }, + { path: '', redirectTo: '/home', pathMatch: 'full' }, + { path: 'registrars', component: RegistrarComponent }, + { path: 'home', component: HomeComponent, canActivate: [RegistrarGuard] }, + { path: 'tlds', component: TldsComponent, canActivate: [RegistrarGuard] }, { path: 'settings', component: SettingsComponent, + canActivate: [RegistrarGuard], children: [ { path: '', diff --git a/console-webapp/src/app/app.component.html b/console-webapp/src/app/app.component.html index b093514e2..078060a48 100644 --- a/console-webapp/src/app/app.component.html +++ b/console-webapp/src/app/app.component.html @@ -12,6 +12,9 @@ Settings + + Select Registrar + diff --git a/console-webapp/src/app/app.module.ts b/console-webapp/src/app/app.module.ts index 5801d4996..f18508009 100644 --- a/console-webapp/src/app/app.module.ts +++ b/console-webapp/src/app/app.module.ts @@ -31,6 +31,8 @@ import SettingsContactComponent, { ContactDetailsDialogComponent, } from './settings/contact/contact.component'; import { HttpClientModule } from '@angular/common/http'; +import { RegistrarComponent } from './registrar/registrar.component'; +import { RegistrarGuard } from './registrar/registrar.guard'; @NgModule({ declarations: [ @@ -41,6 +43,7 @@ import { HttpClientModule } from '@angular/common/http'; SettingsComponent, SettingsContactComponent, ContactDetailsDialogComponent, + RegistrarComponent, ], imports: [ HttpClientModule, @@ -50,7 +53,7 @@ import { HttpClientModule } from '@angular/common/http'; AppRoutingModule, BrowserAnimationsModule, ], - providers: [BackendService], + providers: [BackendService, RegistrarGuard], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/console-webapp/src/app/registrar/registrar.component.html b/console-webapp/src/app/registrar/registrar.component.html new file mode 100644 index 000000000..610525928 --- /dev/null +++ b/console-webapp/src/app/registrar/registrar.component.html @@ -0,0 +1,17 @@ +
+

+ {{ registrarService.activeRegistrarId === "" ? "Select" : "Switch" }} + registrar: +

+ + Registrar + + + {{ registrar }} + + + +
diff --git a/console-webapp/src/app/registrar/registrar.component.less b/console-webapp/src/app/registrar/registrar.component.less new file mode 100644 index 000000000..faa0146e9 --- /dev/null +++ b/console-webapp/src/app/registrar/registrar.component.less @@ -0,0 +1,11 @@ +.console-app { + &__registrar { + display: flex; + justify-content: center; + align-items: center; + margin-top: 1rem; + } + &__title { + margin-right: 1rem; + } +} diff --git a/console-webapp/src/app/registrar/registrar.component.spec.ts b/console-webapp/src/app/registrar/registrar.component.spec.ts new file mode 100644 index 000000000..861a0f1b3 --- /dev/null +++ b/console-webapp/src/app/registrar/registrar.component.spec.ts @@ -0,0 +1,44 @@ +// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RegistrarComponent } from './registrar.component'; +import { BackendService } from '../shared/services/backend.service'; +import { ActivatedRoute } from '@angular/router'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('RegistrarComponent', () => { + let component: RegistrarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RegistrarComponent], + imports: [HttpClientTestingModule], + providers: [ + BackendService, + { provide: ActivatedRoute, useValue: {} as ActivatedRoute }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(RegistrarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console-webapp/src/app/registrar/registrar.component.ts b/console-webapp/src/app/registrar/registrar.component.ts new file mode 100644 index 000000000..2008b0379 --- /dev/null +++ b/console-webapp/src/app/registrar/registrar.component.ts @@ -0,0 +1,45 @@ +// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component } from '@angular/core'; +import { RegistrarService } from './registrar.service'; +import { ActivatedRoute, Router } from '@angular/router'; + +@Component({ + selector: 'app-registrar', + templateUrl: './registrar.component.html', + styleUrls: ['./registrar.component.less'], +}) +export class RegistrarComponent { + private lastActiveRegistrarId: string; + + constructor( + private route: ActivatedRoute, + protected registrarService: RegistrarService, + private router: Router + ) { + this.lastActiveRegistrarId = registrarService.activeRegistrarId; + } + + ngDoCheck() { + if ( + this.registrarService.activeRegistrarId && + this.registrarService.activeRegistrarId !== this.lastActiveRegistrarId && + this.route.snapshot.paramMap.get('nextUrl') + ) { + this.lastActiveRegistrarId = this.registrarService.activeRegistrarId; + this.router.navigate([this.route.snapshot.paramMap.get('nextUrl')]); + } + } +} diff --git a/console-webapp/src/app/registrar/registrar.guard.spec.ts b/console-webapp/src/app/registrar/registrar.guard.spec.ts new file mode 100644 index 000000000..0e668b046 --- /dev/null +++ b/console-webapp/src/app/registrar/registrar.guard.spec.ts @@ -0,0 +1,64 @@ +// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { TestBed } from '@angular/core/testing'; + +import { RegistrarGuard } from './registrar.guard'; +import { Router, RouterStateSnapshot } from '@angular/router'; +import { RegistrarService } from './registrar.service'; + +describe('RegistrarGuard', () => { + let guard: RegistrarGuard; + let dummyRegistrarService: RegistrarService; + let routeSpy: Router; + let dummyRoute: RouterStateSnapshot = {} as RouterStateSnapshot; + + beforeEach(() => { + routeSpy = jasmine.createSpyObj('Router', ['navigate']); + dummyRegistrarService = { activeRegistrarId: '' } as RegistrarService; + + TestBed.configureTestingModule({ + providers: [ + RegistrarGuard, + { provide: Router, useValue: routeSpy }, + { provide: RegistrarService, useValue: dummyRegistrarService }, + ], + }); + }); + + it('should not be able to activate when activeRegistrarId is empty', () => { + guard = TestBed.inject(RegistrarGuard); + const res = guard.canActivate(dummyRoute); + expect(res).toBeFalsy(); + }); + + it('should be able to activate when activeRegistrarId is not empty', () => { + TestBed.overrideProvider(RegistrarService, { + useValue: { activeRegistrarId: 'value' }, + }); + guard = TestBed.inject(RegistrarGuard); + const res = guard.canActivate(dummyRoute); + expect(res).toBeTrue(); + }); + + it('should navigate to registrars when activeRegistrarId is empty', () => { + const dummyRoute = { url: '/value' } as RouterStateSnapshot; + guard = TestBed.inject(RegistrarGuard); + guard.canActivate(dummyRoute); + expect(routeSpy.navigate).toHaveBeenCalledOnceWith([ + '/registrars', + { nextUrl: '/value' }, + ]); + }); +}); diff --git a/console-webapp/src/app/registrar/registrar.guard.ts b/console-webapp/src/app/registrar/registrar.guard.ts new file mode 100644 index 000000000..44dd1923c --- /dev/null +++ b/console-webapp/src/app/registrar/registrar.guard.ts @@ -0,0 +1,34 @@ +// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { Router, RouterStateSnapshot } from '@angular/router'; +import { RegistrarService } from './registrar.service'; + +@Injectable({ + providedIn: 'root', +}) +export class RegistrarGuard { + constructor( + private router: Router, + private registrarService: RegistrarService + ) {} + + canActivate(state: RouterStateSnapshot): Promise | boolean { + if (this.registrarService.activeRegistrarId) { + return true; + } + return this.router.navigate([`/registrars`, { nextUrl: state.url }]); + } +} diff --git a/console-webapp/src/app/registrar/registrar.service.spec.ts b/console-webapp/src/app/registrar/registrar.service.spec.ts new file mode 100644 index 000000000..6cd69b888 --- /dev/null +++ b/console-webapp/src/app/registrar/registrar.service.spec.ts @@ -0,0 +1,35 @@ +// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { TestBed } from '@angular/core/testing'; + +import { RegistrarService } from './registrar.service'; +import { BackendService } from '../shared/services/backend.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; + +describe('RegistrarService', () => { + let service: RegistrarService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [BackendService], + }); + service = TestBed.inject(RegistrarService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/console-webapp/src/app/registrar/registrar.service.ts b/console-webapp/src/app/registrar/registrar.service.ts new file mode 100644 index 000000000..2257233c3 --- /dev/null +++ b/console-webapp/src/app/registrar/registrar.service.ts @@ -0,0 +1,29 @@ +// Copyright 2023 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { BackendService } from '../shared/services/backend.service'; + +@Injectable({ + providedIn: 'root', +}) +export class RegistrarService { + activeRegistrarId: string = ''; + registrars: string[] = []; + constructor(private backend: BackendService) { + this.backend.getRegistrars().subscribe((r) => { + this.registrars = r; + }); + } +} diff --git a/console-webapp/src/app/settings/contact/contact-details.component.html b/console-webapp/src/app/settings/contact/contact-details.component.html index 5810355a2..73ffcd40a 100644 --- a/console-webapp/src/app/settings/contact/contact-details.component.html +++ b/console-webapp/src/app/settings/contact/contact-details.component.html @@ -1,11 +1,12 @@

Contact details

-
+
Name: @@ -19,6 +20,7 @@ type="email" matInput [email]="true" + [required]="true" [(ngModel)]="contact.emailAddress" [ngModelOptions]="{ standalone: true }" /> @@ -94,12 +96,9 @@ (per CL&D requirements) + + + + - -

- - - - -
diff --git a/console-webapp/src/app/settings/contact/contact.component.spec.ts b/console-webapp/src/app/settings/contact/contact.component.spec.ts index f72d53da2..26726ef51 100644 --- a/console-webapp/src/app/settings/contact/contact.component.spec.ts +++ b/console-webapp/src/app/settings/contact/contact.component.spec.ts @@ -14,7 +14,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ContactComponent } from './contact.component'; +import ContactComponent from './contact.component'; +import { MaterialModule } from 'src/app/material.module'; +import { BackendService } from 'src/app/shared/services/backend.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('ContactComponent', () => { let component: ContactComponent; @@ -23,8 +26,9 @@ describe('ContactComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ContactComponent], + imports: [HttpClientTestingModule, MaterialModule], + providers: [BackendService], }).compileComponents(); - fixture = TestBed.createComponent(ContactComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/console-webapp/src/app/settings/contact/contact.component.ts b/console-webapp/src/app/settings/contact/contact.component.ts index 1c04d1c62..4f025b514 100644 --- a/console-webapp/src/app/settings/contact/contact.component.ts +++ b/console-webapp/src/app/settings/contact/contact.component.ts @@ -104,7 +104,11 @@ export class ContactDetailsDialogComponent { this.operation = data.operation; } - saveAndClose() { + saveAndClose(e: any) { + e.preventDefault(); + if (!e.target.checkValidity()) { + return; + } let operationObservable; if (this.operation === Operations.ADD) { operationObservable = this.contactService.addContact(this.contact); @@ -143,7 +147,7 @@ export default class ContactComponent { ) { // TODO: Refactor to registrarId service this.loading = true; - this.contactService.fetchContacts('zoomco').subscribe(() => { + this.contactService.fetchContacts().subscribe(() => { this.loading = false; }); } @@ -160,7 +164,9 @@ export default class ContactComponent { } deleteContact(contact: Contact) { - this.contactService.deleteContact(contact); + if (confirm(`Please confirm contact ${contact.name} delete`)) { + this.contactService.deleteContact(contact).subscribe(); + } } openCreateNew(e: Event) { diff --git a/console-webapp/src/app/settings/contact/contact.service.ts b/console-webapp/src/app/settings/contact/contact.service.ts index 9a269c4fd..07f1e15a5 100644 --- a/console-webapp/src/app/settings/contact/contact.service.ts +++ b/console-webapp/src/app/settings/contact/contact.service.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { Observable, tap } from 'rxjs'; +import { RegistrarService } from 'src/app/registrar/registrar.service'; import { BackendService } from 'src/app/shared/services/backend.service'; export interface Contact { @@ -34,26 +35,30 @@ export interface Contact { export class ContactService { contacts: Contact[] = []; - constructor(private backend: BackendService) {} + constructor( + private backend: BackendService, + private registrarService: RegistrarService + ) {} // TODO: Come up with a better handling for registrarId - fetchContacts(registrarId: string): Observable { - return this.backend.getContacts(registrarId).pipe( - tap((contacts) => { - this.contacts = contacts; - }) - ); + fetchContacts(): Observable { + return this.backend + .getContacts(this.registrarService.activeRegistrarId) + .pipe( + tap((contacts) => { + this.contacts = contacts; + }) + ); } - saveContacts( - contacts: Contact[], - registrarId?: string - ): Observable { - return this.backend.postContacts(registrarId || 'default', contacts).pipe( - tap((_) => { - this.contacts = contacts; - }) - ); + saveContacts(contacts: Contact[]): Observable { + return this.backend + .postContacts(this.registrarService.activeRegistrarId, contacts) + .pipe( + tap((_) => { + this.contacts = contacts; + }) + ); } updateContact(index: number, contact: Contact) { diff --git a/console-webapp/src/app/settings/registrars/registrars.component.spec.ts b/console-webapp/src/app/settings/registrars/registrars.component.spec.ts index 934d97a24..224c39405 100644 --- a/console-webapp/src/app/settings/registrars/registrars.component.spec.ts +++ b/console-webapp/src/app/settings/registrars/registrars.component.spec.ts @@ -14,7 +14,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RegistrarsComponent } from './registrars.component'; +import RegistrarsComponent from './registrars.component'; describe('RegistrarsComponent', () => { let component: RegistrarsComponent; diff --git a/console-webapp/src/app/settings/security/security.component.spec.ts b/console-webapp/src/app/settings/security/security.component.spec.ts index 02fbfaa12..1f88a9551 100644 --- a/console-webapp/src/app/settings/security/security.component.spec.ts +++ b/console-webapp/src/app/settings/security/security.component.spec.ts @@ -14,7 +14,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { SecurityComponent } from './security.component'; +import SecurityComponent from './security.component'; describe('SecurityComponent', () => { let component: SecurityComponent; diff --git a/console-webapp/src/app/settings/users/users.component.spec.ts b/console-webapp/src/app/settings/users/users.component.spec.ts index 2aa6ab686..95b89f54c 100644 --- a/console-webapp/src/app/settings/users/users.component.spec.ts +++ b/console-webapp/src/app/settings/users/users.component.spec.ts @@ -14,7 +14,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { UsersComponent } from './users.component'; +import UsersComponent from './users.component'; describe('UsersComponent', () => { let component: UsersComponent; diff --git a/console-webapp/src/app/settings/whois/whois.component.spec.ts b/console-webapp/src/app/settings/whois/whois.component.spec.ts index 5f3186c71..d830b8be3 100644 --- a/console-webapp/src/app/settings/whois/whois.component.spec.ts +++ b/console-webapp/src/app/settings/whois/whois.component.spec.ts @@ -14,7 +14,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { WhoisComponent } from './whois.component'; +import WhoisComponent from './whois.component'; describe('WhoisComponent', () => { let component: WhoisComponent; diff --git a/console-webapp/src/app/shared/services/backend.service.ts b/console-webapp/src/app/shared/services/backend.service.ts index 4c06afda9..5aab46eeb 100644 --- a/console-webapp/src/app/shared/services/backend.service.ts +++ b/console-webapp/src/app/shared/services/backend.service.ts @@ -41,35 +41,11 @@ export class BackendService { } getContacts(registrarId: string): Observable { - const mockData = [ - { - name: 'Name Lastname', - emailAddress: 'test@google.com', - registrarId: 'zoomco', - types: ['ADMIN'], - visibleInWhoisAsAdmin: false, - visibleInWhoisAsTech: false, - visibleInDomainWhoisAsAbuse: false, - }, - { - name: 'Testname testlastname', - emailAddress: 'testasd@google.com', - registrarId: 'zoomco', - visibleInWhoisAsAdmin: false, - visibleInWhoisAsTech: false, - visibleInDomainWhoisAsAbuse: false, - types: ['BILLING'], - }, - ]; return this.http .get( `/console-api/settings/contacts?registrarId=${registrarId}` ) - .pipe( - catchError((err) => - this.errorCatcher(err, mockData) - ) - ); + .pipe(catchError((err) => this.errorCatcher(err))); } postContacts( @@ -81,4 +57,10 @@ export class BackendService { { contacts } ); } + + getRegistrars(): Observable { + return this.http + .get('/console-api/registrars') + .pipe(catchError((err) => this.errorCatcher(err))); + } }