mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-02-16 17:25:58 +00:00
Migrate notebooks workspaces to generated clients (#876)
This commit is contained in:
parent
8f6cac3d35
commit
fc9f4c5583
@ -5,7 +5,6 @@ import { Collection } from "../Contracts/ViewModels";
|
|||||||
import DocumentId from "../Explorer/Tree/DocumentId";
|
import DocumentId from "../Explorer/Tree/DocumentId";
|
||||||
import { updateUserContext } from "../UserContext";
|
import { updateUserContext } from "../UserContext";
|
||||||
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
import { deleteDocument, getEndpoint, queryDocuments, readDocument, updateDocument } from "./MongoProxyClient";
|
||||||
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
|
|
||||||
|
|
||||||
const databaseId = "testDB";
|
const databaseId = "testDB";
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ import * as ViewModels from "../Contracts/ViewModels";
|
|||||||
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
import { GitHubOAuthService } from "../GitHub/GitHubOAuthService";
|
||||||
import { useSidePanel } from "../hooks/useSidePanel";
|
import { useSidePanel } from "../hooks/useSidePanel";
|
||||||
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
|
||||||
import { NotebookWorkspaceManager } from "../NotebookWorkspaceManager/NotebookWorkspaceManager";
|
|
||||||
import { RouteHandler } from "../RouteHandlers/RouteHandler";
|
import { RouteHandler } from "../RouteHandlers/RouteHandler";
|
||||||
import { ExplorerSettings } from "../Shared/ExplorerSettings";
|
import { ExplorerSettings } from "../Shared/ExplorerSettings";
|
||||||
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants";
|
||||||
@ -26,6 +25,12 @@ import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
|||||||
import { userContext } from "../UserContext";
|
import { userContext } from "../UserContext";
|
||||||
import { getCollectionName, getDatabaseName, getUploadName } from "../Utils/APITypeUtils";
|
import { getCollectionName, getDatabaseName, getUploadName } from "../Utils/APITypeUtils";
|
||||||
import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts";
|
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 { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
|
||||||
import { stringToBlob } from "../Utils/BlobUtils";
|
import { stringToBlob } from "../Utils/BlobUtils";
|
||||||
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
import { isCapabilityEnabled } from "../Utils/CapabilityUtils";
|
||||||
@ -123,7 +128,6 @@ export default class Explorer {
|
|||||||
public isNotebookEnabled: ko.Observable<boolean>;
|
public isNotebookEnabled: ko.Observable<boolean>;
|
||||||
public isNotebooksEnabledForAccount: ko.Observable<boolean>;
|
public isNotebooksEnabledForAccount: ko.Observable<boolean>;
|
||||||
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
|
public notebookServerInfo: ko.Observable<DataModels.NotebookWorkspaceConnectionInfo>;
|
||||||
public notebookWorkspaceManager: NotebookWorkspaceManager;
|
|
||||||
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
|
public sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
|
||||||
public isSynapseLinkUpdating: ko.Observable<boolean>;
|
public isSynapseLinkUpdating: ko.Observable<boolean>;
|
||||||
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
|
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
|
||||||
@ -159,7 +163,6 @@ export default class Explorer {
|
|||||||
? this.refreshDatabaseForResourceToken()
|
? this.refreshDatabaseForResourceToken()
|
||||||
: this.refreshAllDatabases(true);
|
: this.refreshAllDatabases(true);
|
||||||
RouteHandler.getInstance().initHandler();
|
RouteHandler.getInstance().initHandler();
|
||||||
this.notebookWorkspaceManager = new NotebookWorkspaceManager();
|
|
||||||
await this._refreshNotebooksEnabledStateForAccount();
|
await this._refreshNotebooksEnabledStateForAccount();
|
||||||
this.isNotebookEnabled(
|
this.isNotebookEnabled(
|
||||||
userContext.authType !== AuthType.ResourceToken &&
|
userContext.authType !== AuthType.ResourceToken &&
|
||||||
@ -581,37 +584,19 @@ export default class Explorer {
|
|||||||
this._isInitializingNotebooks = true;
|
this._isInitializingNotebooks = true;
|
||||||
|
|
||||||
await this.ensureNotebookWorkspaceRunning();
|
await this.ensureNotebookWorkspaceRunning();
|
||||||
let connectionInfo: DataModels.NotebookWorkspaceConnectionInfo = {
|
const connectionInfo = await listConnectionInfo(
|
||||||
authToken: undefined,
|
userContext.subscriptionId,
|
||||||
notebookServerEndpoint: undefined,
|
userContext.resourceGroup,
|
||||||
};
|
databaseAccount.name,
|
||||||
try {
|
|
||||||
connectionInfo = await this.notebookWorkspaceManager.getNotebookConnectionInfoAsync(
|
|
||||||
databaseAccount.id,
|
|
||||||
"default"
|
"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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userContext.features.notebookServerToken) {
|
this.notebookServerInfo({
|
||||||
connectionInfo.authToken = userContext.features.notebookServerToken;
|
notebookServerEndpoint: userContext.features.notebookServerUrl || connectionInfo.notebookServerEndpoint,
|
||||||
}
|
authToken: userContext.features.notebookServerToken || connectionInfo.authToken,
|
||||||
|
});
|
||||||
this.notebookServerInfo(connectionInfo);
|
|
||||||
this.notebookServerInfo.valueHasMutated();
|
this.notebookServerInfo.valueHasMutated();
|
||||||
this.refreshNotebookList();
|
this.refreshNotebookList();
|
||||||
}
|
|
||||||
|
|
||||||
this._isInitializingNotebooks = false;
|
this._isInitializingNotebooks = false;
|
||||||
}
|
}
|
||||||
@ -643,7 +628,11 @@ export default class Explorer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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");
|
return workspaces && workspaces.length > 0 && workspaces.some((workspace) => workspace.name === "default");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "Explorer/_containsDefaultNotebookWorkspace");
|
Logger.logError(getErrorMessage(error), "Explorer/_containsDefaultNotebookWorkspace");
|
||||||
@ -658,8 +647,10 @@ export default class Explorer {
|
|||||||
|
|
||||||
let clearMessage;
|
let clearMessage;
|
||||||
try {
|
try {
|
||||||
const notebookWorkspace = await this.notebookWorkspaceManager.getNotebookWorkspaceAsync(
|
const notebookWorkspace = await getWorkspace(
|
||||||
userContext.databaseAccount.id,
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
"default"
|
"default"
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
@ -669,7 +660,7 @@ export default class Explorer {
|
|||||||
notebookWorkspace.properties.status.toLowerCase() === "stopped"
|
notebookWorkspace.properties.status.toLowerCase() === "stopped"
|
||||||
) {
|
) {
|
||||||
clearMessage = NotificationConsoleUtils.logConsoleProgress("Initializing notebook workspace");
|
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) {
|
} catch (error) {
|
||||||
handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace");
|
handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace");
|
||||||
|
@ -6,6 +6,7 @@ import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
|
|||||||
import * as Logger from "../../Common/Logger";
|
import * as Logger from "../../Common/Logger";
|
||||||
import * as DataModels from "../../Contracts/DataModels";
|
import * as DataModels from "../../Contracts/DataModels";
|
||||||
import { userContext } from "../../UserContext";
|
import { userContext } from "../../UserContext";
|
||||||
|
import { createOrUpdate, destroy } from "../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
|
||||||
|
|
||||||
export class NotebookContainerClient {
|
export class NotebookContainerClient {
|
||||||
@ -130,16 +131,18 @@ export class NotebookContainerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async recreateNotebookWorkspaceAsync(): Promise<void> {
|
private async recreateNotebookWorkspaceAsync(): Promise<void> {
|
||||||
const explorer = window.dataExplorer;
|
|
||||||
const { databaseAccount } = userContext;
|
const { databaseAccount } = userContext;
|
||||||
if (!databaseAccount?.id) {
|
if (!databaseAccount?.id) {
|
||||||
throw new Error("DataExplorer not initialized");
|
throw new Error("DataExplorer not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const notebookWorkspaceManager = explorer.notebookWorkspaceManager;
|
|
||||||
try {
|
try {
|
||||||
await notebookWorkspaceManager.deleteNotebookWorkspaceAsync(databaseAccount?.id, "default");
|
await destroy(userContext.subscriptionId, userContext.resourceGroup, userContext.databaseAccount.name, "default");
|
||||||
await notebookWorkspaceManager.createNotebookWorkspaceAsync(databaseAccount?.id, "default");
|
await createOrUpdate(
|
||||||
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
|
"default"
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
|
Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
@ -7,6 +7,7 @@ import { useSidePanel } from "../../../hooks/useSidePanel";
|
|||||||
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
|
||||||
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
||||||
import { userContext } from "../../../UserContext";
|
import { userContext } from "../../../UserContext";
|
||||||
|
import { createOrUpdate } from "../../../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces";
|
||||||
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
|
||||||
import Explorer from "../../Explorer";
|
import Explorer from "../../Explorer";
|
||||||
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
import { PanelInfoErrorComponent } from "../PanelInfoErrorComponent";
|
||||||
@ -56,8 +57,10 @@ export const SetupNoteBooksPanel: FunctionComponent<SetupNoteBooksPanelProps> =
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setLoadingTrue();
|
setLoadingTrue();
|
||||||
await explorer.notebookWorkspaceManager.createNotebookWorkspaceAsync(
|
await createOrUpdate(
|
||||||
userContext.databaseAccount && userContext.databaseAccount.id,
|
userContext.subscriptionId,
|
||||||
|
userContext.resourceGroup,
|
||||||
|
userContext.databaseAccount.name,
|
||||||
"default"
|
"default"
|
||||||
);
|
);
|
||||||
explorer.isAccountReady.valueHasMutated(); // re-trigger init notebooks
|
explorer.isAccountReady.valueHasMutated(); // re-trigger init notebooks
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -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>;
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,11 +37,10 @@ export async function createOrUpdate(
|
|||||||
subscriptionId: string,
|
subscriptionId: string,
|
||||||
resourceGroupName: string,
|
resourceGroupName: string,
|
||||||
accountName: string,
|
accountName: string,
|
||||||
notebookWorkspaceName: string,
|
notebookWorkspaceName: string
|
||||||
body: Types.NotebookWorkspaceCreateUpdateParameters
|
|
||||||
): Promise<Types.NotebookWorkspace> {
|
): Promise<Types.NotebookWorkspace> {
|
||||||
const path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/${accountName}/notebookWorkspaces/${notebookWorkspaceName}`;
|
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. */
|
/* Deletes the notebook workspace for a Cosmos DB account. */
|
||||||
|
@ -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
|
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 */
|
/* Parameters to create a notebook workspace resource */
|
||||||
export type NotebookWorkspaceCreateUpdateParameters = unknown;
|
export type NotebookWorkspaceCreateUpdateParameters = unknown;
|
||||||
|
|
||||||
@ -16,7 +18,7 @@ export interface NotebookWorkspaceListResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* A notebook workspace resource */
|
/* A notebook workspace resource */
|
||||||
export type NotebookWorkspace = unknown & {
|
export type NotebookWorkspace = ARMResourceProperties & {
|
||||||
/* Resource properties. */
|
/* Resource properties. */
|
||||||
properties?: NotebookWorkspaceProperties;
|
properties?: NotebookWorkspaceProperties;
|
||||||
};
|
};
|
||||||
|
@ -144,13 +144,13 @@ async function getOperationStatus(operationStatusUrl: string) {
|
|||||||
|
|
||||||
const body = await response.json();
|
const body = await response.json();
|
||||||
const status = body.status;
|
const status = body.status;
|
||||||
if (!status && response.status === 200) {
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
if (response.status === 200) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
throw new Error(`Operation Response: ${JSON.stringify(body)}. Retrying.`);
|
throw new Error(`Operation Response: ${JSON.stringify(body)}. Retrying.`);
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,6 @@
|
|||||||
"./src/Platform/Hosted/extractFeatures.test.ts",
|
"./src/Platform/Hosted/extractFeatures.test.ts",
|
||||||
"./src/Platform/Hosted/extractFeatures.ts",
|
"./src/Platform/Hosted/extractFeatures.ts",
|
||||||
"./src/ReactDevTools.ts",
|
"./src/ReactDevTools.ts",
|
||||||
"./src/ResourceProvider/IResourceProviderClient.ts",
|
|
||||||
"./src/SelfServe/Example/SelfServeExample.types.ts",
|
"./src/SelfServe/Example/SelfServeExample.types.ts",
|
||||||
"./src/SelfServe/SelfServeStyles.tsx",
|
"./src/SelfServe/SelfServeStyles.tsx",
|
||||||
"./src/SelfServe/SqlX/SqlxTypes.ts",
|
"./src/SelfServe/SqlX/SqlxTypes.ts",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user