ocket8888 commented on code in PR #7497:
URL: https://github.com/apache/trafficcontrol/pull/7497#discussion_r1187817854
##########
experimental/traffic-portal/nightwatch/globals/globals.ts:
##########
@@ -163,267 +148,43 @@ export interface CreatedData {
profile: ResponseProfile;
}
-const testData = {};
+let testData = {};
+let client: DataClient;
+let dataCreateFailed = false;
const globals = {
adminPass: config.adminPass,
adminUser: config.adminUser,
+ after: async (done: () => void): Promise<void> => {
+ if (dataCreateFailed){
+ done();
+ return;
Review Comment:
This doesn't make any difference at all, but since `done` returns `void`,
you can do this on one line as `return done();`. Just something neat I thought
you might like to use.
##########
experimental/traffic-portal/nightwatch/dataClient.ts:
##########
@@ -0,0 +1,419 @@
+/*
+ * 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 * as https from "https";
+
+import axios, { AxiosError, AxiosInstance } from "axios";
+import { CreatedData } from "nightwatch/globals/globals";
+import {
+ CDN,
+ GeoLimit,
+ GeoProvider,
+ LoginRequest,
+ ProfileType,
+ Protocol,
+ RequestASN,
+ RequestCacheGroup,
+ RequestCoordinate,
+ RequestDeliveryService,
+ RequestDivision,
+ RequestPhysicalLocation,
+ RequestProfile,
+ RequestRegion, RequestServer, RequestStatus,
+ RequestSteeringTarget,
+ RequestTenant,
+ RequestType,
+ ResponseCacheGroup,
+ ResponseDeliveryService,
+ ResponseDivision,
+ ResponsePhysicalLocation, ResponseProfile,
+ ResponseRegion, ResponseStatus,
+ TypeFromResponse
+} from "trafficops-types";
Review Comment:
Some of these have multiple imported symbols on one line - why?
##########
experimental/traffic-portal/nightwatch/dataClient.ts:
##########
@@ -0,0 +1,419 @@
+/*
+ * 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 * as https from "https";
+
+import axios, { AxiosError, AxiosInstance } from "axios";
+import { CreatedData } from "nightwatch/globals/globals";
+import {
+ CDN,
+ GeoLimit,
+ GeoProvider,
+ LoginRequest,
+ ProfileType,
+ Protocol,
+ RequestASN,
+ RequestCacheGroup,
+ RequestCoordinate,
+ RequestDeliveryService,
+ RequestDivision,
+ RequestPhysicalLocation,
+ RequestProfile,
+ RequestRegion, RequestServer, RequestStatus,
+ RequestSteeringTarget,
+ RequestTenant,
+ RequestType,
+ ResponseCacheGroup,
+ ResponseDeliveryService,
+ ResponseDivision,
+ ResponsePhysicalLocation, ResponseProfile,
+ ResponseRegion, ResponseStatus,
+ TypeFromResponse
+} from "trafficops-types";
+
+/**
+ * Defines the class used to create test data for the E2E environment
+ */
+export class DataClient {
+ private readonly toURL: string;
+ private readonly apiVersion: string;
+ private readonly adminUser: string;
+ private readonly adminPass: string;
+ /** Tracks if the client has logged in */
+ public loggedIn = false;
+ /** Client used to talk to the TO API */
+ private readonly client: AxiosInstance;
+ public constructor(toURL: string, apiVersion: string, adminUser:
string, adminPass: string) {
+ this.toURL = toURL;
+ this.apiVersion = apiVersion;
+ this.adminUser = adminUser;
+ this.adminPass = adminPass;
+
+ this.client = axios.create({
+ httpsAgent: new https.Agent({
+ rejectUnauthorized: false
+ })
+ });
+ }
+
+ /**
+ * Creates data needed for the E2E tests
+ *
+ * @param id ID added to various fields to ensure that creation occurs
regardless of environment
+ */
+ public async createData(id: string): Promise<CreatedData> {
+ const apiUrl = `${this.toURL}/api/${this.apiVersion}`;
+ if
(Object.keys(this.client.defaults.headers.common).indexOf("Cookie") === -1) {
+ this.loggedIn = false;
+ let accessToken = "";
+ const loginReq: LoginRequest = {
+ p: this.adminPass,
+ u: this.adminUser
+ };
+ try {
+ const logResp = await
this.client.post(`${apiUrl}/user/login`, JSON.stringify(loginReq));
+ if (logResp.headers["set-cookie"]) {
+ for (const cookie of
logResp.headers["set-cookie"]) {
+ if
(cookie.indexOf("access_token") > -1) {
+ accessToken = cookie;
+ break;
+ }
+ }
+ }
+ } catch (e) {
+ console.error((e as AxiosError).message);
+ throw e;
+ }
+ if (accessToken === "") {
+ const e = new Error("Access token is not set");
+ console.error(e.message);
+ throw e;
+ }
+ this.loggedIn = true;
+ this.client.defaults.headers.common = {Cookie:
accessToken};
+ }
+
+ const cdn: CDN = {
+ dnssecEnabled: false, domainName: `tests${id}.com`,
name: `testCDN${id}`
+ };
+
+ let resp = await this.client.get(`${apiUrl}/types`);
+ const types: Array<TypeFromResponse> = resp.data.response;
+ const httpType = types.find(typ => typ.name === "HTTP" &&
typ.useInTable === "deliveryservice");
+ if (httpType === undefined) {
+ throw new Error("Unable to find `HTTP` type");
+ }
+ const steeringType = types.find(typ => typ.name === "STEERING"
&& typ.useInTable === "deliveryservice");
+ if (steeringType === undefined) {
+ throw new Error("Unable to find `STEERING` type");
+ }
+ const steeringWeightType = types.find(typ => typ.name ===
"STEERING_WEIGHT" && typ.useInTable === "steering_target");
+ if (steeringWeightType === undefined) {
+ throw new Error("Unable to find `STEERING_WEIGHT`
type");
+ }
+ const cgType = types.find(typ => typ.useInTable ===
"cachegroup");
+ if (!cgType) {
+ throw new Error("Unable to find any Cache Group Types");
+ }
+ const edgeType = types.find(typ => typ.useInTable === "server"
&& typ.name === "EDGE");
+ if (edgeType === undefined) {
+ throw new Error("Unable to find `EDGE` type");
+ }
+
+ const data = {} as CreatedData;
+ let url = `${apiUrl}/cdns`;
+ try {
+ resp = await this.client.post(url, JSON.stringify(cdn));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on CDN post as
${url}`;
+ throw e;
+ }
+ const respCDN = resp.data.response;
+ data.cdn = respCDN;
+
+ const ds: RequestDeliveryService = {
+ active: false,
+ cacheurl: null,
+ cdnId: respCDN.id,
+ displayName: `test DS${id}`,
+ dscp: 0,
+ ecsEnabled: false,
+ edgeHeaderRewrite: null,
+ fqPacingRate: null,
+ geoLimit: GeoLimit.NONE,
+ geoProvider: GeoProvider.MAX_MIND,
+ httpBypassFqdn: null,
+ infoUrl: null,
+ initialDispersion: 1,
+ ipv6RoutingEnabled: false,
+ logsEnabled: false,
+ maxOriginConnections: 0,
+ maxRequestHeaderBytes: 0,
+ midHeaderRewrite: null,
+ missLat: 0,
+ missLong: 0,
+ multiSiteOrigin: false,
+ orgServerFqdn: "http://test.com",
+ profileId: 1,
+ protocol: Protocol.HTTP,
+ qstringIgnore: 0,
+ rangeRequestHandling: 0,
+ regionalGeoBlocking: false,
+ remapText: null,
+ routingName: "test",
+ signed: false,
+ tenantId: 1,
+ typeId: httpType.id,
+ xmlId: `testDS${id}`
+ };
+ url = `${apiUrl}/deliveryservices`;
+ try {
+ resp = await this.client.post(url, JSON.stringify(ds));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on DS post at
${url}`;
+ throw e;
+ }
Review Comment:
The point of reassigning the url on each request in the original was that
you could wrap the entire functionality in a single try/catch and not have so
much repetition
##########
experimental/traffic-portal/nightwatch/page_objects/servers/serversTable.ts:
##########
@@ -16,24 +16,45 @@ import {
EnhancedSectionInstance,
NightwatchAPI
} from "nightwatch";
+import { ResponseServer } from "trafficops-types";
Review Comment:
nit: could be `import type`
##########
experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.html:
##########
@@ -101,96 +113,159 @@
<mat-label>Status Last Updated</mat-label>
<input matInput name="statusLastUpdated"
disabled [value]="server.statusLastUpdated.toLocaleString()"/>
</mat-form-field>
- <fieldset>
- <legend
(click)="hideInterfaces=!hideInterfaces">Interfaces<button
name="addInterfaceBtn" class="add-button" type="button" title="add a new
interface" (click)="addInterface($event)"><fa-icon
[icon]="addIcon"></fa-icon></button></legend>
- <fieldset [hidden]="hideInterfaces" *ngFor="let
inf of server.interfaces; index as infInd">
- <legend>
- <label
for="{{inf.name}}-name">Name</label>
- <input [(ngModel)]="inf.name"
required aria-label="Interface name" id="{{inf.name}}-name"
name="{{inf.name}}-name" class="interface-name-input"/>
- <button class="remove-button"
type="button" title="delete this interface"
(click)="deleteInterface(infInd)"><fa-icon
[icon]="removeIcon"></fa-icon></button>
- </legend>
-
- <div>
- <mat-checkbox
[labelPosition]="'before'" id="{{inf.name}}-monitor"
name="{{inf.name}}-monitor" [(ngModel)]="inf.monitor">Monitor this
interface</mat-checkbox>
- <mat-form-field>
- <mat-label><abbr
title="Maximum Transmission Unit">MTU</abbr></mat-label>
- <input matInput
id="{{inf.name}}-mtu" name="{{inf.name}}-mtu" type="number" min="1500"
max="9000" step="7500" [(ngModel)]="inf.mtu"/>
- </mat-form-field>
- <!-- <small class="input-error"
ng-show="hasPropertyError(serverForm[inf.name+'-mtu'], 'min') ||
hasPropertyError(serverForm[inf.name+'-mtu'], 'max') ||
hasPropertyError(serverForm[inf.name+'-mtu'], 'step')">Invalid MTU - must be
1500 or 9000</small> -->
- <mat-form-field>
- <mat-label>Maximum
Bandwidth</mat-label>
- <input matInput
id="{{inf.name}}-max-bandwidth" [(ngModel)]="inf.maxBandwidth" min="0"
type="number" name="{{inf.name}}-max-bandwidth"/>
- <mat-hint
class="input-warning" *ngIf="inf.maxBandwidth !== 0">Setting Max Bandwidth to
zero will cause cache servers to always be unavailable</mat-hint>
- </mat-form-field>
- <!-- <small class="input-error"
ng-show="hasPropertyError(serverForm[inf.name+'-max-bandwidth'], 'min')">Cannot
be negative</small> -->
+ <mat-expansion-panel [expanded]="true">
+ <mat-expansion-panel-header>
+ <mat-panel-title>
+ <p>Profiles</p>
Review Comment:
`<p>`?
##########
experimental/traffic-portal/nightwatch/dataClient.ts:
##########
@@ -0,0 +1,419 @@
+/*
+ * 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 * as https from "https";
+
+import axios, { AxiosError, AxiosInstance } from "axios";
+import { CreatedData } from "nightwatch/globals/globals";
+import {
+ CDN,
+ GeoLimit,
+ GeoProvider,
+ LoginRequest,
+ ProfileType,
+ Protocol,
+ RequestASN,
+ RequestCacheGroup,
+ RequestCoordinate,
+ RequestDeliveryService,
+ RequestDivision,
+ RequestPhysicalLocation,
+ RequestProfile,
+ RequestRegion, RequestServer, RequestStatus,
+ RequestSteeringTarget,
+ RequestTenant,
+ RequestType,
+ ResponseCacheGroup,
+ ResponseDeliveryService,
+ ResponseDivision,
+ ResponsePhysicalLocation, ResponseProfile,
+ ResponseRegion, ResponseStatus,
+ TypeFromResponse
+} from "trafficops-types";
+
+/**
+ * Defines the class used to create test data for the E2E environment
+ */
+export class DataClient {
+ private readonly toURL: string;
+ private readonly apiVersion: string;
+ private readonly adminUser: string;
+ private readonly adminPass: string;
+ /** Tracks if the client has logged in */
+ public loggedIn = false;
+ /** Client used to talk to the TO API */
+ private readonly client: AxiosInstance;
+ public constructor(toURL: string, apiVersion: string, adminUser:
string, adminPass: string) {
+ this.toURL = toURL;
+ this.apiVersion = apiVersion;
+ this.adminUser = adminUser;
+ this.adminPass = adminPass;
+
+ this.client = axios.create({
+ httpsAgent: new https.Agent({
+ rejectUnauthorized: false
+ })
+ });
+ }
+
+ /**
+ * Creates data needed for the E2E tests
+ *
+ * @param id ID added to various fields to ensure that creation occurs
regardless of environment
+ */
+ public async createData(id: string): Promise<CreatedData> {
+ const apiUrl = `${this.toURL}/api/${this.apiVersion}`;
+ if
(Object.keys(this.client.defaults.headers.common).indexOf("Cookie") === -1) {
+ this.loggedIn = false;
+ let accessToken = "";
+ const loginReq: LoginRequest = {
+ p: this.adminPass,
+ u: this.adminUser
+ };
+ try {
+ const logResp = await
this.client.post(`${apiUrl}/user/login`, JSON.stringify(loginReq));
+ if (logResp.headers["set-cookie"]) {
+ for (const cookie of
logResp.headers["set-cookie"]) {
+ if
(cookie.indexOf("access_token") > -1) {
+ accessToken = cookie;
+ break;
+ }
+ }
+ }
+ } catch (e) {
+ console.error((e as AxiosError).message);
+ throw e;
+ }
+ if (accessToken === "") {
+ const e = new Error("Access token is not set");
+ console.error(e.message);
+ throw e;
+ }
+ this.loggedIn = true;
+ this.client.defaults.headers.common = {Cookie:
accessToken};
+ }
+
+ const cdn: CDN = {
+ dnssecEnabled: false, domainName: `tests${id}.com`,
name: `testCDN${id}`
+ };
+
+ let resp = await this.client.get(`${apiUrl}/types`);
+ const types: Array<TypeFromResponse> = resp.data.response;
+ const httpType = types.find(typ => typ.name === "HTTP" &&
typ.useInTable === "deliveryservice");
+ if (httpType === undefined) {
+ throw new Error("Unable to find `HTTP` type");
+ }
+ const steeringType = types.find(typ => typ.name === "STEERING"
&& typ.useInTable === "deliveryservice");
+ if (steeringType === undefined) {
+ throw new Error("Unable to find `STEERING` type");
+ }
+ const steeringWeightType = types.find(typ => typ.name ===
"STEERING_WEIGHT" && typ.useInTable === "steering_target");
+ if (steeringWeightType === undefined) {
+ throw new Error("Unable to find `STEERING_WEIGHT`
type");
+ }
+ const cgType = types.find(typ => typ.useInTable ===
"cachegroup");
+ if (!cgType) {
+ throw new Error("Unable to find any Cache Group Types");
+ }
+ const edgeType = types.find(typ => typ.useInTable === "server"
&& typ.name === "EDGE");
+ if (edgeType === undefined) {
+ throw new Error("Unable to find `EDGE` type");
+ }
+
+ const data = {} as CreatedData;
+ let url = `${apiUrl}/cdns`;
+ try {
+ resp = await this.client.post(url, JSON.stringify(cdn));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on CDN post as
${url}`;
+ throw e;
+ }
+ const respCDN = resp.data.response;
+ data.cdn = respCDN;
+
+ const ds: RequestDeliveryService = {
+ active: false,
+ cacheurl: null,
+ cdnId: respCDN.id,
+ displayName: `test DS${id}`,
+ dscp: 0,
+ ecsEnabled: false,
+ edgeHeaderRewrite: null,
+ fqPacingRate: null,
+ geoLimit: GeoLimit.NONE,
+ geoProvider: GeoProvider.MAX_MIND,
+ httpBypassFqdn: null,
+ infoUrl: null,
+ initialDispersion: 1,
+ ipv6RoutingEnabled: false,
+ logsEnabled: false,
+ maxOriginConnections: 0,
+ maxRequestHeaderBytes: 0,
+ midHeaderRewrite: null,
+ missLat: 0,
+ missLong: 0,
+ multiSiteOrigin: false,
+ orgServerFqdn: "http://test.com",
+ profileId: 1,
+ protocol: Protocol.HTTP,
+ qstringIgnore: 0,
+ rangeRequestHandling: 0,
+ regionalGeoBlocking: false,
+ remapText: null,
+ routingName: "test",
+ signed: false,
+ tenantId: 1,
+ typeId: httpType.id,
+ xmlId: `testDS${id}`
+ };
+ url = `${apiUrl}/deliveryservices`;
+ try {
+ resp = await this.client.post(url, JSON.stringify(ds));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on DS post at
${url}`;
+ throw e;
+ }
+ let respDS: ResponseDeliveryService = resp.data.response[0];
+ data.ds = respDS;
+
+ ds.displayName = `test DS2${id}`;
+ ds.xmlId = `testDS2${id}`;
+ try {
+ resp = await this.client.post(url, JSON.stringify(ds));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on DS post at
${url}`;
+ throw e;
+ }
+ respDS = resp.data.response[0];
+ data.ds2 = respDS;
+
+ ds.displayName = `test steering DS${id}`;
+ ds.xmlId = `testSDS${id}`;
+ ds.typeId = steeringType.id;
+ try {
+ resp = await this.client.post(url, JSON.stringify(ds));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on DS post at
${url}`;
+ throw e;
+ }
+ respDS = resp.data.response[0];
+ data.steeringDS = respDS;
+
+ const target: RequestSteeringTarget = {
+ targetId: data.ds.id,
+ typeId: steeringWeightType.id,
+ value: 1
+ };
+ url = `${apiUrl}/steering/${data.steeringDS.id}/targets`;
+ try {
+ await this.client.post(url, JSON.stringify(target));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on at ${url}`;
+ throw e;
+ }
+ target.targetId = data.ds2.id;
+ try {
+ await this.client.post(url, JSON.stringify(target));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on Steering post
at ${url}`;
+ throw e;
+ }
+
+ const tenant: RequestTenant = {
+ active: true,
+ name: `testT${id}`,
+ parentId: 1
+ };
+ url = `${apiUrl}/tenants`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(tenant));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on Tenant post at
${url}`;
+ throw e;
+ }
+ data.tenant = resp.data.response;
+
+ const division: RequestDivision = {
+ name: `testD${id}`
+ };
+ url = `${apiUrl}/divisions`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(division));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on Divisions post
at ${url}`;
+ throw e;
+ }
+ const respDivision: ResponseDivision = resp.data.response;
+ data.division = respDivision;
+
+ const region: RequestRegion = {
+ division: respDivision.id,
+ name: `testR${id}`
+ };
+ url = `${apiUrl}/regions`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(region));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on Regions post
at ${url}`;
+ throw e;
+ }
+ const respRegion: ResponseRegion = resp.data.response;
+ data.region = respRegion;
+
+ const cacheGroup: RequestCacheGroup = {
+ name: `test${id}`,
+ shortName: `test${id}`,
+ typeId: cgType.id
+ };
+ url = `${apiUrl}/cachegroups`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(cacheGroup));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on CacheGroups
post at ${url}`;
+ throw e;
+ }
+ const responseCG: ResponseCacheGroup = resp.data.response;
+ data.cacheGroup = responseCG;
+
+ const asn: RequestASN = {
+ asn: +id,
+ cachegroupId: responseCG.id
+ };
+ url = `${apiUrl}/asns`;
+ try {
+ resp = await this.client.post(url, JSON.stringify(asn));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on ASN post at
${url}`;
+ throw e;
+ }
+ data.asn = resp.data.response;
+
+ const physLoc: RequestPhysicalLocation = {
+ address: "street",
+ city: "city",
+ comments: "someone set us up the bomb",
+ email: "[email protected]",
+ name: `phys${id}`,
+ phone: "111-867-5309",
+ poc: "me",
+ regionId: respRegion.id,
+ shortName: `short${id}`,
+ state: "CA",
+ zip: "80000"
+ };
+ url = `${apiUrl}/phys_locations`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(physLoc));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on PhysLoc post
at ${url}`;
+ throw e;
+ }
+ const respPhysLoc: ResponsePhysicalLocation =
resp.data.response;
+ respPhysLoc.region = respRegion.name;
+ data.physLoc = respPhysLoc;
+
+ const coordinate: RequestCoordinate = {
+ latitude: 0,
+ longitude: 0,
+ name: `coord${id}`
+ };
+ url = `${apiUrl}/coordinates`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(coordinate));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on Coordinates
post at ${url}`;
+ throw e;
+ }
+ data.coordinate = resp.data.response;
+
+ const type: RequestType = {
+ description: "blah",
+ name: `type${id}`,
+ useInTable: "server"
+ };
+ url = `${apiUrl}/types`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(type));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on Types post at
${url}`;
+ throw e;
+ }
+
+ data.type = resp.data.response;
+ const status: RequestStatus = {
+ description: "blah",
+ name: `status${id}`,
+ };
+ url = `${apiUrl}/statuses`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(status));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on Status post at
${url}`;
+ throw e;
+ }
+ const respStatus: ResponseStatus = resp.data.response;
+ data.statuses = respStatus;
+
+ const profile: RequestProfile = {
+ cdn: respCDN.id,
+ description: "blah",
+ name: `profile${id}`,
+ routingDisabled: false,
+ type: ProfileType.ATS_PROFILE,
+ };
+ url = `${apiUrl}/profiles`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(profile));
+ } catch(e) {
+ (e as AxiosError).message += ` Failed on Profiles post
at ${url}`;
+ throw e;
+ }
+ const respProfile: ResponseProfile = resp.data.response;
+ data.profile = respProfile;
+
+ const server: RequestServer = {
+ cachegroupId: responseCG.id,
+ cdnId: respCDN.id,
+ domainName: "domain.com",
+ hostName: id,
+ interfaces: [{
+ ipAddresses: [{
+ address: "192.160.1.0",
+ gateway: null,
+ serviceAddress: true
+ }],
+ maxBandwidth: 0,
+ monitor: true,
+ mtu: 1500,
+ name: "eth0"
+ }],
+ physLocationId: respPhysLoc.id,
+ profileNames: [respProfile.name],
+ statusId: respStatus.id,
+ typeId: edgeType.id
+
+ };
+ url = `${apiUrl}/servers`;
+ try {
+ resp = await this.client.post(url,
JSON.stringify(server));
+ } catch(e) {
+ console.log(e);
+ (e as AxiosError).message += ` Failed on Servers post
at ${url}`;
+ throw e;
+ }
+ data.edgeServer = resp.data.response;
+
+ return Promise.resolve(data);
Review Comment:
Wrapping this in a promise is superfluous
##########
experimental/traffic-portal/src/app/core/misc/isogeneration-form/isogeneration-form.component.html:
##########
@@ -79,31 +79,6 @@
</mat-form-field>
</fieldset>
</mat-card>
- <mat-card appearance="outlined">
- <fieldset>
- <legend mat-card-title>Management
Network Interface Details</legend>
- <mat-form-field>
- <mat-label>Management Interface
Device Name</mat-label>
- <input matInput type="text"
formControlName="mgmtInterface" placeholder="eth0"/>
- <mat-hint>This should be the
name of the network device relative to <code>/dev/</code>.</mat-hint>
- </mat-form-field>
- <mat-form-field>
- <mat-label>Management IPv4
Address</mat-label>
- <input matInput type="text"
formControlName="mgmtIpAddress"/>
- <mat-error>Invalid IPv4
address</mat-error>
- </mat-form-field>
- <mat-form-field>
- <mat-label>Management Gateway
IPv4 Address</mat-label>
- <input matInput type="text"
formControlName="mgmtIpGateway"/>
- <mat-error>Invalid IPv4
address</mat-error>
- </mat-form-field>
- <mat-form-field>
- <mat-label>Management IPv4
Netmask</mat-label>
- <input matInput type="text"
formControlName="mgmtIpNetmask"/>
- <mat-error>Invalid IPv4
Netmask</mat-error>
- </mat-form-field>
- </fieldset>
- </mat-card>
Review Comment:
I regret to inform you that these properties of a server still exist in
APIv4 - and indeed APIv5 at the time of this writing.
##########
experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.ts:
##########
@@ -13,16 +13,24 @@
*/
import { Component , type OnInit} from "@angular/core";
-import { UntypedFormControl } from "@angular/forms";
+import { FormControl } from "@angular/forms";
import { MatDialog } from "@angular/material/dialog";
import { ActivatedRoute } from "@angular/router";
import type { ITooltipParams } from "ag-grid-community";
import { BehaviorSubject } from "rxjs";
-import { type ResponseServer, serviceAddresses } from "trafficops-types";
+import { ResponseCDN, type ResponseServer, serviceAddresses } from
"trafficops-types";
-import { ServerService } from "src/app/api";
+import { CDNService, ServerService } from "src/app/api";
import { UpdateStatusComponent } from
"src/app/core/servers/update-status/update-status.component";
-import type { ContextMenuActionEvent, ContextMenuItem } from
"src/app/shared/generic-table/generic-table.component";
+import { CurrentUserService } from
"src/app/shared/current-user/current-user.service";
+import {
+ CollectionChoiceDialogComponent, CollectionChoiceDialogData
+} from
"src/app/shared/dialogs/collection-choice-dialog/collection-choice-dialog.component";
+import type {
+ ContextMenuActionEvent,
+ ContextMenuItem, DoubleClickLink,
+ TableTitleButton
Review Comment:
mixed single-item and multi-item import lines
##########
experimental/traffic-portal/nightwatch/globals/globals.ts:
##########
@@ -163,267 +148,43 @@ export interface CreatedData {
profile: ResponseProfile;
}
-const testData = {};
+let testData = {};
+let client: DataClient;
+let dataCreateFailed = false;
const globals = {
adminPass: config.adminPass,
adminUser: config.adminUser,
+ after: async (done: () => void): Promise<void> => {
+ if (dataCreateFailed){
+ done();
+ return;
+ } else if(client.loggedIn) {
+ try {
+ await
client.createData(String((+globals.uniqueString)-1));
Review Comment:
Do we need to subtract one from the unique string? I can't see what that
buys us, and it forces us to ensure the type of the unique string isn't
actually `string`, but instead <code>\`${number}\`</code> (without having any
compile-time safety around that).
##########
experimental/traffic-portal/src/app/api/server.service.spec.ts:
##########
@@ -14,13 +14,14 @@
*/
import { HttpClientTestingModule, HttpTestingController } from
"@angular/common/http/testing";
import { TestBed } from "@angular/core/testing";
+import { ResponseServer } from "trafficops-types";
Review Comment:
nit: could be `import type`
##########
experimental/traffic-portal/nightwatch/globals/globals.ts:
##########
@@ -163,267 +148,43 @@ export interface CreatedData {
profile: ResponseProfile;
}
-const testData = {};
+let testData = {};
+let client: DataClient;
+let dataCreateFailed = false;
const globals = {
adminPass: config.adminPass,
adminUser: config.adminUser,
+ after: async (done: () => void): Promise<void> => {
+ if (dataCreateFailed){
+ done();
+ return;
+ } else if(client.loggedIn) {
+ try {
+ await
client.createData(String((+globals.uniqueString)-1));
+ } catch(e) {
+ console.error(`Idempotency test failed, err:
${e}`);
Review Comment:
instead of embedding `e` in an interpolated string, if you pass it as a
parameter to `console.error`, you can get it to print out a stack trace.
##########
experimental/traffic-portal/nightwatch/globals/globals.ts:
##########
@@ -163,267 +148,43 @@ export interface CreatedData {
profile: ResponseProfile;
}
-const testData = {};
+let testData = {};
+let client: DataClient;
+let dataCreateFailed = false;
const globals = {
adminPass: config.adminPass,
adminUser: config.adminUser,
+ after: async (done: () => void): Promise<void> => {
+ if (dataCreateFailed){
+ done();
+ return;
+ } else if(client.loggedIn) {
+ try {
+ await
client.createData(String((+globals.uniqueString)-1));
+ } catch(e) {
+ console.error(`Idempotency test failed, err:
${e}`);
+ throw e;
+ }
+ console.log("Data creation is idempotent");
+ } else {
+ console.log("Client not logged in, skipping idempotency
test");
+ }
+ done();
+ },
afterEach: (browser: NightwatchBrowser, done: () => void): void => {
browser.end(() => {
done();
});
},
- apiVersion: "3.1",
+ apiVersion: "4.1",
Review Comment:
We can't use 4.1 as it's unreleased and the TO types package only supports
released API versions; in this case we should be using 4.0.
##########
experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.spec.ts:
##########
@@ -157,16 +157,20 @@ describe("ServerDetailsComponent", () => {
}));
it("opens the 'change status' dialog", () => {
+ /*
expect(component.changeStatusDialogOpen).toBeFalse();
component.changeStatus(new MouseEvent("click"));
expect(component.changeStatusDialogOpen).toBeTrue();
component.isNew = true;
expect(() => component.changeStatus(new
MouseEvent("click"))).toThrow();
+*/
});
it("closes the 'change status' dialog when done", () => {
+ /*
component.changeStatusDialogOpen = true;
component.doneUpdatingStatus(true);
expect(component.changeStatusDialogOpen).toBeFalse();
+*/
Review Comment:
these should be reworked if necessary, removed if no longer relevant, but
not just commented out.
##########
experimental/traffic-portal/nightwatch/tests/servers/servers.detail.spec.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.
+ */
+
+describe("Servers Table Spec", () => {
Review Comment:
This is not the servers table spec, it's for the server details view
##########
experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.ts:
##########
@@ -320,23 +347,27 @@ export class ServersTableComponent implements OnInit {
public fuzzySubject: BehaviorSubject<string>;
/** Form controller for the user search input. */
- public fuzzControl: UntypedFormControl = new UntypedFormControl("");
+ public fuzzControl: FormControl = new FormControl("");
/**
* Constructs the component with its required injections.
*
+ * @param auth The user authorization service.
* @param api The Servers API which is used to provide row data.
* @param route A reference to the route of this view which is used to
set the fuzzy search box text from the 'search' query parameter.
- * @param router Angular router
* @param navSvc Manages the header
+ * @param cdn The CDN API
* @param dialog Dialog manager
*/
Review Comment:
fwiw, you can just get rid of this. With the switch from TSLint to
`typescript-eslint` it became possible to not require JSDoc comments on
constructors.
##########
experimental/traffic-portal/src/app/core/servers/update-status/update-status.component.html:
##########
@@ -17,7 +17,7 @@ <h2 mat-dialog-title>Change status for {{serverName}}</h2>
<mat-form-field appearance="fill" *ngIf="true">
<mat-label>New Status</mat-label>
<select name="status" [(ngModel)]="status" required
matNativeControl>
- <option *ngFor="let status of statuses"
[value]="status" [disabled]="status.id ===
currentStatus">{{status.name}}</option>
+ <option *ngFor="let status of statuses"
[value]="status.name" [disabled]="status.id ===
currentStatus">{{status.name}}</option>
Review Comment:
were these changes to the update-status component necessary? I don't
necessarily disagree that they don't simplify it, since apparently the name is
the only information we need, but it clutters the PR to do a refactor in the
midst of such a large, unrelated change.
##########
experimental/traffic-portal/src/app/api/server.service.ts:
##########
@@ -14,7 +14,13 @@
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
-import type { RequestServer, RequestStatus, ResponseServer, ResponseStatus,
Servercheck, ServerQueueResponse } from "trafficops-types";
+import type {
+ RequestServer,
+ RequestStatus,
+ ResponseServer,
+ ResponseStatus,
+ Servercheck, ServerQueueResponse
+} from "trafficops-types";
Review Comment:
mixed single-item and multi-item import lines
##########
experimental/traffic-portal/src/app/api/cdn.service.ts:
##########
@@ -78,6 +78,28 @@ export class CDNService extends APIService {
return this.post<ResponseCDN>("cdns", cdn).toPromise();
}
+ /**
+ * Queue updates to servers by a CDN
+ *
+ * @param id The CDN id to queue server updates for
+ */
+ public async queueServerUpdates(id: number): Promise<CDNQueueResponse> {
+ const path = `cdns/${id}/queue_update`;
+ const action = {action: "queue"};
+ return this.post<CDNQueueResponse>(path, action).toPromise();
+ }
+
+ /**
+ * Dequeue updates to servers by a CDN
+ *
+ * @param id The CDN id to dequeue server updates for
+ */
+ public async dequeueServerUpdates(id: number):
Promise<CDNQueueResponse> {
Review Comment:
these should also be able to take in `ResponseCDN` objects in addition to
just the IDs thereof
##########
experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.scss:
##########
@@ -160,8 +133,12 @@ form {
form {
grid-template-columns: 1fr;
- fieldset div, {
+ fieldset div, .mat-expansion-panel .expansion-content{
grid-template-columns: 1fr 1fr;
}
+
+ .expansion-content-profile {
+ grid-template-columns: 1fr !important;
Review Comment:
use of `!important` worries me; why is that necessary?
--
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]