Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,540 changes: 1,802 additions & 6,738 deletions experimental/traffic-portal/package-lock.json

Large diffs are not rendered by default.

66 changes: 40 additions & 26 deletions experimental/traffic-portal/src/app/api/cdn.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
*/
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";

import type { CDN } from "src/app/models";
import type { CDN, ResponseCDN, Snapshot } from "trafficops-types";

import { APIService } from "./base-api.service";

Expand All @@ -28,8 +27,8 @@ export class CDNService extends APIService {
super(http);
}

public async getCDNs(id: number): Promise<CDN>;
public async getCDNs(): Promise<Map<string, CDN>>;
public async getCDNs(id: number | string): Promise<ResponseCDN>;
public async getCDNs(): Promise<Map<string, ResponseCDN>>;
/**
* Gets one or all CDNs from Traffic Ops
*
Expand All @@ -38,30 +37,45 @@ export class CDNService extends APIService {
* passed.
* (In the event that `id` is passed but does not match any CDN, `null` will be emitted)
*/
public async getCDNs(id?: number): Promise<Map<string, CDN> | CDN> {
public async getCDNs(id?: number | string): Promise<Map<string, ResponseCDN> | ResponseCDN> {
const path = "cdns";
if (id) {
return this.get<[CDN]>(path, undefined, {id: String(id)}).toPromise().then(
r => r[0]
).catch(
e => {
console.error(`Failed to get CDN #${id}`, e);
return {
dnssecEnabled: false,
domainName: "",
id: -1,
name: "",
};
}
);
if (typeof(id) === "number") {
const r = await this.get<[ResponseCDN]>(path, undefined, {id: String(id)}).toPromise();
if (r.length !== 1) {
throw new Error(`got ${r.length} CDNs with ID ${id}`);
}
return r[0];
}
return this.get<Array<CDN>>(path).toPromise().then(
r => new Map<string, CDN>(r.map(c=>[c.name, c]))
).catch(
e => {
console.error("Failed to get CDNs:", e);
return new Map();
if (typeof(id) === "string") {
const r = await this.get<[ResponseCDN]>(path, undefined, {name: id}).toPromise();
if (r.length !== 1) {
throw new Error(`got ${r.length} CDNs with Name ${id}`);
}
);
return r[0];
}
const cdns = await this.get<Array<ResponseCDN>>(path).toPromise();
return new Map(cdns.map(c=>[c.name, c]));
}

/**
* Gets the *current* Snapshot for a given CDN.
*
* @param cdn The CDN for which to fetch a Snapshot.
* @returns The current Snapshot of the requested CDN.
*/
public async getCurrentSnapshot(cdn: CDN | string): Promise<Snapshot> {
const name = typeof(cdn) === "string" ? cdn : cdn.name;
return this.get<Snapshot>(`cdns/${name}/snapshot`).toPromise();
}

/**
* Gets the *pending* Snapshot for a given CDN.
*
* @param cdn The CDN for which to fetch a Snapshot.
* @returns The current Snapshot of the requested CDN.
*/
public async getPendingSnapshot(cdn: CDN | string): Promise<Snapshot> {
const name = typeof(cdn) === "string" ? cdn : cdn.name;
return this.get<Snapshot>(`cdns/${name}/snapshot/new`).toPromise();
}
}
52 changes: 47 additions & 5 deletions experimental/traffic-portal/src/app/api/testing/cdn.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
* limitations under the License.
*/
import { Injectable } from "@angular/core";

import { CDN } from "src/app/models";
import type { CDN, ResponseCDN, Snapshot } from "trafficops-types";

/**
* CDNService expose API functionality relating to CDNs.
Expand Down Expand Up @@ -44,8 +43,21 @@ export class CDNService {
]
]);

public async getCDNs(id: number): Promise<CDN>;
public async getCDNs(): Promise<Map<string, CDN>>;
private readonly snapshots: Record<PropertyKey, {current: Snapshot; pending: Snapshot}> = {
// this is, unfortunately, standard
// eslint-disable-next-line @typescript-eslint/naming-convention
ALL: {
current: {},
pending: {}
},
test: {
current: {},
pending: {}
}
};

public async getCDNs(id: number): Promise<ResponseCDN>;
public async getCDNs(): Promise<Map<string, ResponseCDN>>;
/**
* Gets one or all CDNs from Traffic Ops
*
Expand All @@ -54,7 +66,7 @@ export class CDNService {
* passed.
* (In the event that `id` is passed but does not match any CDN, `null` will be emitted)
*/
public async getCDNs(id?: number): Promise<Map<string, CDN> | CDN> {
public async getCDNs(id?: number): Promise<Map<string, ResponseCDN> | ResponseCDN> {
if (id !== undefined) {
const cdn = Array.from(this.cdns.values()).filter(c=>c.id===id)[0];
if (!cdn) {
Expand All @@ -64,4 +76,34 @@ export class CDNService {
}
return this.cdns;
}

/**
* Gets the *current* Snapshot for a given CDN.
*
* @param cdn The CDN for which to fetch a Snapshot.
* @returns The current Snapshot of the requested CDN.
*/
public async getCurrentSnapshot(cdn: CDN | string): Promise<Snapshot> {
const name = typeof(cdn) === "string" ? cdn : cdn.name;
const snap = this.snapshots[name];
if (!snap) {
throw new Error(`no such CDN: '${name}'`);
}
return snap.current;
}

/**
* Gets the *pending* Snapshot for a given CDN.
*
* @param cdn The CDN for which to fetch a Snapshot.
* @returns The current Snapshot of the requested CDN.
*/
public async getPendingSnapshot(cdn: CDN | string): Promise<Snapshot> {
const name = typeof(cdn) === "string" ? cdn : cdn.name;
const snap = this.snapshots[name];
if (!snap) {
throw new Error(`no such CDN: '${name}'`);
}
return snap.pending;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!--
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.
-->

<code *ngIf="name">{{name}}</code>
<p class="no-change" *ngIf="(value.oldValue === undefined && value.newValue === undefined) || (value.oldValue === '' && value.newValue === ''); else oneIsDefined"><code>(no value)</code></p>
<ng-template #oneIsDefined>
<p *ngIf="value.oldValue === undefined || value.oldValue === ''"><ins>{{value.newValue}}</ins></p>
<p *ngIf="value.newValue === undefined || value.newValue === ''"><del>{{value.oldValue}}</del></p>
<ng-container *ngIf="value.oldValue !== undefined && value.newValue !== undefined && value.oldValue !== '' && value.newValue !== ''">
<p *ngIf="value.oldValue !== value.newValue">
<del>{{value.oldValue}}</del>&nbsp;<ins>{{value.newValue}}</ins>
</p>
<p class="no-change" *ngIf="value.oldValue === value.newValue">{{value.oldValue}}</p>
</ng-container>
</ng-template>
<mat-divider *ngIf="!last"></mat-divider>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.
*/
ins, del {
color: black;
}

ins {
background-color: #abf2bc;
text-decoration: underline;

&::before {
content: "+";
text-decoration: none;
}
}

del {
background-color: #ffc0c0;
text-decoration: line-through;

&::before {
content: "-";
text-decoration: none;
}
}

.no-change {
color: rgba(0, 0, 0, 0.54)
}

// p {
// margin-top: 0;
// }

code::after {
content: ":";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 { DiffFieldComponent } from "./diff-field.component";

describe("DiffFieldComponent", () => {
let component: DiffFieldComponent;
let fixture: ComponentFixture<DiffFieldComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DiffFieldComponent ]
})
.compileComponents();

fixture = TestBed.createComponent(DiffFieldComponent);
component = fixture.componentInstance;
component.value = {
newValue: undefined,
oldValue: undefined,
};
fixture.detectChanges();
});

it("should create", () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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, Input } from "@angular/core";

import type { DiffVal } from "src/app/utils/snapshot.diffing";

/**
* A "diff field" is a component used for displaying generically the difference
* - or lack thereof - between a simple property in the current and pending
* Snapshots of a CDN. It can't be used to express differences in non-primitive
* types.
*/
@Component({
selector: "tp-diff-field[value]",
styleUrls: ["./diff-field.component.scss"],
templateUrl: "./diff-field.component.html",
})
export class DiffFieldComponent {

@Input() public last = false;
@Input() public value!: DiffVal;
@Input() public name?: string;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 { EventEmitter } from "@angular/core";
import { of, type Subscription } from "rxjs";

import { Differences } from "./differences.class";

/**
* A class that's used simply to test the abstract class it extends.
*/
class TestingDifferencesSubclass extends Differences<null, null> {
public changesPending = new EventEmitter<number>();
public snapshots = of({current: {}, pending: {}});

public subscription: Subscription;

constructor() {
super();
this.subscription = this.snapshots.subscribe();
}

/**
* Angular lifecycle hook.
*/
public ngOnInit(): void {
// do nothing.
}
}

describe("'Differences' abstract class", () => {
let instance: TestingDifferencesSubclass;

beforeEach(() => {
instance = new TestingDifferencesSubclass();
});

it("gets the right string for changes pending", () => {
expect(instance.pendingChangesStr(0)).toBe("0 changes pending");
expect(instance.pendingChangesStr(1)).toBe("1 change pending");
expect(instance.pendingChangesStr(2)).toBe("2 changes pending");
expect(instance.pendingChangesStr(9001)).toBe("9001 changes pending");

const arr = [5];
expect(instance.pendingChangesStr(arr)).toBe("1 change pending");
arr.push(5);
expect(instance.pendingChangesStr(arr)).toBe("2 changes pending");
arr.splice(0, 2);
expect(instance.pendingChangesStr(arr)).toBe("0 changes pending");

const obj: Record<PropertyKey, unknown> = {};
expect(instance.pendingChangesStr(obj)).toBe("0 changes pending");
obj.test = 5;
expect(instance.pendingChangesStr(obj)).toBe("1 change pending");
obj.quest = 5;
expect(instance.pendingChangesStr(obj)).toBe("2 changes pending");
});

it("unsubscribes on component destruction", () => {
instance.ngOnDestroy();
expect(instance.subscription.closed).toBeTrue();
});
});
Loading