diff --git a/src/Common/MongoProxyClient.test.ts b/src/Common/MongoProxyClient.test.ts index 8f7e033f9..3a5a02365 100644 --- a/src/Common/MongoProxyClient.test.ts +++ b/src/Common/MongoProxyClient.test.ts @@ -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"; diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 1686267f2..52535b3c5 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -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; public isNotebooksEnabledForAccount: ko.Observable; public notebookServerInfo: ko.Observable; - public notebookWorkspaceManager: NotebookWorkspaceManager; public sparkClusterConnectionInfo: ko.Observable; public isSynapseLinkUpdating: ko.Observable; public memoryUsageInfo: ko.Observable; @@ -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"); diff --git a/src/Explorer/Notebook/NotebookContainerClient.ts b/src/Explorer/Notebook/NotebookContainerClient.ts index 8b961450f..3764360ef 100644 --- a/src/Explorer/Notebook/NotebookContainerClient.ts +++ b/src/Explorer/Notebook/NotebookContainerClient.ts @@ -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 { - 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); diff --git a/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx b/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx index d465bb7d4..75a17d89a 100644 --- a/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx +++ b/src/Explorer/Panes/SetupNotebooksPanel/SetupNotebooksPanel.tsx @@ -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 = 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 diff --git a/src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts b/src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts deleted file mode 100644 index 171cb5cf4..000000000 --- a/src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts +++ /dev/null @@ -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; - - constructor() { - this.resourceProviderClientFactory = new ResourceProviderClientFactory(); - } - - public async getNotebookWorkspacesAsync(cosmosdbResourceId: string): Promise { - 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 { - 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 { - 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 { - 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 { - const uri = `${cosmosdbResourceId}/notebookWorkspaces/${notebookWorkspaceId}/listConnectionInfo`; - try { - return await this.rpClient(uri).postAsync( - uri, - ArmApiVersions.documentDB, - undefined - ); - } catch (error) { - Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/getNotebookConnectionInfoAsync"); - throw error; - } - } - - public async startNotebookWorkspaceAsync(cosmosdbResourceId: string, notebookWorkspaceId: string): Promise { - 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(uri: string): IResourceProviderClient { - return this.resourceProviderClientFactory.getOrCreate(uri); - } -} diff --git a/src/ResourceProvider/IResourceProviderClient.test.ts b/src/ResourceProvider/IResourceProviderClient.test.ts deleted file mode 100644 index b775564c1..000000000 --- a/src/ResourceProvider/IResourceProviderClient.test.ts +++ /dev/null @@ -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 { - public deleteAsync(url: string, apiVersion?: string): Promise { - return new Promise((resolve, reject) => { - resolve(); - }); - } - - public getAsync(url: string, apiVersion?: string): Promise { - return new Promise((resolve, reject) => { - resolve(expectedResult); - }); - } - - public postAsync(url: string, apiVersion: string, body: TestResource): Promise { - return new Promise((resolve, reject) => { - resolve(expectedResult); - }); - } - - public putAsync(url: string, apiVersion: string, body: TestResource): Promise { - return new Promise((resolve, reject) => { - resolve(expectedResult); - }); - } - - public patchAsync(url: string, apiVersion: string, body: TestResource): Promise { - return new Promise((resolve, reject) => { - resolve(expectedResult); - }); - } - } - - class ErrorClient implements IResourceProviderClient { - public patchAsync(url: string, apiVersion: string, body: TestResource): Promise { - return new Promise((resolve, reject) => { - reject(expectedReason); - }); - } - - public putAsync(url: string, apiVersion: string, body: TestResource): Promise { - return new Promise((resolve, reject) => { - reject(expectedReason); - }); - } - - public postAsync(url: string, apiVersion: string, body: TestResource): Promise { - return new Promise((resolve, reject) => { - reject(expectedReason); - }); - } - - public getAsync(url: string, apiVersion?: string): Promise { - return new Promise((resolve, reject) => { - reject(expectedReason); - }); - } - - public deleteAsync(url: string, apiVersion?: string): Promise { - return new Promise((resolve, reject) => { - reject(expectedReason); - }); - } - } - - class TestResourceProviderClientFactory implements IResourceProviderClientFactory { - public getOrCreate(url: string): IResourceProviderClient { - 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); - }); - }); -}); diff --git a/src/ResourceProvider/IResourceProviderClient.ts b/src/ResourceProvider/IResourceProviderClient.ts deleted file mode 100644 index b9205be4b..000000000 --- a/src/ResourceProvider/IResourceProviderClient.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface IResourceProviderClient { - deleteAsync(url: string, apiVersion: string, requestOptions?: IResourceProviderRequestOptions): Promise; - getAsync( - url: string, - apiVersion: string, - queryString?: string, - requestOptions?: IResourceProviderRequestOptions - ): Promise; - postAsync(url: string, apiVersion: string, body: any, requestOptions?: IResourceProviderRequestOptions): Promise; - putAsync( - url: string, - apiVersion: string, - body: any, - requestOptions?: IResourceProviderRequestOptions - ): Promise; - patchAsync( - url: string, - apiVersion: string, - body: any, - requestOptions?: IResourceProviderRequestOptions - ): Promise; -} - -export interface IResourceProviderRequestOptions { - skipResourceValidation: boolean; -} - -export interface IResourceProviderClientFactory { - getOrCreate(url: string): IResourceProviderClient; -} diff --git a/src/ResourceProvider/ResourceProviderClient.ts b/src/ResourceProvider/ResourceProviderClient.ts deleted file mode 100644 index 10f3c8fae..000000000 --- a/src/ResourceProvider/ResourceProviderClient.ts +++ /dev/null @@ -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 implements IResourceProviderClient { - private httpClient: HttpClient; - - constructor(private armEndpoint: string) { - this.httpClient = new HttpClient(); - } - - public async getAsync( - url: string, - apiVersion: string, - queryString?: string, - requestOptions?: IResourceProviderRequestOptions - ): Promise { - let uri = `${this.armEndpoint}${url}?api-version=${apiVersion}`; - if (queryString) { - uri += `&${queryString}`; - } - return await this.httpClient.getAsync( - uri, - Object.assign({}, { skipResourceValidation: false }, requestOptions) - ); - } - - public async postAsync( - url: string, - apiVersion: string, - body: any, - requestOptions?: IResourceProviderRequestOptions - ): Promise { - 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 { - const fullUrl = UrlUtility.createUri(this.armEndpoint, url); - return await this.httpClient.putAsync( - `${fullUrl}?api-version=${apiVersion}`, - body, - Object.assign({}, { skipResourceValidation: false }, requestOptions) - ); - } - - public async patchAsync( - url: string, - apiVersion: string, - body: any, - requestOptions?: IResourceProviderRequestOptions - ): Promise { - const fullUrl = UrlUtility.createUri(this.armEndpoint, url); - return await this.httpClient.patchAsync( - `${fullUrl}?api-version=${apiVersion}`, - body, - Object.assign({}, { skipResourceValidation: false }, requestOptions) - ); - } - - public async deleteAsync( - url: string, - apiVersion: string, - requestOptions?: IResourceProviderRequestOptions - ): Promise { - 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(url: string, requestOptions: IResourceProviderRequestOptions): Promise { - 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 { - 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(url: string, body: any, requestOptions: IResourceProviderRequestOptions): Promise { - 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(url: string, body: any, requestOptions: IResourceProviderRequestOptions): Promise { - 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 { - const args: RequestInit = { method: "DELETE" }; - await this.httpRequest(new Request(url, args), requestOptions); - return null; - } - - public async httpRequest( - request: RequestInfo, - requestOptions: IResourceProviderRequestOptions, - numRetries: number = 12 - ): Promise { - 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(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((resolve: (value: Response) => void, reject: (error: any) => void) => { - setTimeout(async () => { - try { - const response = await this.httpRequest(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(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( - originalRequest: RequestInfo, - operationStatusUrl: string, - requestOptions: IResourceProviderRequestOptions - ): Promise { - const getOperationResult = async (resolve: (value: T) => void, reject: (error: any) => void) => { - const operationStatus: OperationStatus = await this.getAsync(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((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(getOperationResult); - } -} diff --git a/src/ResourceProvider/ResourceProviderClientFactory.ts b/src/ResourceProvider/ResourceProviderClientFactory.ts deleted file mode 100644 index 4ecc128d1..000000000 --- a/src/ResourceProvider/ResourceProviderClientFactory.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { configContext } from "../ConfigContext"; -import { IResourceProviderClientFactory, IResourceProviderClient } from "./IResourceProviderClient"; -import { ResourceProviderClient } from "./ResourceProviderClient"; - -export class ResourceProviderClientFactory implements IResourceProviderClientFactory { - private armEndpoint: string; - private cachedClients: { [url: string]: IResourceProviderClient } = {}; - - constructor() { - this.armEndpoint = configContext.ARM_ENDPOINT; - } - - public getOrCreate(url: string): IResourceProviderClient { - 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]; - } -} diff --git a/src/Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces.ts b/src/Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces.ts index c08e79855..a46b9aab0 100644 --- a/src/Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces.ts +++ b/src/Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces.ts @@ -37,11 +37,10 @@ export async function createOrUpdate( subscriptionId: string, resourceGroupName: string, accountName: string, - notebookWorkspaceName: string, - body: Types.NotebookWorkspaceCreateUpdateParameters + notebookWorkspaceName: string ): Promise { 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. */ diff --git a/src/Utils/arm/generatedClients/cosmosNotebooks/types.ts b/src/Utils/arm/generatedClients/cosmosNotebooks/types.ts index ecc745703..9704f399a 100644 --- a/src/Utils/arm/generatedClients/cosmosNotebooks/types.ts +++ b/src/Utils/arm/generatedClients/cosmosNotebooks/types.ts @@ -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; }; diff --git a/src/Utils/arm/request.ts b/src/Utils/arm/request.ts index 2d733cac4..e593633ac 100644 --- a/src/Utils/arm/request.ts +++ b/src/Utils/arm/request.ts @@ -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.`); } diff --git a/tsconfig.strict.json b/tsconfig.strict.json index 3693d539c..b10fa0ac6 100644 --- a/tsconfig.strict.json +++ b/tsconfig.strict.json @@ -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",