diff --git a/experimental/traffic-portal/src/app/api/profile.service.spec.ts b/experimental/traffic-portal/src/app/api/profile.service.spec.ts index 63ce8b22f3..304f00a297 100644 --- a/experimental/traffic-portal/src/app/api/profile.service.spec.ts +++ b/experimental/traffic-portal/src/app/api/profile.service.spec.ts @@ -14,7 +14,7 @@ */ import { HttpClientTestingModule, HttpTestingController } from "@angular/common/http/testing"; import { TestBed } from "@angular/core/testing"; -import { ProfileExport, ProfileType , ResponseProfile} from "trafficops-types"; +import { ProfileType, type ResponseProfile} from "trafficops-types"; import { ProfileService } from "./profile.service"; @@ -41,16 +41,6 @@ describe("ProfileService", () => { type: ProfileType.ATS_PROFILE, } }; - const exportProfile: ProfileExport = { - alerts: null, - parameters:[], - profile: { - cdn: "ALL", - description: "test", - name: "TRAFFIC_ANALYTICS", - type: ProfileType.TS_PROFILE - } - }; const parameter = { configFile: "cfg.txt", @@ -136,16 +126,7 @@ describe("ProfileService", () => { await expectAsync(responseP).toBeResolvedTo(profile); }); - it("sends request for Export object by Profile ID", async () => { - const response = service.exportProfile(profile.id); - const req = httpTestingController.expectOne(r => r.url === `/api/${service.apiVersion}/profiles/${profile.id}/export`); - expect(req.request.method).toBe("GET"); - expect(req.request.params.keys().length).toBe(0); - req.flush(exportProfile); - await expectAsync(response).toBeResolvedTo(exportProfile); - }); - - it("send request for import profile", async () => { + it("sends requests to import Profiles", async () => { const responseP = service.importProfile(importProfile); const req = httpTestingController.expectOne(`/api/${service.apiVersion}/profiles/import`); expect(req.request.method).toBe("POST"); diff --git a/experimental/traffic-portal/src/app/api/profile.service.ts b/experimental/traffic-portal/src/app/api/profile.service.ts index b6920a8cfa..623592a380 100644 --- a/experimental/traffic-portal/src/app/api/profile.service.ts +++ b/experimental/traffic-portal/src/app/api/profile.service.ts @@ -14,9 +14,13 @@ import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; -import { - ProfileExport, ProfileImport, ProfileImportResponse, RequestParameter, - RequestProfile, ResponseParameter, ResponseProfile +import type { + ProfileImport, + ProfileImportResponse, + RequestParameter, + RequestProfile, + ResponseParameter, + ResponseProfile, } from "trafficops-types"; import { APIService } from "./base-api.service"; @@ -117,27 +121,16 @@ export class ProfileService extends APIService { } /** - * Deletes an existing profile. + * Deletes an existing Profile. * - * @param profileId Id of the profile to delete. - * @returns The success message. + * @param profile The Profile to delete, or just its ID. + * @returns The deleted Profile. */ - public async deleteProfile(profileId: number | ResponseProfile): Promise { - const id = typeof (profileId) === "number" ? profileId : profileId.id; + public async deleteProfile(profile: number | ResponseProfile): Promise { + const id = typeof (profile) === "number" ? profile : profile.id; return this.delete(`profiles/${id}`).toPromise(); } - /** - * Exports profile - * - * @param profileId Id of the profile to export. - * @returns profile export object. - */ - public async exportProfile(profileId: number | ResponseProfile): Promise{ - const id = typeof (profileId) === "number" ? profileId : profileId.id; - return this.http.get(`/api/${this.apiVersion}/profiles/${id}/export`).toPromise(); - } - /** * Import profile * diff --git a/experimental/traffic-portal/src/app/api/testing/profile.service.ts b/experimental/traffic-portal/src/app/api/testing/profile.service.ts index 4a0440850d..686273d3ff 100644 --- a/experimental/traffic-portal/src/app/api/testing/profile.service.ts +++ b/experimental/traffic-portal/src/app/api/testing/profile.service.ts @@ -14,8 +14,13 @@ import { Injectable } from "@angular/core"; import { - ProfileExport, ProfileImport, ProfileImportResponse, ProfileType, - RequestProfile, ResponseProfile, RequestParameter, ResponseParameter + type ProfileImport, + type ProfileImportResponse, + ProfileType, + type RequestProfile, + type ResponseProfile, + type RequestParameter, + type ResponseParameter, } from "trafficops-types"; /** @@ -138,16 +143,6 @@ export class ProfileService { type: ProfileType.ATS_PROFILE } ]; - private readonly profileExport: ProfileExport = { - alerts: null, - parameters:[], - profile: { - cdn: "ALL", - description: "test", - name: "TRAFFIC_ANALYTICS", - type: ProfileType.TS_PROFILE - }, - }; public async getProfiles(idOrName: number | string): Promise; public async getProfiles(): Promise>; @@ -220,32 +215,18 @@ export class ProfileService { } /** - * Deletes an existing profile. - * - * @param id Id of the profile to delete. - * @returns The success message. - */ - public async deleteProfile(id: number | ResponseProfile): Promise { - const index = this.profiles.findIndex(t => t.id === id); - if (index === -1) { - throw new Error(`no such profile: ${id}`); - } - return this.profiles.splice(index, 1)[0]; - } - - /** - * Export Profile object from the API. + * Deletes an existing Profile. * - * @param profile Specify unique identifier (number) of a specific Profile to retrieve the export object. - * @returns The requested Profile as attachment. + * @param profile The Profile to delete, or just its ID. + * @returns The deleted Profile. */ - public async exportProfile(profile: number | ResponseProfile): Promise { + public async deleteProfile(profile: number | ResponseProfile): Promise { const id = typeof(profile) === "number" ? profile : profile.id; const index = this.profiles.findIndex(t => t.id === id); if (index === -1) { - throw new Error(`no such Profile: ${id}`); + throw new Error(`no such Profile #${id}`); } - return this.profileExport; + return this.profiles.splice(index, 1)[0]; } /** diff --git a/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.spec.ts b/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.spec.ts index ed4bfa266a..a20968a7e1 100644 --- a/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.spec.ts +++ b/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.spec.ts @@ -127,4 +127,34 @@ describe("ProfileTableComponent", () => { await asyncExpectation; })); + + it("constructs profile export context menu links", async () => { + const item = component.contextMenuItems.find(c => c.name === "Export Profile"); + if (!item) { + return fail("missing 'Export Profile' context menu item"); + } + if (isAction(item)) { + return fail("expected a link, not an action"); + } + expect(item.newTab).toBeTrue(); + expect(item.disabled).toBeUndefined(); + expect(item.fragment).toBeUndefined(); + expect(item.queryParams).toBeUndefined(); + + if (typeof(item.href) !== "function") { + return fail(`expected a functional href property, got: ${typeof(item.href)}`); + } + + const api = TestBed.inject(ProfileService); + const profile = await api.createProfile({ + cdn: 1, + description: "blah", + name: "test", + routingDisabled: false, + type: ProfileType.ATS_PROFILE + }); + + expect(item.href(profile)).toBe(`/api/${api.apiVersion}/profiles/${profile.id}/export`); + await api.deleteProfile(profile); + }); }); diff --git a/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.ts b/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.ts index 419a71ce93..515da13db5 100644 --- a/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.ts +++ b/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.ts @@ -22,7 +22,6 @@ import { ProfileImport, ResponseProfile } from "trafficops-types"; import { ProfileService } from "src/app/api"; import { CurrentUserService } from "src/app/shared/current-user/current-user.service"; import { DecisionDialogComponent } from "src/app/shared/dialogs/decision-dialog/decision-dialog.component"; -import { FileUtilsService } from "src/app/shared/file-utils.service"; import { ContextMenuActionEvent, ContextMenuItem, @@ -101,9 +100,9 @@ export class ProfileTableComponent implements OnInit { name: "Clone Profile", }, { - action: "export-profile", - multiRow: false, + href: (profile: ResponseProfile): string => `/api/${this.api.apiVersion}/profiles/${profile.id}/export`, name: "Export Profile", + newTab: true }, { action: "manage-parameters", @@ -144,7 +143,7 @@ export class ProfileTableComponent implements OnInit { private readonly navSvc: NavigationService, private readonly dialog: MatDialog, public readonly auth: CurrentUserService, - private readonly fileUtil: FileUtilsService) { + ) { this.fuzzySubject = new BehaviorSubject(""); this.profiles = this.api.getProfiles(); this.navSvc.headerTitle.next("Profiles"); @@ -194,10 +193,6 @@ export class ProfileTableComponent implements OnInit { } }); break; - case "export-profile": - const response = await this.api.exportProfile(data.id); - this.fileUtil.download(response,data.name); - break; } } diff --git a/experimental/traffic-portal/src/app/shared/file-utils.service.ts b/experimental/traffic-portal/src/app/shared/file-utils.service.ts index 3242f8c93f..d4f37a829e 100644 --- a/experimental/traffic-portal/src/app/shared/file-utils.service.ts +++ b/experimental/traffic-portal/src/app/shared/file-utils.service.ts @@ -25,11 +25,11 @@ import { isArrayBufferView } from "../utils"; export class FileUtilsService { /** The default MIME-Type for string data downloads. */ - public static readonly TEXT_CONTENT_TYPE = "text/plain;charset=UTF-8"; + public static readonly TEXT_CONTENT_TYPE = "text/plain+x-traffic-ops;charset=UTF-8"; /** The default MIME-Type for raw binary data downloads. */ public static readonly BINARY_DATA_CONTENT_TYPE = "application/octet-stream"; /** The default MIME-Type for arbitrary object downloads. */ - public static readonly JSON_DATA_CONTENT_TYPE = "application/json"; + public static readonly JSON_DATA_CONTENT_TYPE = "application/json+x-traffic-ops"; /** * The file name that will be used for downloads, if one is not provided.