ocket8888 commented on code in PR #7358:
URL: https://github.com/apache/trafficcontrol/pull/7358#discussion_r1126861189
##########
experimental/traffic-portal/src/app/api/server.service.ts:
##########
@@ -186,4 +187,33 @@ export class ServerService extends APIService {
return this.put(`servers/${id}/status`, {offlineReason,
status}).toPromise();
}
+
+ /**
+ * Creating new Status.
+ *
+ * @param data containes name and description for the status.
+ * @returns The 'response' property of the TO status response. See TO
API docs.
+ */
+ public async createStatus(data: ResponseStatus):
Promise<ResponseStatus> {
+ return this.post<ResponseStatus>("statuses", data).toPromise();
+ }
+
+ /**
+ * Updates status Details.
+ *
+ * @param data containes name and description for the status., unique
identifier thereof.
+ * @param id The Status ID
+ */
+ public async updateStatusDetail(data: ResponseStatus, id: number):
Promise<ResponseStatus | undefined> {
Review Comment:
Why is this `| undefined`? The API should be consistent in what it returns
##########
experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.ts:
##########
@@ -0,0 +1,175 @@
+/*
+ * 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, OnInit } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { MatDialog } from "@angular/material/dialog";
+import { ActivatedRoute, Router } from "@angular/router";
+import { ResponseStatus } from "trafficops-types";
+
+import { ServerService } from "src/app/api";
+import { DecisionDialogComponent, DecisionDialogData } from
"src/app/shared/dialogs/decision-dialog/decision-dialog.component";
+import { NavigationService } from
"src/app/shared/navigation/navigation.service";
+
+/**
+ * StatusDetailsComponent is the controller for a status "details" page.
+ */
+@Component({
+ providers: [ServerService],
+ selector: "tp-status-details",
+ styleUrls: ["./status-details.component.scss"],
+ templateUrl: "./status-details.component.html",
+})
+export class StatusDetailsComponent implements OnInit {
+
+ /** Status ID expected from the route param using which we identify
whether we are creating new status or load existing status */
+ public id: string | number | null = null;
+
+ /** All details of status requested */
+ public statusDetails: ResponseStatus | null = null;
+
+ /** Reactive form intialized to creat / edit status details */
+ public statusDetailsForm!: FormGroup;
+
+ /** Loader status for the actions */
+ public loading = false;
+
+ /**
+ * Constructor.
+ *
+ * @param serverService The Servers API which is used to provide row
data.
+ * @param route A reference to the route of this view which is used to
get the 'id' query parameter of status.
+ * @param router Angular router
+ * @param dialog Dialog manager
+ * @param fb Form builder
+ * @param navSvc Manages the header
+ */
+ constructor(
+ private readonly serverService: ServerService,
+ private readonly route: ActivatedRoute,
+ private readonly router: Router,
+ private readonly dialog: MatDialog,
+ private readonly navSvc: NavigationService,
+ ) { }
+
+ /** Initializes table data, loading it from Traffic Ops. */
+ public ngOnInit(): void {
+ this.statusDetailsForm = new FormGroup({
+ description: new FormControl("",Validators.required),
+ name: new FormControl("",Validators.required),
+ });
+
+ // Getting id from the route
+ this.id = this.route.snapshot.paramMap.get("id");
+
+ // we check whether params is a number if not we shall assume
user wants to add a new status.
+ if (!this.isNew) {
+ this.loading = true;
+ this.statusDetailsForm.addControl("id", new
FormControl(""));
+ this.statusDetailsForm.addControl("lastUpdated", new
FormControl(""));
+ this.getStatusDetails();
+ } else {
+ this.navSvc.headerTitle.next("New Status");
+ }
+ }
+
+ /**
+ * Reloads the servers table data.
+ *
+ * @param id is the id passed in route for this page if this is a edit
view.
+ */
+ public async getStatusDetails(): Promise<void> {
+ const id = Number(this.id) ; // id Type 'null' is not
assignable to type 'string'
+ this.statusDetails = await this.serverService.getStatuses(id);
+ const data: ResponseStatus = {
+ description: this.statusDetails.description,
+ id: this.statusDetails.id,
+ lastUpdated: new Date(),
+ name: this.statusDetails.name
+ };
+
+ // Set page title with status ID
+ this.navSvc.headerTitle.next(`Status #${data.id}`);
+
+ // Patch the form with existing data we got from service
requested above.
+ this.statusDetailsForm.patchValue(data);
+ this.loading = false;
+ }
+
+ /**
+ * On submitting the form we check for whether we are performing Create
or Edit
+ */
+ public onSubmit(): void {
+ if (this.isNew) {
+ this.createStatus();
+
+ } else {
+ this.updateStatus();
+ }
+ }
+
+ /**
+ * For Creating a new status
+ */
+ public createStatus(): void {
+
this.serverService.createStatus(this.statusDetailsForm.value).then((res:
ResponseStatus) => {
+ if (res) {
+ this.id = (res?.id);
+
this.router.navigate([`/core/statuses/${this.id}`]);
+ this.navSvc.headerTitle.next(`Status
#${this.id}`);
+ }
+ });
+ }
+
+ /**
+ * For updating the Status
+ */
+ public updateStatus(): void {
+
this.serverService.updateStatusDetail(this.statusDetailsForm.value,
Number(this.id));
+ }
+
+ /**
+ * Deleteting status
+ */
+ public async deleteStatus(): Promise<void> {
+ const ref = this.dialog.open<DecisionDialogComponent,
DecisionDialogData, boolean>(DecisionDialogComponent, {
+ data: {
+ message: `This action CANNOT be undone. This
will permanently delete '${this.statusDetails?.name}'.`,
+ title: `Delete Status:
${this.statusDetails?.name}`
+ }
+ });
+
+ ref.afterClosed().subscribe(result => {
+ if (result) {
+ const id = Number(this.id);
+ this.serverService.deleteStatus(id).then(() => {
+
this.router.navigate(["/core/statuses"]);
+ });
+ }
+ });
+ }
+
+ /**
+ * Title for the page
+ */
+ public get title(): string {
+ return this.isNew ? "Add New Status" : "Edit Status";
+ }
+
+ /**
+ * Checking for params to ensure given id is a number
+ */
+ public get isNew(): boolean {
+ return this.id === "new" && isNaN(Number(this.id));
Review Comment:
the second part of this condition is redundant, because
`isNaN(Number("new"))` is a static value: `true`.
##########
experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.ts:
##########
@@ -0,0 +1,175 @@
+/*
+ * 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, OnInit } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { MatDialog } from "@angular/material/dialog";
+import { ActivatedRoute, Router } from "@angular/router";
+import { ResponseStatus } from "trafficops-types";
+
+import { ServerService } from "src/app/api";
+import { DecisionDialogComponent, DecisionDialogData } from
"src/app/shared/dialogs/decision-dialog/decision-dialog.component";
+import { NavigationService } from
"src/app/shared/navigation/navigation.service";
+
+/**
+ * StatusDetailsComponent is the controller for a status "details" page.
+ */
+@Component({
+ providers: [ServerService],
+ selector: "tp-status-details",
+ styleUrls: ["./status-details.component.scss"],
+ templateUrl: "./status-details.component.html",
+})
+export class StatusDetailsComponent implements OnInit {
+
+ /** Status ID expected from the route param using which we identify
whether we are creating new status or load existing status */
+ public id: string | number | null = null;
+
+ /** All details of status requested */
+ public statusDetails: ResponseStatus | null = null;
+
+ /** Reactive form intialized to creat / edit status details */
+ public statusDetailsForm!: FormGroup;
+
+ /** Loader status for the actions */
+ public loading = false;
+
+ /**
+ * Constructor.
+ *
+ * @param serverService The Servers API which is used to provide row
data.
+ * @param route A reference to the route of this view which is used to
get the 'id' query parameter of status.
+ * @param router Angular router
+ * @param dialog Dialog manager
+ * @param fb Form builder
+ * @param navSvc Manages the header
+ */
+ constructor(
+ private readonly serverService: ServerService,
+ private readonly route: ActivatedRoute,
+ private readonly router: Router,
+ private readonly dialog: MatDialog,
+ private readonly navSvc: NavigationService,
+ ) { }
+
+ /** Initializes table data, loading it from Traffic Ops. */
+ public ngOnInit(): void {
+ this.statusDetailsForm = new FormGroup({
+ description: new FormControl("",Validators.required),
+ name: new FormControl("",Validators.required),
+ });
+
+ // Getting id from the route
+ this.id = this.route.snapshot.paramMap.get("id");
+
+ // we check whether params is a number if not we shall assume
user wants to add a new status.
+ if (!this.isNew) {
+ this.loading = true;
+ this.statusDetailsForm.addControl("id", new
FormControl(""));
+ this.statusDetailsForm.addControl("lastUpdated", new
FormControl(""));
+ this.getStatusDetails();
+ } else {
+ this.navSvc.headerTitle.next("New Status");
+ }
+ }
+
+ /**
+ * Reloads the servers table data.
+ *
+ * @param id is the id passed in route for this page if this is a edit
view.
+ */
+ public async getStatusDetails(): Promise<void> {
+ const id = Number(this.id) ; // id Type 'null' is not
assignable to type 'string'
+ this.statusDetails = await this.serverService.getStatuses(id);
+ const data: ResponseStatus = {
+ description: this.statusDetails.description,
+ id: this.statusDetails.id,
+ lastUpdated: new Date(),
+ name: this.statusDetails.name
+ };
+
+ // Set page title with status ID
+ this.navSvc.headerTitle.next(`Status #${data.id}`);
Review Comment:
The title should probably be the name of the status, instead of or in
addition to its ID. Nobody knows what status #4 is, but everyone knows what
`ONLINE` is.
##########
experimental/traffic-portal/src/app/core/statuses/statuses-table/statuses-table.component.spec.ts:
##########
@@ -0,0 +1,61 @@
+/*
+ * 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 { HttpClientTestingModule } from "@angular/common/http/testing";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { FormsModule } from "@angular/forms";
+import { MatCardModule } from "@angular/material/card";
+import { RouterTestingModule } from "@angular/router/testing";
+
+import { ServerService } from "src/app/api";
+import { SharedModule } from "src/app/shared/shared.module";
+
+import { StatusesTableComponent } from "./statuses-table.component";
+
+const statuses = [{description: "test", id: 1,lastUpdated: new
Date("02/02/2023"), name: "test"}];
+describe("StatusesTableComponent", () => {
+ let component: StatusesTableComponent;
+ let fixture: ComponentFixture<StatusesTableComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ StatusesTableComponent ],
+ imports:[
+ RouterTestingModule,
+ HttpClientTestingModule,
+ FormsModule,
+ MatCardModule,
+ SharedModule
+ ],
+ providers:[ServerService]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(StatusesTableComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("should get all statuses from getStatuses",(()=>{
+ const service =
fixture.debugElement.injector.get(ServerService);
+ spyOn(service,
"getStatuses").and.returnValue(Promise.resolve(statuses));
+
+ service.getStatuses().then((result)=>{
+ expect(result).toEqual(statuses);
+ });
+ }));
Review Comment:
so, in this test, you create a spy that returns a value, then test that
getting that value from the spy gets that value from the spy. This is a test of
`.and.returnValue`, if anything, and we shouldn't be testing external libraries
in our tests, let alone in a file that's supposed to be for a specific
component's tests. A component test should verify some expected behavior _of
the component_.
##########
experimental/traffic-portal/src/app/api/server.service.ts:
##########
@@ -186,4 +187,33 @@ export class ServerService extends APIService {
return this.put(`servers/${id}/status`, {offlineReason,
status}).toPromise();
}
+
+ /**
+ * Creating new Status.
+ *
+ * @param data containes name and description for the status.
+ * @returns The 'response' property of the TO status response. See TO
API docs.
+ */
+ public async createStatus(data: ResponseStatus):
Promise<ResponseStatus> {
+ return this.post<ResponseStatus>("statuses", data).toPromise();
+ }
+
+ /**
+ * Updates status Details.
+ *
+ * @param data containes name and description for the status., unique
identifier thereof.
+ * @param id The Status ID
+ */
+ public async updateStatusDetail(data: ResponseStatus, id: number):
Promise<ResponseStatus | undefined> {
+ return this.put<ResponseStatus>(`statuses/${id}`,
data).toPromise();
+ }
+
+ /**
+ * Deletes an existing Status.
+ *
+ * @param id The Status ID
+ */
+ public async deleteStatus(id: number): Promise<void> {
Review Comment:
This should also accept an entire Status
##########
experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.spec.ts:
##########
@@ -0,0 +1,97 @@
+/*
+ * 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 { HttpClientTestingModule } from "@angular/common/http/testing";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { ReactiveFormsModule } from "@angular/forms";
+import { MatButtonModule } from "@angular/material/button";
+import { MatCardModule } from "@angular/material/card";
+import { MatFormFieldModule } from "@angular/material/form-field";
+import { MatGridListModule } from "@angular/material/grid-list";
+import { MatInputModule } from "@angular/material/input";
+import { BrowserDynamicTestingModule } from
"@angular/platform-browser-dynamic/testing";
+import { Router } from "@angular/router";
+import { RouterTestingModule } from "@angular/router/testing";
+
+import { ServerService } from "src/app/api";
+import { DecisionDialogComponent } from
"src/app/shared/dialogs/decision-dialog/decision-dialog.component";
+import { SharedModule } from "src/app/shared/shared.module";
+
+import { StatusDetailsComponent } from "./status-details.component";
+
+const status = { description: "test", id: 1,lastUpdated: new
Date("02/02/2023"), name: "test"};
+
+describe("StatusDetailsComponent", () => {
+ let component: StatusDetailsComponent;
+ let fixture: ComponentFixture<StatusDetailsComponent>;
+ let router: Router;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [StatusDetailsComponent,
DecisionDialogComponent],
+ imports: [
+ HttpClientTestingModule,
+ RouterTestingModule.withRoutes([
+ { component: StatusDetailsComponent,
path: "core/statuses/:id" }
+ ]),
+ ReactiveFormsModule,
+ MatFormFieldModule,
+ MatInputModule,
+ MatGridListModule,
+ MatCardModule,
+ MatButtonModule,
+ SharedModule
+ ],
+ providers: [ServerService]
+ })
+ .compileComponents();
+ TestBed.overrideModule(BrowserDynamicTestingModule, {
+ set: {
+ entryComponents: [DecisionDialogComponent]
+ }
+ });
+ router = TestBed.inject(Router);
+ fixture = TestBed.createComponent(StatusDetailsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("submits a update status request", (() => {
+ const service = TestBed.inject(ServerService);
+ component.statusDetailsForm.setValue(status);
+ spyOn(service,
"updateStatusDetail").and.returnValue(Promise.resolve(status));
+ component.updateStatus();
+
+ service.updateStatus(component.statusDetailsForm.value,
"1").then((result) => {
+ expect(result).toEqual(status);
+ });
+ }));
+
+ it("submits a status creation request", (() => {
+ const service = TestBed.inject(ServerService);
+ component.statusDetailsForm.setValue(status);
+
+ spyOn(service,
"createStatus").and.returnValue(Promise.resolve(status));
+ component.createStatus();
+
+
service.createStatus(component.statusDetailsForm.value).then((result) => {
+ expect(result).toEqual(status);
+ router.navigate(["/core/statuses/1"]);
+ });
+ }));
Review Comment:
In these tests, you describe it as "submitting a ... request", but what you
do is make a spy, then call the component method that should call the spy, but
you don't verify that behavior, instead you call the spy directly and verify
that it gives you what you told it to give you. We don't need to test Jasmine
utilities, these tests should verify some behavior of the component.
##########
experimental/traffic-portal/src/app/api/server.service.ts:
##########
@@ -186,4 +187,33 @@ export class ServerService extends APIService {
return this.put(`servers/${id}/status`, {offlineReason,
status}).toPromise();
}
+
+ /**
+ * Creating new Status.
+ *
+ * @param data containes name and description for the status.
+ * @returns The 'response' property of the TO status response. See TO
API docs.
+ */
+ public async createStatus(data: ResponseStatus):
Promise<ResponseStatus> {
Review Comment:
this should not take in a `ResponseStatus` parameter; response versions of
things have read-only fields generated by the server like `id` and
`lastUpdated`, and have different field typings regarding null vs undefined vs
"zero-valued" than the objects TO requires in requests. It should use a
`RequestStatus` instead.
##########
experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.ts:
##########
@@ -0,0 +1,175 @@
+/*
+ * 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, OnInit } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { MatDialog } from "@angular/material/dialog";
+import { ActivatedRoute, Router } from "@angular/router";
+import { ResponseStatus } from "trafficops-types";
+
+import { ServerService } from "src/app/api";
+import { DecisionDialogComponent, DecisionDialogData } from
"src/app/shared/dialogs/decision-dialog/decision-dialog.component";
+import { NavigationService } from
"src/app/shared/navigation/navigation.service";
+
+/**
+ * StatusDetailsComponent is the controller for a status "details" page.
+ */
+@Component({
+ providers: [ServerService],
+ selector: "tp-status-details",
+ styleUrls: ["./status-details.component.scss"],
+ templateUrl: "./status-details.component.html",
+})
+export class StatusDetailsComponent implements OnInit {
+
+ /** Status ID expected from the route param using which we identify
whether we are creating new status or load existing status */
+ public id: string | number | null = null;
+
+ /** All details of status requested */
+ public statusDetails: ResponseStatus | null = null;
+
+ /** Reactive form intialized to creat / edit status details */
+ public statusDetailsForm!: FormGroup;
Review Comment:
it'd be a lot simpler to just put the value here or in the constructor,
instead of using not null assertion. The value doesn't depend on any binding to
inputs or outputs of anything, so there's no reason to wait for `ngOnInit`
##########
experimental/traffic-portal/src/app/core/core.module.ts:
##########
@@ -107,14 +112,17 @@ export const ROUTES: Routes = [
DivisionDetailComponent,
RegionsTableComponent,
RegionDetailComponent,
- CacheGroupDetailsComponent
+ CacheGroupDetailsComponent,
+ StatusesTableComponent,
+ StatusDetailsComponent
],
exports: [],
imports: [
SharedModule,
AppUIModule,
CommonModule,
- RouterModule.forChild(ROUTES)
+ RouterModule.forChild(ROUTES),
+ ReactiveFormsModule
Review Comment:
The `AppUIModule` imports and exports `ReactiveFormsModule`, so you
shouldn't need to add this.
##########
experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.ts:
##########
@@ -0,0 +1,175 @@
+/*
+ * 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, OnInit } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { MatDialog } from "@angular/material/dialog";
+import { ActivatedRoute, Router } from "@angular/router";
+import { ResponseStatus } from "trafficops-types";
+
+import { ServerService } from "src/app/api";
+import { DecisionDialogComponent, DecisionDialogData } from
"src/app/shared/dialogs/decision-dialog/decision-dialog.component";
+import { NavigationService } from
"src/app/shared/navigation/navigation.service";
+
+/**
+ * StatusDetailsComponent is the controller for a status "details" page.
+ */
+@Component({
+ providers: [ServerService],
+ selector: "tp-status-details",
+ styleUrls: ["./status-details.component.scss"],
+ templateUrl: "./status-details.component.html",
+})
+export class StatusDetailsComponent implements OnInit {
+
+ /** Status ID expected from the route param using which we identify
whether we are creating new status or load existing status */
+ public id: string | number | null = null;
+
+ /** All details of status requested */
+ public statusDetails: ResponseStatus | null = null;
+
+ /** Reactive form intialized to creat / edit status details */
+ public statusDetailsForm!: FormGroup;
+
+ /** Loader status for the actions */
+ public loading = false;
+
+ /**
+ * Constructor.
+ *
+ * @param serverService The Servers API which is used to provide row
data.
+ * @param route A reference to the route of this view which is used to
get the 'id' query parameter of status.
+ * @param router Angular router
+ * @param dialog Dialog manager
+ * @param fb Form builder
+ * @param navSvc Manages the header
+ */
+ constructor(
+ private readonly serverService: ServerService,
+ private readonly route: ActivatedRoute,
+ private readonly router: Router,
+ private readonly dialog: MatDialog,
+ private readonly navSvc: NavigationService,
+ ) { }
+
+ /** Initializes table data, loading it from Traffic Ops. */
+ public ngOnInit(): void {
+ this.statusDetailsForm = new FormGroup({
+ description: new FormControl("",Validators.required),
+ name: new FormControl("",Validators.required),
+ });
+
+ // Getting id from the route
+ this.id = this.route.snapshot.paramMap.get("id");
+
+ // we check whether params is a number if not we shall assume
user wants to add a new status.
+ if (!this.isNew) {
+ this.loading = true;
+ this.statusDetailsForm.addControl("id", new
FormControl(""));
+ this.statusDetailsForm.addControl("lastUpdated", new
FormControl(""));
+ this.getStatusDetails();
+ } else {
+ this.navSvc.headerTitle.next("New Status");
+ }
+ }
+
+ /**
+ * Reloads the servers table data.
+ *
+ * @param id is the id passed in route for this page if this is a edit
view.
+ */
+ public async getStatusDetails(): Promise<void> {
+ const id = Number(this.id) ; // id Type 'null' is not
assignable to type 'string'
+ this.statusDetails = await this.serverService.getStatuses(id);
+ const data: ResponseStatus = {
+ description: this.statusDetails.description,
+ id: this.statusDetails.id,
+ lastUpdated: new Date(),
+ name: this.statusDetails.name
+ };
+
+ // Set page title with status ID
+ this.navSvc.headerTitle.next(`Status #${data.id}`);
+
+ // Patch the form with existing data we got from service
requested above.
+ this.statusDetailsForm.patchValue(data);
Review Comment:
shouldn't you be able to just patch it with the status itself?
##########
experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.ts:
##########
@@ -0,0 +1,175 @@
+/*
+ * 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, OnInit } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { MatDialog } from "@angular/material/dialog";
+import { ActivatedRoute, Router } from "@angular/router";
+import { ResponseStatus } from "trafficops-types";
+
+import { ServerService } from "src/app/api";
+import { DecisionDialogComponent, DecisionDialogData } from
"src/app/shared/dialogs/decision-dialog/decision-dialog.component";
+import { NavigationService } from
"src/app/shared/navigation/navigation.service";
+
+/**
+ * StatusDetailsComponent is the controller for a status "details" page.
+ */
+@Component({
+ providers: [ServerService],
+ selector: "tp-status-details",
+ styleUrls: ["./status-details.component.scss"],
+ templateUrl: "./status-details.component.html",
+})
+export class StatusDetailsComponent implements OnInit {
+
+ /** Status ID expected from the route param using which we identify
whether we are creating new status or load existing status */
+ public id: string | number | null = null;
+
+ /** All details of status requested */
+ public statusDetails: ResponseStatus | null = null;
+
+ /** Reactive form intialized to creat / edit status details */
+ public statusDetailsForm!: FormGroup;
+
+ /** Loader status for the actions */
+ public loading = false;
+
+ /**
+ * Constructor.
+ *
+ * @param serverService The Servers API which is used to provide row
data.
+ * @param route A reference to the route of this view which is used to
get the 'id' query parameter of status.
+ * @param router Angular router
+ * @param dialog Dialog manager
+ * @param fb Form builder
+ * @param navSvc Manages the header
+ */
+ constructor(
+ private readonly serverService: ServerService,
+ private readonly route: ActivatedRoute,
+ private readonly router: Router,
+ private readonly dialog: MatDialog,
+ private readonly navSvc: NavigationService,
+ ) { }
+
+ /** Initializes table data, loading it from Traffic Ops. */
+ public ngOnInit(): void {
+ this.statusDetailsForm = new FormGroup({
+ description: new FormControl("",Validators.required),
+ name: new FormControl("",Validators.required),
+ });
+
+ // Getting id from the route
+ this.id = this.route.snapshot.paramMap.get("id");
+
+ // we check whether params is a number if not we shall assume
user wants to add a new status.
+ if (!this.isNew) {
+ this.loading = true;
+ this.statusDetailsForm.addControl("id", new
FormControl(""));
+ this.statusDetailsForm.addControl("lastUpdated", new
FormControl(""));
+ this.getStatusDetails();
+ } else {
+ this.navSvc.headerTitle.next("New Status");
+ }
+ }
+
+ /**
+ * Reloads the servers table data.
+ *
+ * @param id is the id passed in route for this page if this is a edit
view.
+ */
+ public async getStatusDetails(): Promise<void> {
+ const id = Number(this.id) ; // id Type 'null' is not
assignable to type 'string'
+ this.statusDetails = await this.serverService.getStatuses(id);
+ const data: ResponseStatus = {
+ description: this.statusDetails.description,
+ id: this.statusDetails.id,
+ lastUpdated: new Date(),
+ name: this.statusDetails.name
+ };
+
+ // Set page title with status ID
+ this.navSvc.headerTitle.next(`Status #${data.id}`);
+
+ // Patch the form with existing data we got from service
requested above.
+ this.statusDetailsForm.patchValue(data);
+ this.loading = false;
+ }
+
+ /**
+ * On submitting the form we check for whether we are performing Create
or Edit
+ */
+ public onSubmit(): void {
+ if (this.isNew) {
+ this.createStatus();
+
+ } else {
+ this.updateStatus();
+ }
+ }
+
+ /**
+ * For Creating a new status
+ */
+ public createStatus(): void {
+
this.serverService.createStatus(this.statusDetailsForm.value).then((res:
ResponseStatus) => {
+ if (res) {
+ this.id = (res?.id);
+
this.router.navigate([`/core/statuses/${this.id}`]);
+ this.navSvc.headerTitle.next(`Status
#${this.id}`);
+ }
+ });
+ }
+
+ /**
+ * For updating the Status
+ */
+ public updateStatus(): void {
+
this.serverService.updateStatusDetail(this.statusDetailsForm.value,
Number(this.id));
+ }
+
+ /**
+ * Deleteting status
+ */
+ public async deleteStatus(): Promise<void> {
+ const ref = this.dialog.open<DecisionDialogComponent,
DecisionDialogData, boolean>(DecisionDialogComponent, {
+ data: {
+ message: `This action CANNOT be undone. This
will permanently delete '${this.statusDetails?.name}'.`,
+ title: `Delete Status:
${this.statusDetails?.name}`
+ }
+ });
+
+ ref.afterClosed().subscribe(result => {
+ if (result) {
+ const id = Number(this.id);
+ this.serverService.deleteStatus(id).then(() => {
+
this.router.navigate(["/core/statuses"]);
+ });
+ }
+ });
+ }
+
+ /**
+ * Title for the page
+ */
+ public get title(): string {
+ return this.isNew ? "Add New Status" : "Edit Status";
+ }
Review Comment:
This property appears to be unused
##########
experimental/traffic-portal/src/app/api/server.service.ts:
##########
@@ -186,4 +187,33 @@ export class ServerService extends APIService {
return this.put(`servers/${id}/status`, {offlineReason,
status}).toPromise();
}
+
+ /**
+ * Creating new Status.
+ *
+ * @param data containes name and description for the status.
+ * @returns The 'response' property of the TO status response. See TO
API docs.
+ */
+ public async createStatus(data: ResponseStatus):
Promise<ResponseStatus> {
+ return this.post<ResponseStatus>("statuses", data).toPromise();
+ }
+
+ /**
+ * Updates status Details.
+ *
+ * @param data containes name and description for the status., unique
identifier thereof.
+ * @param id The Status ID
+ */
+ public async updateStatusDetail(data: ResponseStatus, id: number):
Promise<ResponseStatus | undefined> {
+ return this.put<ResponseStatus>(`statuses/${id}`,
data).toPromise();
+ }
+
+ /**
+ * Deletes an existing Status.
+ *
+ * @param id The Status ID
+ */
+ public async deleteStatus(id: number): Promise<void> {
+ return this.delete(`statuses/${id}`).toPromise();
+ }
Review Comment:
These new methods need to also be added to the testing service at
`src/app/api/testing/server.service.ts`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]