This is an automated email from the ASF dual-hosted git repository.

rshah pushed a commit to branch feature/tpv2-role-details
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git

commit ecde018433e5380c65842ebe43ea12508a3d9fe4
Author: Rima Shah <[email protected]>
AuthorDate: Fri May 5 09:48:51 2023 -0600

    Add role details.
---
 .../traffic-portal/nightwatch/globals/globals.ts   |   2 +
 .../nightwatch/page_objects/users/roleDetails.ts   |  42 +++++++++
 .../nightwatch/tests/users/role/detail.spec.ts     |  44 +++++++++
 .../src/app/api/testing/user.service.ts            |  46 ++++++++++
 .../traffic-portal/src/app/api/user.service.ts     |  31 +++++++
 .../traffic-portal/src/app/core/core.module.ts     |   2 +
 .../users/roles/detail/role-detail.component.html  |  42 +++++++++
 .../users/roles/detail/role-detail.component.scss  |  26 ++++++
 .../roles/detail/role-detail.component.spec.ts     |  77 ++++++++++++++++
 .../users/roles/detail/role-detail.component.ts    | 101 +++++++++++++++++++++
 10 files changed, 413 insertions(+)

diff --git a/experimental/traffic-portal/nightwatch/globals/globals.ts 
b/experimental/traffic-portal/nightwatch/globals/globals.ts
index 3ca3773ea6..4034d330cb 100644
--- a/experimental/traffic-portal/nightwatch/globals/globals.ts
+++ b/experimental/traffic-portal/nightwatch/globals/globals.ts
@@ -41,6 +41,7 @@ import type { StatusDetailPageObject } from 
"nightwatch/page_objects/statuses/st
 import type { StatusesTablePageObject } from 
"nightwatch/page_objects/statuses/statusesTable";
 import type { ChangeLogsPageObject } from 
"nightwatch/page_objects/users/changeLogs";
 import type { RolesPageObject } from 
"nightwatch/page_objects/users/rolesTable";
+import type { RoleDetailPageObject } from 
"nightwatch/page_objects/users/roleDetail";
 import type { TenantDetailPageObject } from 
"nightwatch/page_objects/users/tenantDetail";
 import type { TenantsPageObject } from "nightwatch/page_objects/users/tenants";
 import type { UsersPageObject } from "nightwatch/page_objects/users/users";
