Poll on Location header for operation status (#309)

This commit is contained in:
victor-meng 2020-11-02 16:59:08 -08:00 committed by GitHub
parent e6ca1d25c9
commit 3e782527d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 13 additions and 33 deletions

View File

@ -21,13 +21,12 @@ describe("ARM request", () => {
it("should poll for async operations", async () => { it("should poll for async operations", async () => {
const headers = new Headers(); const headers = new Headers();
headers.set("azure-asyncoperation", "https://foo.com/operationStatus"); headers.set("location", "https://foo.com/operationStatus");
window.fetch = jest.fn().mockResolvedValue({ window.fetch = jest.fn().mockResolvedValue({
ok: true, ok: true,
headers, headers,
json: async () => { status: 200,
return { status: "Succeeded" }; json: async () => ({})
}
}); });
await armRequest({ apiVersion: "2001-01-01", host: "https://foo.com", path: "foo", method: "GET" }); await armRequest({ apiVersion: "2001-01-01", host: "https://foo.com", path: "foo", method: "GET" });
expect(window.fetch).toHaveBeenCalledTimes(2); expect(window.fetch).toHaveBeenCalledTimes(2);
@ -35,10 +34,11 @@ describe("ARM request", () => {
it("should throw for failed async operations", async () => { it("should throw for failed async operations", async () => {
const headers = new Headers(); const headers = new Headers();
headers.set("azure-asyncoperation", "https://foo.com/operationStatus"); headers.set("location", "https://foo.com/operationStatus");
window.fetch = jest.fn().mockResolvedValue({ window.fetch = jest.fn().mockResolvedValue({
ok: true, ok: true,
headers, headers,
status: 200,
json: async () => { json: async () => {
return { status: "Failed" }; return { status: "Failed" };
} }

View File

@ -70,55 +70,35 @@ export async function armRequest<T>({ host, path, apiVersion, method, body: requ
throw error; throw error;
} }
const operationStatusUrl = response.headers && response.headers.get("azure-asyncoperation"); const operationStatusUrl = response.headers && response.headers.get("location");
if (operationStatusUrl) { if (operationStatusUrl) {
await promiseRetry(() => getOperationStatus(operationStatusUrl)); return await promiseRetry(() => getOperationStatus(operationStatusUrl));
// TODO: ARM is supposed to return a resourceLocation property, but it does not https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#target-resource-location
// When Cosmos RP adds resourceLocation, we should use it instead
// For now manually execute a GET if the operation was a mutation and not a deletion
if (method === "POST" || method === "PATCH" || method === "PUT") {
return armRequest({
host,
path,
apiVersion,
method: "GET"
});
}
} }
const responseBody = (await response.json()) as T; const responseBody = (await response.json()) as T;
return responseBody; return responseBody;
} }
const SUCCEEDED = "Succeeded" as const;
const FAILED = "Failed" as const;
const CANCELED = "Canceled" as const;
type Status = typeof SUCCEEDED | typeof FAILED | typeof CANCELED;
interface OperationResponse {
status: Status;
error: unknown;
}
async function getOperationStatus(operationStatusUrl: string) { async function getOperationStatus(operationStatusUrl: string) {
const response = await window.fetch(operationStatusUrl, { const response = await window.fetch(operationStatusUrl, {
headers: { headers: {
Authorization: userContext.authorizationToken Authorization: userContext.authorizationToken
} }
}); });
if (!response.ok) { if (!response.ok) {
const errorResponse = (await response.json()) as ErrorResponse; const errorResponse = (await response.json()) as ErrorResponse;
const error = new Error(errorResponse.message) as ARMError; const error = new Error(errorResponse.message) as ARMError;
error.code = errorResponse.code; error.code = errorResponse.code;
throw new AbortError(error); throw new AbortError(error);
} }
const body = (await response.json()) as OperationResponse;
const body = await response.json();
const status = body.status; const status = body.status;
if (status === SUCCEEDED) { if (!status && response.status === 200) {
return; return body;
} }
if (status === CANCELED || status === FAILED) { if (status === "Canceled" || status === "Failed") {
const errorMessage = body.error ? JSON.stringify(body.error) : "Operation could not be completed"; const errorMessage = body.error ? JSON.stringify(body.error) : "Operation could not be completed";
const error = new Error(errorMessage); const error = new Error(errorMessage);
throw new AbortError(error); throw new AbortError(error);