mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-01-31 13:16:43 +00:00
Add UX for error Information for Phoenix workspace connection (#1234)
* Add UX for error Information * update text messages
This commit is contained in:
parent
8b22027cb6
commit
06f6df83ad
@ -450,6 +450,24 @@ export interface IResponse<T> {
|
|||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IValidationError {
|
||||||
|
message: string;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMaxAllocationTimeExceeded extends IValidationError {
|
||||||
|
earliestAllocationTimestamp: string;
|
||||||
|
maxAllocationTimePerDayPerUserInMinutes: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMaxDbAccountsPerUserExceeded extends IValidationError {
|
||||||
|
maxSimultaneousConnectionsPerUser: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMaxUsersPerDbAccountExceeded extends IValidationError {
|
||||||
|
maxSimultaneousUsersPerDbAccount: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IPhoenixConnectionInfoResult {
|
export interface IPhoenixConnectionInfoResult {
|
||||||
readonly notebookAuthToken?: string;
|
readonly notebookAuthToken?: string;
|
||||||
readonly notebookServerUrl?: string;
|
readonly notebookServerUrl?: string;
|
||||||
@ -531,3 +549,12 @@ export interface ContainerConnectionInfo {
|
|||||||
status: ConnectionStatusType;
|
status: ConnectionStatusType;
|
||||||
//need to add ram and rom info
|
//need to add ram and rom info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PhoenixErrorType {
|
||||||
|
MaxAllocationTimeExceeded = "MaxAllocationTimeExceeded",
|
||||||
|
MaxDbAccountsPerUserExceeded = "MaxDbAccountsPerUserExceeded",
|
||||||
|
MaxUsersPerDbAccountExceeded = "MaxUsersPerDbAccountExceeded",
|
||||||
|
AllocationValidationResult = "AllocationValidationResult",
|
||||||
|
RegionNotServicable = "RegionNotServicable",
|
||||||
|
SubscriptionNotAllowed = "SubscriptionNotAllowed",
|
||||||
|
}
|
||||||
|
@ -362,12 +362,13 @@ export default class Explorer {
|
|||||||
status: ConnectionStatusType.Connecting,
|
status: ConnectionStatusType.Connecting,
|
||||||
};
|
};
|
||||||
useNotebook.getState().setConnectionInfo(connectionStatus);
|
useNotebook.getState().setConnectionInfo(connectionStatus);
|
||||||
|
let connectionInfo;
|
||||||
try {
|
try {
|
||||||
TelemetryProcessor.traceStart(Action.PhoenixConnection, {
|
TelemetryProcessor.traceStart(Action.PhoenixConnection, {
|
||||||
dataExplorerArea: Areas.Notebook,
|
dataExplorerArea: Areas.Notebook,
|
||||||
});
|
});
|
||||||
useNotebook.getState().setIsAllocating(true);
|
useNotebook.getState().setIsAllocating(true);
|
||||||
const connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
|
connectionInfo = await this.phoenixClient.allocateContainer(provisionData);
|
||||||
if (connectionInfo.status !== HttpStatusCodes.OK) {
|
if (connectionInfo.status !== HttpStatusCodes.OK) {
|
||||||
throw new Error(`Received status code: ${connectionInfo?.status}`);
|
throw new Error(`Received status code: ${connectionInfo?.status}`);
|
||||||
}
|
}
|
||||||
@ -386,12 +387,16 @@ export default class Explorer {
|
|||||||
});
|
});
|
||||||
connectionStatus.status = ConnectionStatusType.Failed;
|
connectionStatus.status = ConnectionStatusType.Failed;
|
||||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||||
useDialog
|
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
|
||||||
.getState()
|
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
|
||||||
.showOkModalDialog(
|
} else {
|
||||||
"Connection Failed",
|
useDialog
|
||||||
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
|
.getState()
|
||||||
);
|
.showOkModalDialog(
|
||||||
|
"Connection Failed",
|
||||||
|
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
|
||||||
|
);
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
useNotebook.getState().setIsAllocating(false);
|
useNotebook.getState().setIsAllocating(false);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Notebook container related stuff
|
* Notebook container related stuff
|
||||||
*/
|
*/
|
||||||
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
import promiseRetry, { AbortError } from "p-retry";
|
||||||
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
import { PhoenixClient } from "Phoenix/PhoenixClient";
|
||||||
import * as Constants from "../../Common/Constants";
|
import * as Constants from "../../Common/Constants";
|
||||||
@ -159,6 +160,16 @@ export class NotebookContainerClient {
|
|||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
|
Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
|
||||||
|
if (error?.status === HttpStatusCodes.Forbidden && error.message) {
|
||||||
|
useDialog.getState().showOkModalDialog("Connection Failed", `${error.message}`);
|
||||||
|
} else {
|
||||||
|
useDialog
|
||||||
|
.getState()
|
||||||
|
.showOkModalDialog(
|
||||||
|
"Connection Failed",
|
||||||
|
"We are unable to connect to the temporary workspace. Please try again in a few minutes. If the error persists, file a support ticket."
|
||||||
|
);
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useDialog } from "Explorer/Controls/Dialog";
|
||||||
import promiseRetry, { AbortError } from "p-retry";
|
import promiseRetry, { AbortError } from "p-retry";
|
||||||
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
import { Action } from "Shared/Telemetry/TelemetryConstants";
|
||||||
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
|
import { allowedJunoOrigins, validateEndpoint } from "Utils/EndpointValidation";
|
||||||
@ -16,9 +17,14 @@ import {
|
|||||||
ContainerConnectionInfo,
|
ContainerConnectionInfo,
|
||||||
ContainerInfo,
|
ContainerInfo,
|
||||||
IContainerData,
|
IContainerData,
|
||||||
|
IMaxAllocationTimeExceeded,
|
||||||
|
IMaxDbAccountsPerUserExceeded,
|
||||||
|
IMaxUsersPerDbAccountExceeded,
|
||||||
IPhoenixConnectionInfoResult,
|
IPhoenixConnectionInfoResult,
|
||||||
IProvisionData,
|
IProvisionData,
|
||||||
IResponse,
|
IResponse,
|
||||||
|
IValidationError,
|
||||||
|
PhoenixErrorType,
|
||||||
} from "../Contracts/DataModels";
|
} from "../Contracts/DataModels";
|
||||||
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
import { useNotebook } from "../Explorer/Notebook/useNotebook";
|
||||||
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor";
|
||||||
@ -45,23 +51,25 @@ export class PhoenixClient {
|
|||||||
provisionData: IProvisionData,
|
provisionData: IProvisionData,
|
||||||
operation: string
|
operation: string
|
||||||
): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
): Promise<IResponse<IPhoenixConnectionInfoResult>> {
|
||||||
|
let response;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections`, {
|
response = await fetch(`${this.getPhoenixControlPlanePathPrefix()}/containerconnections`, {
|
||||||
method: operation === "allocate" ? "POST" : "PATCH",
|
method: operation === "allocate" ? "POST" : "PATCH",
|
||||||
headers: PhoenixClient.getHeaders(),
|
headers: PhoenixClient.getHeaders(),
|
||||||
body: JSON.stringify(provisionData),
|
body: JSON.stringify(provisionData),
|
||||||
});
|
});
|
||||||
|
const responseJson = await response?.json();
|
||||||
let data: IPhoenixConnectionInfoResult;
|
if (response.status === HttpStatusCodes.Forbidden) {
|
||||||
if (response.status === HttpStatusCodes.OK) {
|
throw new Error(this.ConvertToForbiddenErrorString(responseJson));
|
||||||
data = await response.json();
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
data,
|
data: responseJson,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
if (response.status === HttpStatusCodes.Forbidden) {
|
||||||
|
error.status = HttpStatusCodes.Forbidden;
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,6 +116,18 @@ export class PhoenixClient {
|
|||||||
});
|
});
|
||||||
useNotebook.getState().resetContainerConnection(connectionStatus);
|
useNotebook.getState().resetContainerConnection(connectionStatus);
|
||||||
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed);
|
||||||
|
useDialog
|
||||||
|
.getState()
|
||||||
|
.showOkModalDialog(
|
||||||
|
"Disconnected",
|
||||||
|
"Disconnected from temporary workspace. Please click on connect button to connect to temporary workspace."
|
||||||
|
);
|
||||||
|
throw new AbortError(response.statusText);
|
||||||
|
} else if (response?.status === HttpStatusCodes.Forbidden) {
|
||||||
|
const validationMessage = this.ConvertToForbiddenErrorString(await response.json());
|
||||||
|
if (validationMessage) {
|
||||||
|
useDialog.getState().showOkModalDialog("Connection Failed", `${validationMessage}`);
|
||||||
|
}
|
||||||
throw new AbortError(response.statusText);
|
throw new AbortError(response.statusText);
|
||||||
}
|
}
|
||||||
throw new Error(response.statusText);
|
throw new Error(response.statusText);
|
||||||
@ -177,4 +197,48 @@ export class PhoenixClient {
|
|||||||
[HttpHeaders.contentType]: "application/json",
|
[HttpHeaders.contentType]: "application/json",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ConvertToForbiddenErrorString(jsonData: IValidationError): string {
|
||||||
|
const errInfo = jsonData;
|
||||||
|
switch (errInfo?.type) {
|
||||||
|
case PhoenixErrorType.MaxAllocationTimeExceeded: {
|
||||||
|
const maxAllocationTimeExceeded = errInfo as IMaxAllocationTimeExceeded;
|
||||||
|
const allocateAfterTimestamp = new Date(maxAllocationTimeExceeded?.earliestAllocationTimestamp);
|
||||||
|
allocateAfterTimestamp.setDate(allocateAfterTimestamp.getDate() + 1);
|
||||||
|
return (
|
||||||
|
`${errInfo.message}` +
|
||||||
|
" Max allocation time for a day to a user is " +
|
||||||
|
`${maxAllocationTimeExceeded.maxAllocationTimePerDayPerUserInMinutes}` +
|
||||||
|
". Please try again after " +
|
||||||
|
`${allocateAfterTimestamp.toLocaleString()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case PhoenixErrorType.MaxDbAccountsPerUserExceeded: {
|
||||||
|
const maxDbAccountsPerUserExceeded = errInfo as IMaxDbAccountsPerUserExceeded;
|
||||||
|
return (
|
||||||
|
`${errInfo.message}` +
|
||||||
|
" Max simultaneous connections allowed per user is " +
|
||||||
|
`${maxDbAccountsPerUserExceeded.maxSimultaneousConnectionsPerUser}` +
|
||||||
|
"."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case PhoenixErrorType.MaxUsersPerDbAccountExceeded: {
|
||||||
|
const maxUsersPerDbAccountExceeded = errInfo as IMaxUsersPerDbAccountExceeded;
|
||||||
|
return (
|
||||||
|
`${errInfo.message}` +
|
||||||
|
" Max simultaneous users allowed per DbAccount is " +
|
||||||
|
`${maxUsersPerDbAccountExceeded.maxSimultaneousUsersPerDbAccount}` +
|
||||||
|
"."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case PhoenixErrorType.AllocationValidationResult:
|
||||||
|
case PhoenixErrorType.RegionNotServicable:
|
||||||
|
case PhoenixErrorType.SubscriptionNotAllowed: {
|
||||||
|
return `${errInfo.message}`;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user