@@ -135,6 +136,7 @@ declare module "nightwatch" {
                users: {
                        changeLogs: () => ChangeLogsPageObject;
                        roles: () => RolesPageObject;
+                       roleDetail: () => RoleDetailPageObject;
                        tenants: () => TenantsPageObject;
                        tenantDetail: () => TenantDetailPageObject;
                        users: () => UsersPageObject;
diff --git 
a/experimental/traffic-portal/nightwatch/page_objects/users/roleDetails.ts 
b/experimental/traffic-portal/nightwatch/page_objects/users/roleDetails.ts
new file mode 100644
index 0000000000..de01fda53d
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/page_objects/users/roleDetails.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 { EnhancedPageObject } from "nightwatch";
+
+/**
+ * Defines the PageObject for Role Details.
+ */
+export type RoleDetailPageObject = EnhancedPageObject<{}, typeof 
roleDetailPageObject.elements>;
+
+const roleDetailPageObject = {
+       elements: {
+               description: {
+                       selector: "input[name='description']"
+               },
+               lastUpdated: {
+                       selector: "input[name='lastUpdated']"
+               },
+               name: {
+                       selector: "input[name='name']"
+               },
+               permissions: {
+                       selector: "input[name='permissions']"
+               },
+               saveBtn: {
+                       selector: "button[type='submit']"
+               }
+       },
+};
+
+export default roleDetailPageObject;
diff --git 
a/experimental/traffic-portal/nightwatch/tests/users/role/detail.spec.ts 
b/experimental/traffic-portal/nightwatch/tests/users/role/detail.spec.ts
new file mode 100644
index 0000000000..8856106316
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/tests/users/role/detail.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+describe("Role Detail Spec", () => {
+       it("Test Role", () => {
+               const page = browser.page.users.roles();
+               
browser.url(`${page.api.launchUrl}/core/roles/${browser.globals.testData.role.name}`,
 res => {
+                       browser.assert.ok(res.status === 0);
+                       page.waitForElementVisible("mat-card")
+                               .assert.enabled("@role")
+                               .assert.enabled("@description")
+                               .assert.enabled("@permissions")
+                               .assert.enabled("@saveBtn")
+                               .assert.not.enabled("@lastUpdated")
+                               .assert.valueEquals("@name", 
String(browser.globals.testData.role.name))
+                               .assert.valueEquals("@permission", 
String(browser.globals.testData.role.permissions));
+               });
+       });
+
+       // it("New asn", () => {
+       //      const page = browser.page.cacheGroups.asnDetail();
+       //      browser.url(`${page.api.launchUrl}/core/asns/new`, res => {
+       //              browser.assert.ok(res.status === 0);
+       //              page.waitForElementVisible("mat-card")
+       //                      .assert.enabled("@asn")
+       //                      .assert.enabled("@cachegroup")
+       //                      .assert.enabled("@saveBtn")
+       //                      .assert.not.elementPresent("@id")
+       //                      .assert.not.elementPresent("@lastUpdated")
+       //                      .assert.valueEquals("@asn", "1");
+       //      });
+       // });
+});
diff --git a/experimental/traffic-portal/src/app/api/testing/user.service.ts 
b/experimental/traffic-portal/src/app/api/testing/user.service.ts
index caf06c84a7..bcaf7c6dc2 100644
--- a/experimental/traffic-portal/src/app/api/testing/user.service.ts
+++ b/experimental/traffic-portal/src/app/api/testing/user.service.ts
@@ -17,6 +17,7 @@ import { Injectable } from "@angular/core";
 import type {
        PostRequestUser,
        PutRequestUser,
+       RequestRole,
        RequestTenant,
        ResponseCurrentUser,
        ResponseRole,
@@ -428,6 +429,51 @@ export class UserService {
                return this.roles;
        }
 
+       /**
+        * Creates a new role.
+        *
+        * @param role The role to create.
+        * @returns The created role.
+        */
+       public async createRole(role: RequestRole): Promise<ResponseRole> {
+               const resp = {
+                       ...role,
+                       name: role.name,
+                       lastUpdated: new Date(),
+               };
+               this.roles.push(resp);
+               return resp;
+       }
+
+       /**
+        * Updates an existing role.
+        *
+        * @param role The role to update.
+        * @returns The updated role.
+        */
+       public async updateRole(role: ResponseRole): Promise<ResponseRole> {
+               const name = this.tenants.findIndex(r => r.name === role.name);
+               if (name === null) {
+                       throw new Error(`no such Role: ${role.name}`);
+               }
+               this.roles[name] = role;
+               return role;
+       }
+
+       /**
+        * Deletes an existing role.
+        *
+        * @param tenant The role to be deleted.
+        * @returns The deleted role.
+        */
+       public async deleteRole(role: ResponseRole): Promise<ResponseRole> {
+               const index = this.tenants.findIndex(r => r.name === role.name);
+               if (index < 0) {
+                       throw new Error(`no such role: ${role.name}`);
+               }
+               return this.roles.splice(index, 1)[0];
+       }
+
        /**
         * Retrieves all (visible) Tenants from Traffic Ops.
         *
diff --git a/experimental/traffic-portal/src/app/api/user.service.ts 
b/experimental/traffic-portal/src/app/api/user.service.ts
index 8ad95889cb..3b1bb51b1c 100644
--- a/experimental/traffic-portal/src/app/api/user.service.ts
+++ b/experimental/traffic-portal/src/app/api/user.service.ts
@@ -27,6 +27,7 @@ import {
 } from "trafficops-types";
 
 import { APIService } from "./base-api.service";
+import {RequestRole} from "trafficops-types";
 
 /**
  * UserService exposes API functionality related to Users, Roles and Tenants.
@@ -267,6 +268,36 @@ export class UserService extends APIService {
                return this.get<Array<ResponseRole>>(path).toPromise();
        }
 
+       /**
+        * Creates a new Role.
+        *
+        * @param role The role to create.
+        * @returns The created role.
+        */
+       public async createRole(role: RequestRole): Promise<ResponseRole> {
+               return this.post<ResponseRole>("roles", role).toPromise();
+       }
+
+       /**
+        * Updates an existing Role.
+        *
+        * @param role The role to update.
+        * @returns The updated role.
+        */
+       public async updateRole(role: ResponseRole): Promise<ResponseRole> {
+               return this.put<ResponseRole>(`roles/${role.name}`, 
role).toPromise();
+       }
+
+       /**
+        * Deletes an existing role.
+        *
+        * @param tenant The role to be deleted.
+        * @returns The deleted role.
+        */
+       public async deleteRole(role: ResponseRole): Promise<ResponseRole> {
+               return 
this.delete<ResponseRole>(`roles/${role.name}`).toPromise();
+       }
+
        /**
         * Retrieves Tenants from Traffic Ops.
         *
diff --git a/experimental/traffic-portal/src/app/core/core.module.ts 
b/experimental/traffic-portal/src/app/core/core.module.ts
index 93fa833903..f90d850688 100644
--- a/experimental/traffic-portal/src/app/core/core.module.ts
+++ b/experimental/traffic-portal/src/app/core/core.module.ts
@@ -62,6 +62,7 @@ import { StatusesTableComponent } from 
"./statuses/statuses-table/statuses-table
 import { TypeDetailComponent } from "./types/detail/type-detail.component";
 import { TypesTableComponent } from "./types/table/types-table.component";
 import { RolesTableComponent } from 
"./users/roles/table/roles-table.component";
+import { RoleDetailComponent } from 
"./users/roles/detail/role-detail.component";
 import { TenantDetailsComponent } from 
"./users/tenants/tenant-details/tenant-details.component";
 import { TenantsComponent } from "./users/tenants/tenants.component";
 import { UserDetailsComponent } from 
"./users/user-details/user-details.component";
@@ -91,6 +92,7 @@ export const ROUTES: Routes = [
        { component: CacheGroupTableComponent, path: "cache-groups" },
        { component: CacheGroupDetailsComponent, path: "cache-groups/:id"},
        { component: RolesTableComponent, path: "roles"},
+       { component: RoleDetailComponent, path: "roles/:name"},
        { component: TenantsComponent, path: "tenants"},
        { component: ChangeLogsComponent, path: "change-logs" },
        { component: TenantDetailsComponent, path: "tenants/:id"},
diff --git 
a/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.html
 
b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.html
new file mode 100644
index 0000000000..512420ec62
--- /dev/null
+++ 
b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.html
@@ -0,0 +1,42 @@
+<!--
+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.
+-->
+
+<mat-card>
+       <tp-loading *ngIf="!role"></tp-loading>
+       <form ngNativeValidate (ngSubmit)="submit($event)" *ngIf="role">
+               <mat-card-content>
+                       <mat-form-field *ngIf="!new">
+                               <mat-label>ID</mat-label>
+                               <input matInput type="text" name="name" 
disabled readonly [defaultValue]="role.name" />
+                       </mat-form-field>
+                       <mat-form-field>
+                               <mat-label>ASN</mat-label>
+                               <input matInput type="text" name="description" 
required [(ngModel)]="role.description" />
+                       </mat-form-field>
+                       <mat-form-field>
+                               <mat-label>Cache Group</mat-label>
+                               <mat-select name="permissions" 
[(ngModel)]="role.permissions" required>
+                                       <mat-option *ngFor="" 
[value]="">{{}}</mat-option>
+                               </mat-select>
+                       </mat-form-field>
+                       <mat-form-field *ngIf="!new">
+                               <mat-label>Last Updated</mat-label>
+                               <input matInput type="text" name="lastUpdated" 
disabled readonly [defaultValue]="role.lastUpdated" /> </mat-form-field>
+               </mat-card-content>
+               <mat-card-actions align="end">
+                       <button mat-raised-button type="button" *ngIf="!new" 
color="warn" (click)="deleteRole()">Delete</button>
+                       <button mat-raised-button type="submit" 
color="primary">Save</button>
+               </mat-card-actions>
+       </form>
+</mat-card>
diff --git 
a/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.scss
 
b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.scss
new file mode 100644
index 0000000000..fdfbde7654
--- /dev/null
+++ 
b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.scss
@@ -0,0 +1,26 @@
+/*
+* 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.
+*/
+
+mat-card {
+       margin: 1em auto;
+       width: 80%;
+       min-width: 350px;
+
+       mat-card-content {
+               display: grid;
+               grid-template-columns: 1fr;
+               row-gap: 2em;
+               margin: 1em auto 50px;
+       }
+}
diff --git 
a/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.spec.ts
 
b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.spec.ts
new file mode 100644
index 0000000000..7bbe9d896f
--- /dev/null
+++ 
b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.spec.ts
@@ -0,0 +1,77 @@
+/*
+* 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 { MatDialogModule } from "@angular/material/dialog";
+import { ActivatedRoute } from "@angular/router";
+import { RouterTestingModule } from "@angular/router/testing";
+import { ReplaySubject } from "rxjs";
+
+import { APITestingModule } from "src/app/api/testing";
+import { RoleDetailComponent } from 
"src/app/core/users/roles/detail/role-detail.component";
+import { NavigationService } from 
"src/app/shared/navigation/navigation.service";
+
+describe("RoleDetailComponent", () => {
+       let component: RoleDetailComponent;
+       let fixture: ComponentFixture<RoleDetailComponent>;
+       let route: ActivatedRoute;
+       let paramMap: jasmine.Spy;
+
+       const headerSvc = jasmine.createSpyObj([],{headerHidden: new 
ReplaySubject<boolean>(), headerTitle: new ReplaySubject<string>()});
+       beforeEach(async () => {
+               await TestBed.configureTestingModule({
+                       declarations: [ RoleDetailComponent ],
+                       imports: [ APITestingModule, RouterTestingModule, 
MatDialogModule ],
+                       providers: [ { provide: NavigationService, useValue: 
headerSvc } ]
+               })
+                       .compileComponents();
+
+               route = TestBed.inject(ActivatedRoute);
+               paramMap = spyOn(route.snapshot.paramMap, "get");
+               paramMap.and.returnValue(null);
+               fixture = TestBed.createComponent(RoleDetailComponent);
+               component = fixture.componentInstance;
+               fixture.detectChanges();
+       });
+
+       it("should create", () => {
+               expect(component).toBeTruthy();
+               expect(paramMap).toHaveBeenCalled();
+       });
+
+       it("new role", async () => {
+               paramMap.and.returnValue("new");
+
+               fixture = TestBed.createComponent(RoleDetailComponent);
+               component = fixture.componentInstance;
+               fixture.detectChanges();
+               await fixture.whenStable();
+               expect(paramMap).toHaveBeenCalled();
+               expect(component.roles).not.toBeNull();
+               expect(component.roles.name).toBe(1);
+               expect(component.new).toBeTrue();
+       });
+
+       it("existing role", async () => {
+               paramMap.and.returnValue("1");
+
+               fixture = TestBed.createComponent(RoleDetailComponent);
+               component = fixture.componentInstance;
+               fixture.detectChanges();
+               await fixture.whenStable();
+               expect(paramMap).toHaveBeenCalled();
+               expect(component.roles).not.toBeNull();
+               expect(component.roles.name).toBe(0);
+               expect(component.new).toBeFalse();
+       });
+});
diff --git 
a/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.ts
 
b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.ts
new file mode 100644
index 0000000000..9e7897eee3
--- /dev/null
+++ 
b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.ts
@@ -0,0 +1,101 @@
+/*
+* 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 { Location } from "@angular/common";
+import { Component, OnInit } from "@angular/core";
+import { MatDialog } from "@angular/material/dialog";
+import { ActivatedRoute } from "@angular/router";
+import { ResponseRole } from "trafficops-types";
+
+import { UserService } from "src/app/api";
+import { DecisionDialogComponent } from 
"src/app/shared/dialogs/decision-dialog/decision-dialog.component";
+import { NavigationService } from 
"src/app/shared/navigation/navigation.service";
+
+/**
+ * AsnDetailComponent is the controller for the ASN add/edit form.
+ */
+@Component({
+       selector: "tp-role-detail",
+       styleUrls: ["./role-detail.component.scss"],
+       templateUrl: "./role-detail.component.html"
+})
+export class RoleDetailComponent implements OnInit {
+       public new = false;
+       public asn!: ResponseRole;
+       constructor(private readonly route: ActivatedRoute, private readonly 
location: Location,
+                               private readonly dialog: MatDialog, private 
readonly header: NavigationService) {
+       }
+
+       /**
+        * Angular lifecycle hook where data is initialized.
+        */
+       public async ngOnInit(): Promise<void> {
+               const role = this.route.snapshot.paramMap.get("name");
+               if (role === null) {
+                       console.error("missing required route parameter 
'name'");
+                       return;
+               }
+
+               if (role === "new") {
+                       this.header.headerTitle.next("New Role");
+                       this.new = true;
+                       this.role = {
+                               description: "Read Only",
+                               lastUpdated: new Date(),
+                               name: "test",
+                               permissions: []
+                       };
+                       return;
+               }
+
+               this.role = await this.UserService.getRoles(role);
+               this.header.headerTitle.next(`Role: ${this.role.name}`);
+       }
+
+       /**
+        * Deletes the current ASN.
+        */
+       public async deleteRole(): Promise<void> {
+               if (this.new) {
+                       console.error("Unable to delete new role");
+                       return;
+               }
+               const ref = this.dialog.open(DecisionDialogComponent, {
+                       data: {message: `Are you sure you want to delete role 
${this.role.name} with description ${this.role.description}`,
+                               title: "Confirm Delete"}
+               });
+               ref.afterClosed().subscribe(result => {
+                       if(result) {
+                               this.UserService.deleteRole(this.role.name);
+                               this.location.back();
+                       }
+               });
+       }
+
+       /**
+        * Submits new/updated ASN.
+        *
+        * @param e HTML form submission event.
+        */
+       public async submit(e: Event): Promise<void> {
+               e.preventDefault();
+               e.stopPropagation();
+               if(this.new) {
+                       this.asn = await this.UserService.createRole(this.role);
+                       this.new = false;
+               } else {
+                       this.asn = await 
this.UserService.updateRoleN(this.Role);
+               }
+       }
+
+}

Reply via email to