Skip to content

Commit

Permalink
TPv2 file download fix (#7593)
Browse files Browse the repository at this point in the history
* Add custom MIME extensions

* Fix incorrect call signature in Profile testing API service

* Switch Profile export to a link

* Remove now-unused service method for exporting Profiles
  • Loading branch information
ocket8888 committed Jul 17, 2023
1 parent 2df128c commit 14d0709
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 82 deletions.
23 changes: 2 additions & 21 deletions experimental/traffic-portal/src/app/api/profile.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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",
Expand Down Expand Up @@ -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");
Expand Down
31 changes: 12 additions & 19 deletions experimental/traffic-portal/src/app/api/profile.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<ResponseProfile> {
const id = typeof (profileId) === "number" ? profileId : profileId.id;
public async deleteProfile(profile: number | ResponseProfile): Promise<ResponseProfile> {
const id = typeof (profile) === "number" ? profile : profile.id;
return this.delete<ResponseProfile>(`profiles/${id}`).toPromise();
}

/**
* Exports profile
*
* @param profileId Id of the profile to export.
* @returns profile export object.
*/
public async exportProfile(profileId: number | ResponseProfile): Promise<ProfileExport>{
const id = typeof (profileId) === "number" ? profileId : profileId.id;
return this.http.get<ProfileExport>(`/api/${this.apiVersion}/profiles/${id}/export`).toPromise();
}

/**
* Import profile
*
Expand Down
45 changes: 13 additions & 32 deletions experimental/traffic-portal/src/app/api/testing/profile.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down Expand Up @@ -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<ResponseProfile>;
public async getProfiles(): Promise<Array<ResponseProfile>>;
Expand Down Expand Up @@ -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<ResponseProfile> {
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<ProfileExport> {
public async deleteProfile(profile: number | ResponseProfile): Promise<ResponseProfile> {
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];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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<string>("");
this.profiles = this.api.getProfiles();
this.navSvc.headerTitle.next("Profiles");
Expand Down Expand Up @@ -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;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 14d0709

Please sign in to comment.