mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2024-12-03 02:47:00 +00:00
ecdc41ada9
* exposed baselineValues * added getOnSaveNotification * disable UI when onSave is taking place * added optional polling * Added portal notifications * minor edits * added label for description * Added correlationids and polling of refresh * added label tooltip * removed ClassInfo decorator * Added dynamic decription * added info and warninf types for description * promise retry changes * compile errors fixed * merged sqlxEdits * undid sqlx changes * added completed notification * passed retryInterval in notif options * added polling on landing on the page * edits for error display * added link generation * addressed PR comments * modified test * fixed compilation error
157 lines
4.3 KiB
TypeScript
157 lines
4.3 KiB
TypeScript
/*
|
|
|
|
A general purpose fetch function for ARM resources. Not designed to be used directly
|
|
Instead, generate ARM clients that consume this function with stricter typing.
|
|
|
|
*/
|
|
|
|
import promiseRetry, { AbortError } from "p-retry";
|
|
import { configContext } from "../../ConfigContext";
|
|
import { userContext } from "../../UserContext";
|
|
|
|
interface ErrorResponse {
|
|
code: string;
|
|
message: string;
|
|
}
|
|
|
|
// ARM sometimes returns an error wrapped in a top level error object
|
|
// Example: 409 Conflict error when trying to delete a locked resource
|
|
interface WrappedErrorResponse {
|
|
error: ErrorResponse;
|
|
}
|
|
|
|
type ParsedErrorResponse = ErrorResponse | WrappedErrorResponse;
|
|
|
|
export class ARMError extends Error {
|
|
constructor(message: string) {
|
|
super(message);
|
|
// Set the prototype explicitly.
|
|
// https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work
|
|
Object.setPrototypeOf(this, ARMError.prototype);
|
|
}
|
|
|
|
public code?: string | number;
|
|
}
|
|
|
|
interface ARMQueryParams {
|
|
filter?: string;
|
|
metricNames?: string;
|
|
}
|
|
|
|
interface Options {
|
|
host: string;
|
|
path: string;
|
|
apiVersion: string;
|
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
|
|
body?: unknown;
|
|
queryParams?: ARMQueryParams;
|
|
}
|
|
|
|
export async function armRequestWithoutPolling<T>({
|
|
host,
|
|
path,
|
|
apiVersion,
|
|
method,
|
|
body: requestBody,
|
|
queryParams,
|
|
}: Options): Promise<{ result: T; operationStatusUrl: string }> {
|
|
const url = new URL(path, host);
|
|
url.searchParams.append("api-version", configContext.armAPIVersion || apiVersion);
|
|
if (queryParams) {
|
|
queryParams.filter && url.searchParams.append("$filter", queryParams.filter);
|
|
queryParams.metricNames && url.searchParams.append("metricnames", queryParams.metricNames);
|
|
}
|
|
|
|
if (!userContext.authorizationToken) {
|
|
throw new Error("No authority token provided");
|
|
}
|
|
|
|
const response = await window.fetch(url.href, {
|
|
method,
|
|
headers: {
|
|
Authorization: userContext.authorizationToken,
|
|
},
|
|
body: requestBody ? JSON.stringify(requestBody) : undefined,
|
|
});
|
|
if (!response.ok) {
|
|
let error: ARMError;
|
|
try {
|
|
const errorResponse = (await response.json()) as ParsedErrorResponse;
|
|
if ("error" in errorResponse) {
|
|
error = new ARMError(errorResponse.error.message);
|
|
error.code = errorResponse.error.code;
|
|
} else {
|
|
error = new ARMError(errorResponse.message);
|
|
error.code = errorResponse.code;
|
|
}
|
|
} catch (error) {
|
|
throw new Error(await response.text());
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
|
|
const operationStatusUrl = (response.headers && response.headers.get("location")) || "";
|
|
const responseBody = (await response.json()) as T;
|
|
return { result: responseBody, operationStatusUrl: operationStatusUrl };
|
|
}
|
|
|
|
// TODO: This is very similar to what is happening in ResourceProviderClient.ts. Should probably merge them.
|
|
export async function armRequest<T>({
|
|
host,
|
|
path,
|
|
apiVersion,
|
|
method,
|
|
body: requestBody,
|
|
queryParams,
|
|
}: Options): Promise<T> {
|
|
const armRequestResult = await armRequestWithoutPolling<T>({
|
|
host,
|
|
path,
|
|
apiVersion,
|
|
method,
|
|
body: requestBody,
|
|
queryParams,
|
|
});
|
|
const operationStatusUrl = armRequestResult.operationStatusUrl;
|
|
if (operationStatusUrl) {
|
|
return await promiseRetry(() => getOperationStatus(operationStatusUrl));
|
|
}
|
|
return armRequestResult.result;
|
|
}
|
|
|
|
async function getOperationStatus(operationStatusUrl: string) {
|
|
if (!userContext.authorizationToken) {
|
|
throw new Error("No authority token provided");
|
|
}
|
|
|
|
const response = await window.fetch(operationStatusUrl, {
|
|
headers: {
|
|
Authorization: userContext.authorizationToken,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorResponse = (await response.json()) as ErrorResponse;
|
|
const error = new Error(errorResponse.message) as ARMError;
|
|
error.code = errorResponse.code;
|
|
throw new AbortError(error);
|
|
}
|
|
|
|
if (response.status === 204) {
|
|
return;
|
|
}
|
|
|
|
const body = await response.json();
|
|
const status = body.status;
|
|
if (!status && response.status === 200) {
|
|
return body;
|
|
}
|
|
if (status === "Canceled" || status === "Failed") {
|
|
const errorMessage = body.error ? JSON.stringify(body.error) : "Operation could not be completed";
|
|
const error = new Error(errorMessage);
|
|
throw new AbortError(error);
|
|
}
|
|
throw new Error(`Operation Response: ${JSON.stringify(body)}. Retrying.`);
|
|
}
|