Migrate notebooks workspaces to generated clients (#876)

This commit is contained in:
Steve Faulkner 2021-06-09 13:08:10 -07:00 committed by GitHub
parent 8f6cac3d35
commit fc9f4c5583
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 49 additions and 583 deletions

View File

@ -5,7 +5,6 @@ import { Collection } from "../Contracts/ViewModels";
import DocumentId from "../Explorer/Tree/DocumentId";
import { updateUserContext } from "../UserContext";
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
const databaseId = "testDB";

View File

@ -18,7 +18,6 @@ import * as ViewModels from "../Contracts/ViewModels";
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
import { useSidePanel } from "../hooks/useSidePanel";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { ExplorerSettings } from "../Shared/ExplorerSettings";
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
@ -26,6 +25,12 @@ import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../UserContext";
import { getCollectionName, getDatabaseName, getUploadName } from "../Utils/APITypeUtils";
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
import {
get as getWorkspace,
listByDatabaseAccount,
listConnectionInfo,
start,
} from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { stringToBlob } from "../Utils/BlobUtils";
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
@ -123,7 +128,6 @@ export default class Explorer {
public isNotebookEnabled: ko.Observable<boolean>;
public isNotebooksEnabledForAccount: ko.Observable<boolean>;
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
public notebookWorkspaceManager: NotebookWorkspaceManager;
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
public isSynapseLinkUpdating: ko.Observable<boolean>;
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
@ -159,7 +163,6 @@ export default class Explorer {
? this.refreshDatabaseForResourceToken()
: this.refreshAllDatabases(true);
RouteHandler.getInstance().initHandler();
this.notebookWorkspaceManager = new NotebookWorkspaceManager();
await this._refreshNotebooksEnabledStateForAccount();
this.isNotebookEnabled(
userContext.authType !== AuthType.ResourceToken &&
@ -581,37 +584,19 @@ export default class Explorer {
this._isInitializingNotebooks = true;
await this.ensureNotebookWorkspaceRunning();
let connectionInfo: DataModels.NotebookWorkspaceConnectionInfo = {
authToken: undefined,
notebookServerEndpoint: undefined,
};
try {
connectionInfo = await this.notebookWorkspaceManager.getNotebookConnectionInfoAsync(
databaseAccount.id,
"default"
);
} catch (error) {
this._isInitializingNotebooks = false;
handleError(
error,
"initNotebooks/getNotebookConnectionInfoAsync",
`Failed to get notebook workspace connection info: ${getErrorMessage(error)}`
);
throw error;
} finally {
// Overwrite with feature flags
if (userContext.features.notebookServerUrl) {
connectionInfo.notebookServerEndpoint = userContext.features.notebookServerUrl;
}
const connectionInfo = await listConnectionInfo(
userContext.subscriptionId,
userContext.resourceGroup,
databaseAccount.name,
"default"
);
if (userContext.features.notebookServerToken) {
connectionInfo.authToken = userContext.features.notebookServerToken;
}
this.notebookServerInfo(connectionInfo);
this.notebookServerInfo.valueHasMutated();
this.refreshNotebookList();
}
this.notebookServerInfo({
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
});
this.notebookServerInfo.valueHasMutated();
this.refreshNotebookList();
this._isInitializingNotebooks = false;
}
@ -643,7 +628,11 @@ export default class Explorer {
}
try {
const workspaces = await this.notebookWorkspaceManager.getNotebookWorkspacesAsync(databaseAccount?.id);
const { value: workspaces } = await listByDatabaseAccount(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name
);
return workspaces && workspaces.length > 0 && workspaces.some((workspace) => workspace.name === "default");
} catch (error) {
Logger.logError(getErrorMessage(error), "Explorer/_containsDefaultNotebookWorkspace");
@ -658,8 +647,10 @@ export default class Explorer {
let clearMessage;
try {
const notebookWorkspace = await this.notebookWorkspaceManager.getNotebookWorkspaceAsync(
userContext.databaseAccount.id,
const notebookWorkspace = await getWorkspace(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
"default"
);
if (
@ -669,7 +660,7 @@ export default class Explorer {
notebookWorkspace.properties.status.toLowerCase() === "stopped"
) {
clearMessage = NotificationConsoleUtils.logConsoleProgress("Initializing notebook workspace");
await this.notebookWorkspaceManager.startNotebookWorkspaceAsync(userContext.databaseAccount.id, "default");
await start(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
}
} catch (error) {
handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace");

View File

@ -6,6 +6,7 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
import * as Logger from "../../Common/Logger";
import * as DataModels from "../../Contracts/DataModels";
import { userContext } from "../../UserContext";
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
export class NotebookContainerClient {
@ -130,16 +131,18 @@ export class NotebookContainerClient {
}
private async recreateNotebookWorkspaceAsync(): Promise<void> {
const explorer = window.dataExplorer;
const { databaseAccount } = userContext;
if (!databaseAccount?.id) {
throw new Error("DataExplorer not initialized");
}
const notebookWorkspaceManager = explorer.notebookWorkspaceManager;
try {
await notebookWorkspaceManager.deleteNotebookWorkspaceAsync(databaseAccount?.id, "default");
await notebookWorkspaceManager.createNotebookWorkspaceAsync(databaseAccount?.id, "default");
await destroy(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
await createOrUpdate(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
"default"
);
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
return Promise.reject(error);

View File

@ -7,6 +7,7 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import { userContext } from "../../../UserContext";
import { createOrUpdate } from "../../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import Explorer from "../../Explorer";
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
@ -56,8 +57,10 @@ export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> =
try {
setLoadingTrue();
await explorer.notebookWorkspaceManager.createNotebookWorkspaceAsync(
userContext.databaseAccount && userContext.databaseAccount.id,
await createOrUpdate(
userContext.subscriptionId,
userContext.resourceGroup,
userContext.databaseAccount.name,
"default"
);
explorer.isAccountReady.valueHasMutated(); // re-trigger init notebooks

View File

@ -1,98 +0,0 @@
import { ArmApiVersions } from "../Common/Constants";
import { IResourceProviderClient, IResourceProviderClientFactory } from "../ResourceProvider/IResourceProviderClient";
import * as Logger from "../Common/Logger";
import {
NotebookWorkspace,
NotebookWorkspaceConnectionInfo,
NotebookWorkspaceFeedResponse,
} from "../Contracts/DataModels";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
import { getErrorMessage } from "../Common/ErrorHandlingUtils";
export class NotebookWorkspaceManager {
private resourceProviderClientFactory: IResourceProviderClientFactory<any>;
constructor() {
this.resourceProviderClientFactory = new ResourceProviderClientFactory();
}
public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise<NotebookWorkspace[]> {
const uri = `${cosmosdbResourceId}/notebookWorkspaces`;
try {
const response = (await this.rpClient(uri).getAsync(
uri,
ArmApiVersions.documentDB
)) as NotebookWorkspaceFeedResponse;
return response && response.value;
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/getNotebookWorkspacesAsync");
throw error;
}
}
public async getNotebookWorkspaceAsync(
cosmosdbResourceId: string,
notebookWorkspaceId: string
): Promise<NotebookWorkspace> {
const uri = `${cosmosdbResourceId}/notebookWorkspaces/${notebookWorkspaceId}`;
try {
return (await this.rpClient(uri).getAsync(uri, ArmApiVersions.documentDB)) as NotebookWorkspace;
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/getNotebookWorkspaceAsync");
throw error;
}
}
public async createNotebookWorkspaceAsync(cosmosdbResourceId: string, notebookWorkspaceId: string): Promise<void> {
const uri = `${cosmosdbResourceId}/notebookWorkspaces/${notebookWorkspaceId}`;
try {
await this.rpClient(uri).putAsync(uri, ArmApiVersions.documentDB, { name: notebookWorkspaceId });
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/createNotebookWorkspaceAsync");
throw error;
}
}
public async deleteNotebookWorkspaceAsync(cosmosdbResourceId: string, notebookWorkspaceId: string): Promise<void> {
const uri = `${cosmosdbResourceId}/notebookWorkspaces/${notebookWorkspaceId}`;
try {
await this.rpClient(uri).deleteAsync(uri, ArmApiVersions.documentDB);
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/deleteNotebookWorkspaceAsync");
throw error;
}
}
public async getNotebookConnectionInfoAsync(
cosmosdbResourceId: string,
notebookWorkspaceId: string
): Promise<NotebookWorkspaceConnectionInfo> {
const uri = `${cosmosdbResourceId}/notebookWorkspaces/${notebookWorkspaceId}/listConnectionInfo`;
try {
return await this.rpClient<NotebookWorkspaceConnectionInfo>(uri).postAsync(
uri,
ArmApiVersions.documentDB,
undefined
);
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/getNotebookConnectionInfoAsync");
throw error;
}
}
public async startNotebookWorkspaceAsync(cosmosdbResourceId: string, notebookWorkspaceId: string): Promise<void> {
const uri = `${cosmosdbResourceId}/notebookWorkspaces/${notebookWorkspaceId}/start`;
try {
return await this.rpClient(uri).postAsync(uri, ArmApiVersions.documentDB, undefined, {
skipResourceValidation: true,
});
} catch (error) {
Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/startNotebookWorkspaceAsync");
throw error;
}
}
private rpClient<TResource>(uri: string): IResourceProviderClient<TResource> {
return this.resourceProviderClientFactory.getOrCreate(uri);
}
}

View File

@ -1,163 +0,0 @@
import { IResourceProviderClient, IResourceProviderClientFactory } from "./IResourceProviderClient";
describe("IResourceProviderClient", () => {
interface TestResource {
id: string;
}
const expectedResult: TestResource = { id: "a" };
const expectedReason: any = "error";
class SuccessClient implements IResourceProviderClient<TestResource> {
public deleteAsync(url: string, apiVersion?: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
resolve();
});
}
public getAsync(url: string, apiVersion?: string): Promise<TestResource> {
return new Promise<TestResource>((resolve, reject) => {
resolve(expectedResult);
});
}
public postAsync(url: string, apiVersion: string, body: TestResource): Promise<TestResource> {
return new Promise<TestResource>((resolve, reject) => {
resolve(expectedResult);
});
}
public putAsync(url: string, apiVersion: string, body: TestResource): Promise<TestResource> {
return new Promise<TestResource>((resolve, reject) => {
resolve(expectedResult);
});
}
public patchAsync(url: string, apiVersion: string, body: TestResource): Promise<TestResource> {
return new Promise<TestResource>((resolve, reject) => {
resolve(expectedResult);
});
}
}
class ErrorClient implements IResourceProviderClient<TestResource> {
public patchAsync(url: string, apiVersion: string, body: TestResource): Promise<TestResource> {
return new Promise<TestResource>((resolve, reject) => {
reject(expectedReason);
});
}
public putAsync(url: string, apiVersion: string, body: TestResource): Promise<TestResource> {
return new Promise<TestResource>((resolve, reject) => {
reject(expectedReason);
});
}
public postAsync(url: string, apiVersion: string, body: TestResource): Promise<TestResource> {
return new Promise<TestResource>((resolve, reject) => {
reject(expectedReason);
});
}
public getAsync(url: string, apiVersion?: string): Promise<TestResource> {
return new Promise<TestResource>((resolve, reject) => {
reject(expectedReason);
});
}
public deleteAsync(url: string, apiVersion?: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
reject(expectedReason);
});
}
}
class TestResourceProviderClientFactory implements IResourceProviderClientFactory<TestResource> {
public getOrCreate(url: string): IResourceProviderClient<TestResource> {
switch (url) {
case "reject":
return new ErrorClient();
case "fulfill":
default:
return new SuccessClient();
}
}
}
const factory = new TestResourceProviderClientFactory();
const fulfillClient = factory.getOrCreate("fulfill");
const rejectClient = factory.getOrCreate("reject");
const testApiVersion = "apiversion";
describe("deleteAsync", () => {
it("returns a fulfilled promise on success", async () => {
const result = await fulfillClient.deleteAsync("/foo", testApiVersion);
expect(result).toEqual(undefined);
});
it("returns a rejected promise with a reason on error", async () => {
let result: any;
try {
result = await rejectClient.deleteAsync("/foo", testApiVersion);
} catch (reason) {
result = reason;
}
expect(result).toEqual(expectedReason);
});
});
describe("getAsync", () => {
it("returns a fulfilled promise with a value on success", async () => {
const result = await fulfillClient.getAsync("/foo", testApiVersion);
expect(result).toEqual(expectedResult);
});
it("returns a rejected promise with a reason on error", async () => {
let result: any;
try {
result = await rejectClient.getAsync("/foo", testApiVersion);
} catch (reason) {
result = reason;
}
expect(result).toEqual(expectedReason);
});
});
describe("postAsync", () => {
it("returns a fulfilled promise with a value on success", async () => {
const result = await fulfillClient.postAsync("/foo", testApiVersion, {});
expect(result).toEqual(expectedResult);
});
it("returns a rejected promise with a reason on error", async () => {
let result: any;
try {
result = await rejectClient.postAsync("/foo", testApiVersion, {});
} catch (reason) {
result = reason;
}
expect(result).toEqual(expectedReason);
});
});
describe("putAsync", () => {
it("returns a fulfilled promise with a value on success", async () => {
const result = await fulfillClient.putAsync("/foo", testApiVersion, {});
expect(result).toEqual(expectedResult);
});
it("returns a rejected promise with a reason on error", async () => {
let result: any;
try {
result = await rejectClient.putAsync("/foo", testApiVersion, {});
} catch (reason) {
result = reason;
}
expect(result).toEqual(expectedReason);
});
});
});

View File

@ -1,30 +0,0 @@
export interface IResourceProviderClient<TResource> {
deleteAsync(url: string, apiVersion: string, requestOptions?: IResourceProviderRequestOptions): Promise<void>;
getAsync(
url: string,
apiVersion: string,
queryString?: string,
requestOptions?: IResourceProviderRequestOptions
): Promise<TResource | TResource[]>;
postAsync(url: string, apiVersion: string, body: any, requestOptions?: IResourceProviderRequestOptions): Promise<any>;
putAsync(
url: string,
apiVersion: string,
body: any,
requestOptions?: IResourceProviderRequestOptions
): Promise<TResource>;
patchAsync(
url: string,
apiVersion: string,
body: any,
requestOptions?: IResourceProviderRequestOptions
): Promise<TResource>;
}
export interface IResourceProviderRequestOptions {
skipResourceValidation: boolean;
}
export interface IResourceProviderClientFactory<TResult> {
getOrCreate(url: string): IResourceProviderClient<TResult>;
}

View File

@ -1,217 +0,0 @@
import * as ViewModels from "../Contracts/ViewModels";
import { HttpStatusCodes } from "../Common/Constants";
import { IResourceProviderClient, IResourceProviderRequestOptions } from "./IResourceProviderClient";
import { OperationStatus } from "../Contracts/DataModels";
import { TokenProviderFactory } from "../TokenProviders/TokenProviderFactory";
import * as UrlUtility from "../Common/UrlUtility";
export class ResourceProviderClient<T> implements IResourceProviderClient<T> {
private httpClient: HttpClient;
constructor(private armEndpoint: string) {
this.httpClient = new HttpClient();
}
public async getAsync(
url: string,
apiVersion: string,
queryString?: string,
requestOptions?: IResourceProviderRequestOptions
): Promise<T | T[]> {
let uri = `${this.armEndpoint}${url}?api-version=${apiVersion}`;
if (queryString) {
uri += `&${queryString}`;
}
return await this.httpClient.getAsync<T | T[]>(
uri,
Object.assign({}, { skipResourceValidation: false }, requestOptions)
);
}
public async postAsync(
url: string,
apiVersion: string,
body: any,
requestOptions?: IResourceProviderRequestOptions
): Promise<any> {
const fullUrl = UrlUtility.createUri(this.armEndpoint, url);
return await this.httpClient.postAsync(
`${fullUrl}?api-version=${apiVersion}`,
body,
Object.assign({}, { skipResourceValidation: false }, requestOptions)
);
}
public async putAsync(
url: string,
apiVersion: string,
body: any,
requestOptions?: IResourceProviderRequestOptions
): Promise<T> {
const fullUrl = UrlUtility.createUri(this.armEndpoint, url);
return await this.httpClient.putAsync<T>(
`${fullUrl}?api-version=${apiVersion}`,
body,
Object.assign({}, { skipResourceValidation: false }, requestOptions)
);
}
public async patchAsync(
url: string,
apiVersion: string,
body: any,
requestOptions?: IResourceProviderRequestOptions
): Promise<T> {
const fullUrl = UrlUtility.createUri(this.armEndpoint, url);
return await this.httpClient.patchAsync<T>(
`${fullUrl}?api-version=${apiVersion}`,
body,
Object.assign({}, { skipResourceValidation: false }, requestOptions)
);
}
public async deleteAsync(
url: string,
apiVersion: string,
requestOptions?: IResourceProviderRequestOptions
): Promise<void> {
const fullUrl = UrlUtility.createUri(this.armEndpoint, url);
return await this.httpClient.deleteAsync(
`${fullUrl}?api-version=${apiVersion}`,
Object.assign({}, { skipResourceValidation: true }, requestOptions)
);
}
}
class HttpClient {
private static readonly SUCCEEDED_STATUS = "Succeeded";
private static readonly FAILED_STATUS = "Failed";
private static readonly CANCELED_STATUS = "Canceled";
private static readonly AZURE_ASYNC_OPERATION_HEADER = "azure-asyncoperation";
private static readonly RETRY_AFTER_HEADER = "Retry-After";
private static readonly DEFAULT_THROTTLE_WAIT_TIME_SECONDS = 5;
private tokenProvider: ViewModels.TokenProvider;
constructor() {
this.tokenProvider = TokenProviderFactory.create();
}
public async getAsync<T>(url: string, requestOptions: IResourceProviderRequestOptions): Promise<T> {
const args: RequestInit = { method: "GET" };
const response = await this.httpRequest(new Request(url, args), requestOptions);
return (await response.json()) as T;
}
public async postAsync(url: string, body: any, requestOptions: IResourceProviderRequestOptions): Promise<any> {
body = typeof body !== "string" && body !== undefined ? JSON.stringify(body) : body;
const args: RequestInit = { method: "POST", headers: { "Content-Type": "application/json" }, body };
const response = await this.httpRequest(new Request(url, args), requestOptions);
return await response.json();
}
public async putAsync<T>(url: string, body: any, requestOptions: IResourceProviderRequestOptions): Promise<T> {
body = typeof body !== "string" && body !== undefined ? JSON.stringify(body) : body;
const args: RequestInit = { method: "PUT", headers: { "Content-Type": "application/json" }, body };
const response = await this.httpRequest(new Request(url, args), requestOptions);
return (await response.json()) as T;
}
public async patchAsync<T>(url: string, body: any, requestOptions: IResourceProviderRequestOptions): Promise<T> {
body = typeof body !== "string" && body !== undefined ? JSON.stringify(body) : body;
const args: RequestInit = { method: "PATCH", headers: { "Content-Type": "application/json" }, body };
const response = await this.httpRequest(new Request(url, args), requestOptions);
return (await response.json()) as T;
}
public async deleteAsync(url: string, requestOptions: IResourceProviderRequestOptions): Promise<void> {
const args: RequestInit = { method: "DELETE" };
await this.httpRequest(new Request(url, args), requestOptions);
return null;
}
public async httpRequest<T>(
request: RequestInfo,
requestOptions: IResourceProviderRequestOptions,
numRetries: number = 12
): Promise<Response> {
const authHeader = await this.tokenProvider.getAuthHeader();
authHeader &&
authHeader.forEach((value: string, header: string) => {
(request as Request).headers.append(header, value);
});
const response = await fetch(request);
if (response.status === HttpStatusCodes.Accepted) {
const operationStatusUrl: string =
response.headers && response.headers.get(HttpClient.AZURE_ASYNC_OPERATION_HEADER);
const resource = await this.pollOperationAndGetResultAsync<T>(request, operationStatusUrl, requestOptions);
return new Response(resource && JSON.stringify(resource));
}
if (response.status === HttpStatusCodes.TooManyRequests && numRetries > 0) {
// retry on throttles
let waitTimeInSeconds = response.headers.has(HttpClient.RETRY_AFTER_HEADER)
? parseInt(response.headers.get(HttpClient.RETRY_AFTER_HEADER))
: HttpClient.DEFAULT_THROTTLE_WAIT_TIME_SECONDS;
return new Promise<Response>((resolve: (value: Response) => void, reject: (error: any) => void) => {
setTimeout(async () => {
try {
const response = await this.httpRequest<T>(request, requestOptions, numRetries - 1);
resolve(response);
} catch (error) {
reject(error);
throw error;
}
}, waitTimeInSeconds * 1000);
});
}
if (response.ok) {
// RP sometimes returns HTTP 200 for async operations instead of HTTP 202 (e.g., on PATCH operations), so we need to check
const operationStatusUrl: string =
response.headers && response.headers.get(HttpClient.AZURE_ASYNC_OPERATION_HEADER);
if (operationStatusUrl) {
const resource = await this.pollOperationAndGetResultAsync<T>(request, operationStatusUrl, requestOptions);
return new Response(resource && JSON.stringify(resource));
}
return response;
}
return Promise.reject({ code: response.status, message: await response.text() });
}
private async pollOperationAndGetResultAsync<T>(
originalRequest: RequestInfo,
operationStatusUrl: string,
requestOptions: IResourceProviderRequestOptions
): Promise<T> {
const getOperationResult = async (resolve: (value: T) => void, reject: (error: any) => void) => {
const operationStatus: OperationStatus = await this.getAsync<OperationStatus>(operationStatusUrl, requestOptions);
if (!operationStatus) {
return reject("Could not retrieve operation status");
} else if (operationStatus.status === HttpClient.SUCCEEDED_STATUS) {
let result;
if (requestOptions?.skipResourceValidation === false) {
result = await this.getAsync<T>((originalRequest as Request).url, requestOptions);
}
return resolve(result);
} else if (
operationStatus.status === HttpClient.CANCELED_STATUS ||
operationStatus.status === HttpClient.FAILED_STATUS
) {
const errorMessage = operationStatus.error
? JSON.stringify(operationStatus.error)
: "Operation could not be completed";
return reject(errorMessage);
}
// TODO: add exponential backup and timeout threshold
setTimeout(getOperationResult, 1000, resolve, reject);
};
return new Promise<T>(getOperationResult);
}
}

View File

@ -1,22 +0,0 @@
import { configContext } from "../ConfigContext";
import { IResourceProviderClientFactory, IResourceProviderClient } from "./IResourceProviderClient";
import { ResourceProviderClient } from "./ResourceProviderClient";
export class ResourceProviderClientFactory implements IResourceProviderClientFactory<any> {
private armEndpoint: string;
private cachedClients: { [url: string]: IResourceProviderClient<any> } = {};
constructor() {
this.armEndpoint = configContext.ARM_ENDPOINT;
}
public getOrCreate(url: string): IResourceProviderClient<any> {
if (!url) {
throw new Error("No resource provider client factory params specified");
}
if (!this.cachedClients[url]) {
this.cachedClients[url] = new ResourceProviderClient(this.armEndpoint);
}
return this.cachedClients[url];
}
}

View File

@ -37,11 +37,10 @@ export async function createOrUpdate(
subscriptionId: string,
resourceGroupName: string,
accountName: string,
notebookWorkspaceName: string,
body: Types.NotebookWorkspaceCreateUpdateParameters
notebookWorkspaceName: string
): Promise<Types.NotebookWorkspace> {
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/notebookWorkspaces/${notebookWorkspaceName}`;
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body });
return armRequest({ host: configContext.ARM_ENDPOINT, path, method: "PUT", apiVersion, body: {} });
}
/* Deletes the notebook workspace for a Cosmos DB account. */

View File

@ -6,6 +6,8 @@
Generated from: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/specification/cosmos-db/resource-manager/Microsoft.DocumentDB/stable/2021-04-15/notebook.json
*/
import { ARMResourceProperties } from "../cosmos/types";
/* Parameters to create a notebook workspace resource */
export type NotebookWorkspaceCreateUpdateParameters = unknown;
@ -16,7 +18,7 @@ export interface NotebookWorkspaceListResult {
}
/* A notebook workspace resource */
export type NotebookWorkspace = unknown & {
export type NotebookWorkspace = ARMResourceProperties & {
/* Resource properties. */
properties?: NotebookWorkspaceProperties;
};

View File

@ -144,13 +144,13 @@ async function getOperationStatus(operationStatusUrl: string) {
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);
}
if (response.status === 200) {
return body;
}
throw new Error(`Operation Response: ${JSON.stringify(body)}. Retrying.`);
}

View File

@ -85,7 +85,6 @@
"./src/Platform/Hosted/extractFeatures.test.ts",
"./src/Platform/Hosted/extractFeatures.ts",
"./src/ReactDevTools.ts",
"./src/ResourceProvider/IResourceProviderClient.ts",
"./src/SelfServe/Example/SelfServeExample.types.ts",
"./src/SelfServe/SelfServeStyles.tsx",
"./src/SelfServe/SqlX/SqlxTypes.ts",