Add UX for error Information for Phoenix workspace connection (#1234)

* Add UX for error Information

* update text messages
This commit is contained in:
Karthik chakravarthy 2022-03-30 08:59:37 -04:00 committed by GitHub
parent 8b22027cb6
commit 06f6df83ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 14 deletions

View File

@ -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",
}

View File

@ -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);

View File

@ -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;
} }
} }

View File

@ -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;
}
}
}
} }