From 5741802c25cf3efc903eae299443e9c47724b068 Mon Sep 17 00:00:00 2001
From: victor-meng <56978073+victor-meng@users.noreply.github.com>
Date: Fri, 30 Oct 2020 15:09:24 -0700
Subject: [PATCH 01/13] refactor error handling part 1 (#307)
- created `getErrorMessage` function which takes in an error string or any type of error object and returns the correct error message
- replaced `error.message` with `getErrorMessage` since `error` could be a string in some cases
- merged sendNotificationForError.ts with ErrorHandlingUtils.ts
- some minor refactoring
In part 2, I will make the following changes:
- Make `Logger.logError` function take an error message string instead of an error object. This will reduce some redundancy where the `getErrorMessage` function is being called twice (the error object passed by the caller is already an error message).
- Update every `TelemetryProcessor.traceFailure` call to make sure we pass in an error message instead of an error object since we stringify the data we send.
---
src/Common/CosmosClient.ts | 3 +-
src/Common/ErrorHandlingUtils.ts | 55 ++++++++++++++++---
src/Common/ErrorParserUtility.ts | 14 -----
src/Common/Logger.ts | 12 ++--
src/Common/QueriesClient.ts | 37 +++----------
src/Common/dataAccess/createTrigger.ts | 9 +--
src/Common/dataAccess/readOffers.ts | 4 +-
.../dataAccess/sendNotificationForError.ts | 20 -------
.../updateOfferThroughputBeyondLimit.ts | 12 +++-
.../Controls/Settings/SettingsComponent.tsx | 3 +-
src/Explorer/Explorer.ts | 33 ++++++-----
.../GraphExplorerComponent/GraphExplorer.tsx | 5 +-
.../GraphExplorerComponent/GremlinClient.ts | 12 ++--
src/Explorer/Panes/AddCollectionPane.ts | 8 +--
src/Explorer/Panes/BrowseQueriesPane.ts | 7 ++-
src/Explorer/Panes/RenewAdHocAccessPane.ts | 3 +-
src/Explorer/Panes/SaveQueryPane.ts | 16 +++---
src/Explorer/Panes/SetupNotebooksPane.ts | 3 +-
src/Explorer/Tabs/DocumentsTab.ts | 9 ++-
src/Explorer/Tabs/SettingsTab.ts | 3 +-
src/Explorer/Tree/Collection.ts | 16 +++---
src/Explorer/Tree/Database.ts | 8 ++-
src/Explorer/Tree/StoredProcedure.ts | 3 +-
src/GitHub/GitHubContentProvider.ts | 3 +-
src/HostedExplorer.ts | 15 +++--
src/Platform/Hosted/Main.ts | 3 +-
src/workers/upload/index.ts | 3 +-
tsconfig.strict.json | 2 -
28 files changed, 162 insertions(+), 159 deletions(-)
delete mode 100644 src/Common/dataAccess/sendNotificationForError.ts
diff --git a/src/Common/CosmosClient.ts b/src/Common/CosmosClient.ts
index de71c11b1..f54e8a073 100644
--- a/src/Common/CosmosClient.ts
+++ b/src/Common/CosmosClient.ts
@@ -1,6 +1,7 @@
import * as Cosmos from "@azure/cosmos";
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
import { configContext, Platform } from "../ConfigContext";
+import { getErrorMessage } from "./ErrorHandlingUtils";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { EmulatorMasterKey, HttpHeaders } from "./Constants";
import { userContext } from "../UserContext";
@@ -69,7 +70,7 @@ export async function getTokenFromAuthService(verb: string, resourceType: string
const result = JSON.parse(await response.json());
return result;
} catch (error) {
- logConsoleError(`Failed to get authorization headers for ${resourceType}: ${error.message}`);
+ logConsoleError(`Failed to get authorization headers for ${resourceType}: ${getErrorMessage(error)}`);
return Promise.reject(error);
}
}
diff --git a/src/Common/ErrorHandlingUtils.ts b/src/Common/ErrorHandlingUtils.ts
index f100e0b07..e5acc6b49 100644
--- a/src/Common/ErrorHandlingUtils.ts
+++ b/src/Common/ErrorHandlingUtils.ts
@@ -1,11 +1,52 @@
-import { CosmosError, sendNotificationForError } from "./dataAccess/sendNotificationForError";
+import { HttpStatusCodes } from "./Constants";
+import { MessageTypes } from "../Contracts/ExplorerContracts";
+import { SubscriptionType } from "../Contracts/ViewModels";
import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { logError } from "./Logger";
-import { replaceKnownError } from "./ErrorParserUtility";
+import { sendMessage } from "./MessageHandler";
-export const handleError = (error: CosmosError, consoleErrorPrefix: string, area: string): void => {
- const sanitizedErrorMsg = replaceKnownError(error.message);
- logConsoleError(`${consoleErrorPrefix}:\n ${sanitizedErrorMsg}`);
- logError(sanitizedErrorMsg, area, error.code);
- sendNotificationForError(error);
+export interface CosmosError {
+ code: number;
+ message?: string;
+}
+
+export const handleError = (error: string | CosmosError, consoleErrorPrefix: string, area: string): void => {
+ const errorMessage = getErrorMessage(error);
+ const errorCode = typeof error === "string" ? undefined : error.code;
+ // logs error to data explorer console
+ logConsoleError(`${consoleErrorPrefix}:\n ${errorMessage}`);
+ // logs error to both app insight and kusto
+ logError(errorMessage, area, errorCode);
+ // checks for errors caused by firewall and sends them to portal to handle
+ sendNotificationForError(errorMessage, errorCode);
+};
+
+export const getErrorMessage = (error: string | CosmosError | Error): string => {
+ const errorMessage = typeof error === "string" ? error : error.message;
+ return replaceKnownError(errorMessage);
+};
+
+const sendNotificationForError = (errorMessage: string, errorCode: number): void => {
+ if (errorCode === HttpStatusCodes.Forbidden) {
+ if (errorMessage?.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
+ return;
+ }
+ sendMessage({
+ type: MessageTypes.ForbiddenError,
+ reason: errorMessage
+ });
+ }
+};
+
+const replaceKnownError = (errorMessage: string): string => {
+ if (
+ window.dataExplorer?.subscriptionType() === SubscriptionType.Internal &&
+ errorMessage.indexOf("SharedOffer is Disabled for your account") >= 0
+ ) {
+ return "Database throughput is not supported for internal subscriptions.";
+ } else if (errorMessage.indexOf("Partition key paths must contain only valid") >= 0) {
+ return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
+ }
+
+ return errorMessage;
};
diff --git a/src/Common/ErrorParserUtility.ts b/src/Common/ErrorParserUtility.ts
index 95d1ace34..968737f5c 100644
--- a/src/Common/ErrorParserUtility.ts
+++ b/src/Common/ErrorParserUtility.ts
@@ -1,18 +1,4 @@
import * as DataModels from "../Contracts/DataModels";
-import * as ViewModels from "../Contracts/ViewModels";
-
-export function replaceKnownError(err: string): string {
- if (
- window.dataExplorer.subscriptionType() === ViewModels.SubscriptionType.Internal &&
- err.indexOf("SharedOffer is Disabled for your account") >= 0
- ) {
- return "Database throughput is not supported for internal subscriptions.";
- } else if (err.indexOf("Partition key paths must contain only valid") >= 0) {
- return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
- }
-
- return err;
-}
export function parse(err: any): DataModels.ErrorDataModel[] {
try {
diff --git a/src/Common/Logger.ts b/src/Common/Logger.ts
index 73d50a15e..39b3196c5 100644
--- a/src/Common/Logger.ts
+++ b/src/Common/Logger.ts
@@ -1,3 +1,4 @@
+import { CosmosError, getErrorMessage } from "./ErrorHandlingUtils";
import { sendMessage } from "./MessageHandler";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { appInsights } from "../Shared/appInsights";
@@ -21,14 +22,9 @@ export function logWarning(message: string, area: string, code?: number): void {
return _logEntry(entry);
}
-export function logError(message: string | Error, area: string, code?: number): void {
- let logMessage: string;
- if (typeof message === "string") {
- logMessage = message;
- } else {
- logMessage = JSON.stringify(message, Object.getOwnPropertyNames(message));
- }
- const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, logMessage, area, code);
+export function logError(error: string | CosmosError | Error, area: string, code?: number): void {
+ const errorMessage: string = getErrorMessage(error);
+ const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, errorMessage, area, code);
return _logEntry(entry);
}
diff --git a/src/Common/QueriesClient.ts b/src/Common/QueriesClient.ts
index c54d167fc..7a3106890 100644
--- a/src/Common/QueriesClient.ts
+++ b/src/Common/QueriesClient.ts
@@ -12,6 +12,7 @@ import { BackendDefaults, HttpStatusCodes, SavedQueries } from "./Constants";
import { userContext } from "../UserContext";
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
import { createCollection } from "./dataAccess/createCollection";
+import { handleError } from "./ErrorHandlingUtils";
import * as ErrorParserUtility from "./ErrorParserUtility";
import * as Logger from "./Logger";
@@ -53,13 +54,8 @@ export class QueriesClient {
return Promise.resolve(collection);
},
(error: any) => {
- const stringifiedError: string = error.message;
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Failed to set up account for saving queries: ${stringifiedError}`
- );
- Logger.logError(stringifiedError, "setupQueriesCollection");
- return Promise.reject(stringifiedError);
+ handleError(error, "Failed to set up account for saving queries", "setupQueriesCollection");
+ return Promise.reject(error);
}
)
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
@@ -163,25 +159,15 @@ export class QueriesClient {
return Promise.resolve(queries);
},
(error: any) => {
- const stringifiedError: string = error.message;
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Failed to fetch saved queries: ${stringifiedError}`
- );
- Logger.logError(stringifiedError, "getSavedQueries");
- return Promise.reject(stringifiedError);
+ handleError(error, "Failed to fetch saved queries", "getSavedQueries");
+ return Promise.reject(error);
}
);
},
(error: any) => {
// should never get into this state but we handle this regardless
- const stringifiedError: string = error.message;
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Failed to fetch saved queries: ${stringifiedError}`
- );
- Logger.logError(stringifiedError, "getSavedQueries");
- return Promise.reject(stringifiedError);
+ handleError(error, "Failed to fetch saved queries", "getSavedQueries");
+ return Promise.reject(error);
}
)
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
@@ -232,13 +218,8 @@ export class QueriesClient {
return Promise.resolve();
},
(error: any) => {
- const stringifiedError: string = error.message;
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Failed to delete query ${query.queryName}: ${stringifiedError}`
- );
- Logger.logError(stringifiedError, "deleteQuery");
- return Promise.reject(stringifiedError);
+ handleError(error, `Failed to delete query ${query.queryName}`, "deleteQuery");
+ return Promise.reject(error);
}
)
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
diff --git a/src/Common/dataAccess/createTrigger.ts b/src/Common/dataAccess/createTrigger.ts
index 341dd46e7..179d0103e 100644
--- a/src/Common/dataAccess/createTrigger.ts
+++ b/src/Common/dataAccess/createTrigger.ts
@@ -7,9 +7,8 @@ import {
} from "../../Utils/arm/generatedClients/2020-04-01/types";
import { client } from "../CosmosClient";
import { createUpdateSqlTrigger, getSqlTrigger } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
-import { logConsoleError, logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
-import { logError } from "../Logger";
-import { sendNotificationForError } from "./sendNotificationForError";
+import { handleError } from "../ErrorHandlingUtils";
+import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { userContext } from "../../UserContext";
export async function createTrigger(
@@ -66,9 +65,7 @@ export async function createTrigger(
.scripts.triggers.create(trigger);
return response.resource;
} catch (error) {
- logConsoleError(`Error while creating trigger ${trigger.id}:\n ${error.message}`);
- logError(error.message, "CreateTrigger", error.code);
- sendNotificationForError(error);
+ handleError(error, `Error while creating trigger ${trigger.id}`, "CreateTrigger");
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/readOffers.ts b/src/Common/dataAccess/readOffers.ts
index c3717c5b3..8fc743c9c 100644
--- a/src/Common/dataAccess/readOffers.ts
+++ b/src/Common/dataAccess/readOffers.ts
@@ -1,7 +1,7 @@
import { Offer } from "../../Contracts/DataModels";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
import { client } from "../CosmosClient";
-import { handleError } from "../ErrorHandlingUtils";
+import { handleError, getErrorMessage } from "../ErrorHandlingUtils";
export const readOffers = async (): Promise => {
const clearMessage = logConsoleProgress(`Querying offers`);
@@ -13,7 +13,7 @@ export const readOffers = async (): Promise => {
return response?.resources;
} catch (error) {
// This should be removed when we can correctly identify if an account is serverless when connected using connection string too.
- if (error.message.includes("Reading or replacing offers is not supported for serverless accounts")) {
+ if (getErrorMessage(error)?.includes("Reading or replacing offers is not supported for serverless accounts")) {
return [];
}
diff --git a/src/Common/dataAccess/sendNotificationForError.ts b/src/Common/dataAccess/sendNotificationForError.ts
deleted file mode 100644
index 8c7666991..000000000
--- a/src/Common/dataAccess/sendNotificationForError.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import * as Constants from "../Constants";
-import { sendMessage } from "../MessageHandler";
-import { MessageTypes } from "../../Contracts/ExplorerContracts";
-
-export interface CosmosError {
- code: number;
- message?: string;
-}
-
-export function sendNotificationForError(error: CosmosError): void {
- if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
- if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
- return;
- }
- sendMessage({
- type: MessageTypes.ForbiddenError,
- reason: error && error.message ? error.message : error
- });
- }
-}
diff --git a/src/Common/dataAccess/updateOfferThroughputBeyondLimit.ts b/src/Common/dataAccess/updateOfferThroughputBeyondLimit.ts
index b25ed571a..1b713de85 100644
--- a/src/Common/dataAccess/updateOfferThroughputBeyondLimit.ts
+++ b/src/Common/dataAccess/updateOfferThroughputBeyondLimit.ts
@@ -1,8 +1,9 @@
import { Platform, configContext } from "../../ConfigContext";
import { getAuthorizationHeader } from "../../Utils/AuthorizationUtils";
import { AutoPilotOfferSettings } from "../../Contracts/DataModels";
-import { logConsoleProgress, logConsoleInfo, logConsoleError } from "../../Utils/NotificationConsoleUtils";
+import { logConsoleProgress, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { HttpHeaders } from "../Constants";
+import { handleError } from "../ErrorHandlingUtils";
interface UpdateOfferThroughputRequest {
subscriptionId: string;
@@ -44,8 +45,13 @@ export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThrou
clearMessage();
return undefined;
}
+
const error = await response.json();
- logConsoleError(`Failed to request an increase in throughput for ${request.throughput}: ${error.message}`);
+ handleError(
+ error,
+ `Failed to request an increase in throughput for ${request.throughput}`,
+ "updateOfferThroughputBeyondLimit"
+ );
clearMessage();
- throw new Error(error.message);
+ throw error;
}
diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx
index a3c14f5f8..2966e1cf8 100644
--- a/src/Explorer/Controls/Settings/SettingsComponent.tsx
+++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx
@@ -50,6 +50,7 @@ import {
getMongoDBCollectionIndexTransformationProgress,
readMongoDBCollectionThroughRP
} from "../../../Common/dataAccess/readMongoDBCollection";
+import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
interface SettingsV2TabInfo {
tab: SettingsV2TabTypes;
@@ -437,7 +438,7 @@ export class SettingsComponent extends React.Component {
- const stringifiedError: string = error.message;
+ const stringifiedError: string = getErrorMessage(error);
this.renewTokenError("Invalid connection string specified");
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
@@ -1141,7 +1142,7 @@ export default class Explorer {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
- `Failed to generate share url: ${error.message}`
+ `Failed to generate share url: ${getErrorMessage(error)}`
);
console.error(error);
}
@@ -1166,7 +1167,10 @@ export default class Explorer {
deferred.resolve();
},
(error: any) => {
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${error.message}`);
+ NotificationConsoleUtils.logConsoleMessage(
+ ConsoleDataType.Error,
+ `Failed to connect: ${getErrorMessage(error)}`
+ );
deferred.reject(error);
}
)
@@ -1440,19 +1444,20 @@ export default class Explorer {
this._setLoadingStatusText("Failed to fetch databases.");
this.isRefreshingExplorer(false);
deferred.reject(error);
+ const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
Action.LoadDatabases,
{
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
- error: error.message
+ error: errorMessage
},
startKey
);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
- `Error while refreshing databases: ${error.message}`
+ `Error while refreshing databases: ${errorMessage}`
);
}
);
@@ -1554,8 +1559,7 @@ export default class Explorer {
return Promise.all(sparkPromises).then(() => workspaceItems);
} catch (error) {
- Logger.logError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error.message);
+ handleError(error, "Get Arcadia workspaces failed", "Explorer/this._arcadiaManager.listWorkspacesAsync");
return Promise.resolve([]);
}
}
@@ -1590,10 +1594,10 @@ export default class Explorer {
);
} catch (error) {
this._isInitializingNotebooks = false;
- Logger.logError(error, "initNotebooks/getNotebookConnectionInfoAsync");
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Failed to get notebook workspace connection info: ${error.message}`
+ handleError(
+ error,
+ `Failed to get notebook workspace connection info: ${getErrorMessage(error)}`,
+ "initNotebooks/getNotebookConnectionInfoAsync"
);
throw error;
} finally {
@@ -1669,8 +1673,7 @@ export default class Explorer {
await this.notebookWorkspaceManager.startNotebookWorkspaceAsync(this.databaseAccount().id, "default");
}
} catch (error) {
- Logger.logError(error, "Explorer/ensureNotebookWorkspaceRunning");
- NotificationConsoleUtils.logConsoleError(`Failed to initialize notebook workspace: ${error.message}`);
+ handleError(error, "Failed to initialize notebook workspace", "Explorer/ensureNotebookWorkspaceRunning");
} finally {
clearMessage && clearMessage();
}
@@ -2052,7 +2055,7 @@ export default class Explorer {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
- trace: error.message
+ trace: getErrorMessage(error)
},
startKey
);
@@ -2514,7 +2517,7 @@ export default class Explorer {
(error: any) => {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
- `Could not download notebook ${error.message}`
+ `Could not download notebook ${getErrorMessage(error)}`
);
clearMessage();
diff --git a/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx b/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
index bbe2982b7..974ec3a8f 100644
--- a/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
+++ b/src/Explorer/Graph/GraphExplorerComponent/GraphExplorer.tsx
@@ -29,6 +29,7 @@ import { InputProperty } from "../../../Contracts/ViewModels";
import { QueryIterator, ItemDefinition, Resource } from "@azure/cosmos";
import LoadingIndicatorIcon from "../../../../images/LoadingIndicator_3Squares.gif";
import { queryDocuments, queryDocumentsPage } from "../../../Common/DocumentClientUtilityBase";
+import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
export interface GraphAccessor {
applyFilter: () => void;
@@ -892,7 +893,7 @@ export class GraphExplorer extends React.Component (this.queryTotalRequestCharge = result.requestCharge),
(error: any) => {
- const errorMsg = `Failure in submitting query: ${query}: ${error.message}`;
+ const errorMsg = `Failure in submitting query: ${query}: ${getErrorMessage(error)}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({
filterQueryError: errorMsg
@@ -1826,7 +1827,7 @@ export class GraphExplorer extends React.Component this.processGremlinQueryResults(result))
.catch((error: any) => {
- const errorMsg = `Failed to process query result: ${error.message}`;
+ const errorMsg = `Failed to process query result: ${getErrorMessage(error)}`;
GraphExplorer.reportToConsole(ConsoleDataType.Error, errorMsg);
this.setState({
filterQueryError: errorMsg
diff --git a/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts b/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
index 05627e2bf..7350f71d5 100644
--- a/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
+++ b/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
@@ -8,6 +8,7 @@ import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUti
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { HashMap } from "../../../Common/HashMap";
import * as Logger from "../../../Common/Logger";
+import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
export interface GremlinClientParameters {
endpoint: string;
@@ -58,14 +59,11 @@ export class GremlinClient {
}
},
failureCallback: (result: Result, error: any) => {
- if (typeof error !== "string") {
- error = error.message;
- }
-
+ const errorMessage = getErrorMessage(error);
const requestId = result.requestId;
if (!requestId || !this.pendingResults.has(requestId)) {
- const msg = `Error: ${error}, unknown requestId:${requestId} ${GremlinClient.getRequestChargeString(
+ const msg = `Error: ${errorMessage}, unknown requestId:${requestId} ${GremlinClient.getRequestChargeString(
result.requestCharge
)}`;
GremlinClient.reportError(msg);
@@ -73,11 +71,11 @@ export class GremlinClient {
// Fail all pending requests if no request id (fatal)
if (!requestId) {
this.pendingResults.keys().forEach((reqId: string) => {
- this.abortPendingRequest(reqId, error, null);
+ this.abortPendingRequest(reqId, errorMessage, null);
});
}
} else {
- this.abortPendingRequest(requestId, error, result.requestCharge);
+ this.abortPendingRequest(requestId, errorMessage, result.requestCharge);
}
},
infoCallback: (msg: string) => {
diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts
index 08141f678..8c38a7809 100644
--- a/src/Explorer/Panes/AddCollectionPane.ts
+++ b/src/Explorer/Panes/AddCollectionPane.ts
@@ -15,6 +15,7 @@ import { configContext, Platform } from "../../ConfigContext";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { createCollection } from "../../Common/dataAccess/createCollection";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
isPreferredApiTable: ko.Computed;
@@ -881,10 +882,9 @@ export default class AddCollectionPane extends ContextualPaneBase {
this.resetData();
this.container.refreshAllDatabases();
},
- (reason: any) => {
+ (error: any) => {
this.isExecuting(false);
- const message = ErrorParserUtility.parse(reason);
- const errorMessage = ErrorParserUtility.replaceKnownError(message[0].message);
+ const errorMessage: string = getErrorMessage(error);
this.formErrors(errorMessage);
this.formErrorsDetails(errorMessage);
const addCollectionPaneFailedMessage = {
@@ -912,7 +912,7 @@ export default class AddCollectionPane extends ContextualPaneBase {
flight: this.container.flight()
},
dataExplorerArea: Constants.Areas.ContextualPane,
- error: reason
+ error: errorMessage
};
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
}
diff --git a/src/Explorer/Panes/BrowseQueriesPane.ts b/src/Explorer/Panes/BrowseQueriesPane.ts
index 5c194c970..af3ef0a15 100644
--- a/src/Explorer/Panes/BrowseQueriesPane.ts
+++ b/src/Explorer/Panes/BrowseQueriesPane.ts
@@ -7,6 +7,7 @@ import * as Logger from "../../Common/Logger";
import { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import QueryTab from "../Tabs/QueryTab";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export class BrowseQueriesPane extends ContextualPaneBase {
public queriesGridComponentAdapter: QueriesGridComponentAdapter;
@@ -60,17 +61,19 @@ export class BrowseQueriesPane extends ContextualPaneBase {
startKey
);
} catch (error) {
+ const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
Action.SetupSavedQueries,
{
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane,
- paneTitle: this.title()
+ paneTitle: this.title(),
+ error: errorMessage
},
startKey
);
- this.formErrors(`Failed to setup a collection for saved queries: ${error.message}`);
+ this.formErrors(`Failed to setup a collection for saved queries: ${errorMessage}`);
} finally {
this.isExecuting(false);
}
diff --git a/src/Explorer/Panes/RenewAdHocAccessPane.ts b/src/Explorer/Panes/RenewAdHocAccessPane.ts
index 440df54ee..f9476c341 100644
--- a/src/Explorer/Panes/RenewAdHocAccessPane.ts
+++ b/src/Explorer/Panes/RenewAdHocAccessPane.ts
@@ -7,6 +7,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { DefaultExperienceUtility } from "../../Shared/DefaultExperienceUtility";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export class RenewAdHocAccessPane extends ContextualPaneBase {
public accessKey: ko.Observable;
@@ -82,7 +83,7 @@ export class RenewAdHocAccessPane extends ContextualPaneBase {
this.container
.renewShareAccess(this.accessKey())
.fail((error: any) => {
- const errorMessage: string = error.message;
+ const errorMessage: string = getErrorMessage(error);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${errorMessage}`);
this.formErrors(errorMessage);
this.formErrorsDetails(errorMessage);
diff --git a/src/Explorer/Panes/SaveQueryPane.ts b/src/Explorer/Panes/SaveQueryPane.ts
index 4d249d6a9..b379f6d3a 100644
--- a/src/Explorer/Panes/SaveQueryPane.ts
+++ b/src/Explorer/Panes/SaveQueryPane.ts
@@ -8,6 +8,7 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import QueryTab from "../Tabs/QueryTab";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export class SaveQueryPane extends ContextualPaneBase {
public queryName: ko.Observable;
@@ -87,18 +88,17 @@ export class SaveQueryPane extends ContextualPaneBase {
},
(error: any) => {
this.isExecuting(false);
- if (typeof error != "string") {
- error = error.message;
- }
+ const errorMessage = getErrorMessage(error);
this.formErrors("Failed to save query");
- this.formErrorsDetails(`Failed to save query: ${error}`);
+ this.formErrorsDetails(`Failed to save query: ${errorMessage}`);
TelemetryProcessor.traceFailure(
Action.SaveQuery,
{
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
- paneTitle: this.title()
+ paneTitle: this.title(),
+ error: errorMessage
},
startKey
);
@@ -132,18 +132,20 @@ export class SaveQueryPane extends ContextualPaneBase {
startKey
);
} catch (error) {
+ const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
Action.SetupSavedQueries,
{
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
- paneTitle: this.title()
+ paneTitle: this.title(),
+ error: errorMessage
},
startKey
);
this.formErrors("Failed to setup a container for saved queries");
- this.formErrors(`Failed to setup a container for saved queries: ${error.message}`);
+ this.formErrorsDetails(`Failed to setup a container for saved queries: ${errorMessage}`);
} finally {
this.isExecuting(false);
}
diff --git a/src/Explorer/Panes/SetupNotebooksPane.ts b/src/Explorer/Panes/SetupNotebooksPane.ts
index 6a8e56dc9..a95bcb667 100644
--- a/src/Explorer/Panes/SetupNotebooksPane.ts
+++ b/src/Explorer/Panes/SetupNotebooksPane.ts
@@ -6,6 +6,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as ko from "knockout";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export class SetupNotebooksPane extends ContextualPaneBase {
private description: ko.Observable;
@@ -85,7 +86,7 @@ export class SetupNotebooksPane extends ContextualPaneBase {
"Successfully created a default notebook workspace for the account"
);
} catch (error) {
- const errorMessage = typeof error == "string" ? error : error.message;
+ const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
Action.CreateNotebookWorkspace,
{
diff --git a/src/Explorer/Tabs/DocumentsTab.ts b/src/Explorer/Tabs/DocumentsTab.ts
index 1779e10a0..368c85ada 100644
--- a/src/Explorer/Tabs/DocumentsTab.ts
+++ b/src/Explorer/Tabs/DocumentsTab.ts
@@ -32,6 +32,7 @@ import {
createDocument
} from "../../Common/DocumentClientUtilityBase";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export default class DocumentsTab extends TabsBase {
public selectedDocumentId: ko.Observable;
@@ -774,10 +775,8 @@ export default class DocumentsTab extends TabsBase {
},
error => {
this.isExecutionError(true);
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- typeof error === "string" ? error : error.message
- );
+ const errorMessage = getErrorMessage(error);
+ NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, errorMessage);
if (this.onLoadStartKey != null && this.onLoadStartKey != undefined) {
TelemetryProcessor.traceFailure(
Action.Tab,
@@ -788,7 +787,7 @@ export default class DocumentsTab extends TabsBase {
defaultExperience: this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
- error: error
+ error: errorMessage
},
this.onLoadStartKey
);
diff --git a/src/Explorer/Tabs/SettingsTab.ts b/src/Explorer/Tabs/SettingsTab.ts
index 4ae2785fe..29dfbe9fc 100644
--- a/src/Explorer/Tabs/SettingsTab.ts
+++ b/src/Explorer/Tabs/SettingsTab.ts
@@ -22,6 +22,7 @@ import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandBu
import { userContext } from "../../UserContext";
import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit";
import { configContext, Platform } from "../../ConfigContext";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
const ttlWarning: string = `
The system will automatically delete items based on the TTL value (in seconds) you provide, without needing a delete operation explicitly issued by a client application.
@@ -1174,7 +1175,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.WaitsFor
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
- error: error.message
+ error: getErrorMessage(error)
},
startKey
);
diff --git a/src/Explorer/Tree/Collection.ts b/src/Explorer/Tree/Collection.ts
index 14192ae2b..a1921cda7 100644
--- a/src/Explorer/Tree/Collection.ts
+++ b/src/Explorer/Tree/Collection.ts
@@ -40,6 +40,7 @@ import Explorer from "../Explorer";
import { userContext } from "../../UserContext";
import TabsBase from "../Tabs/TabsBase";
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export default class Collection implements ViewModels.Collection {
public nodeKind: string;
@@ -610,6 +611,7 @@ export default class Collection implements ViewModels.Collection {
settingsTab.pendingNotification(pendingNotification);
},
(error: any) => {
+ const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
Action.Tab,
{
@@ -619,13 +621,13 @@ export default class Collection implements ViewModels.Collection {
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: settingsTabOptions.title,
- error: error
+ error: errorMessage
},
startKey
);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
- `Error while fetching container settings for container ${this.id()}: ${error.message}`
+ `Error while fetching container settings for container ${this.id()}: ${errorMessage}`
);
throw error;
}
@@ -869,7 +871,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
- error: typeof error === "string" ? error : error.message
+ error: getErrorMessage(error)
});
}
);
@@ -928,7 +930,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
- error: typeof error === "string" ? error : error.message
+ error: getErrorMessage(error)
});
}
);
@@ -988,7 +990,7 @@ export default class Collection implements ViewModels.Collection {
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
- error: typeof error === "string" ? error : error.message
+ error: getErrorMessage(error)
});
}
);
@@ -1185,7 +1187,7 @@ export default class Collection implements ViewModels.Collection {
},
error => {
record.numFailed++;
- record.errors = [...record.errors, error.message];
+ record.errors = [...record.errors, getErrorMessage(error)];
return Q.resolve();
}
);
@@ -1238,7 +1240,7 @@ export default class Collection implements ViewModels.Collection {
(error: any) => {
Logger.logError(
JSON.stringify({
- error: error.message,
+ error: getErrorMessage(error),
accountName: this.container && this.container.databaseAccount(),
databaseName: this.databaseId,
collectionName: this.id()
diff --git a/src/Explorer/Tree/Database.ts b/src/Explorer/Tree/Database.ts
index faa33d406..83bb3c361 100644
--- a/src/Explorer/Tree/Database.ts
+++ b/src/Explorer/Tree/Database.ts
@@ -16,6 +16,7 @@ import { readCollections } from "../../Common/dataAccess/readCollections";
import { readDatabaseOffer } from "../../Common/dataAccess/readDatabaseOffer";
import { DefaultAccountExperienceType } from "../../DefaultAccountExperienceType";
import { fetchPortalNotifications } from "../../Common/PortalNotifications";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export default class Database implements ViewModels.Database {
public nodeKind: string;
@@ -88,6 +89,7 @@ export default class Database implements ViewModels.Database {
this.container.tabsManager.activateNewTab(settingsTab);
},
(error: any) => {
+ const errorMessage = getErrorMessage(error);
TelemetryProcessor.traceFailure(
Action.Tab,
{
@@ -97,13 +99,13 @@ export default class Database implements ViewModels.Database {
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Scale",
- error: error
+ error: errorMessage
},
startKey
);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
- `Error while fetching database settings for database ${this.id()}: ${error.message}`
+ `Error while fetching database settings for database ${this.id()}: ${errorMessage}`
);
throw error;
}
@@ -239,7 +241,7 @@ export default class Database implements ViewModels.Database {
(error: any) => {
Logger.logError(
JSON.stringify({
- error: error.message,
+ error: getErrorMessage(error),
accountName: this.container && this.container.databaseAccount(),
databaseName: this.id(),
collectionName: this.id()
diff --git a/src/Explorer/Tree/StoredProcedure.ts b/src/Explorer/Tree/StoredProcedure.ts
index f2db0b577..1c35dfa8f 100644
--- a/src/Explorer/Tree/StoredProcedure.ts
+++ b/src/Explorer/Tree/StoredProcedure.ts
@@ -9,6 +9,7 @@ import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Explorer from "../Explorer";
import StoredProcedureTab from "../Tabs/StoredProcedureTab";
import TabsBase from "../Tabs/TabsBase";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
const sampleStoredProcedureBody: string = `// SAMPLE STORED PROCEDURE
function sample(prefix) {
@@ -158,7 +159,7 @@ export default class StoredProcedure {
sprocTab.onExecuteSprocsResult(result, result.scriptLogs);
},
(error: any) => {
- sprocTab.onExecuteSprocsError(error.message);
+ sprocTab.onExecuteSprocsError(getErrorMessage(error));
}
)
.finally(() => {
diff --git a/src/GitHub/GitHubContentProvider.ts b/src/GitHub/GitHubContentProvider.ts
index 9d6dcc82b..6142b7a6f 100644
--- a/src/GitHub/GitHubContentProvider.ts
+++ b/src/GitHub/GitHubContentProvider.ts
@@ -9,6 +9,7 @@ import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
import { GitHubClient, IGitHubFile, IGitHubResponse } from "./GitHubClient";
import * as GitHubUtils from "../Utils/GitHubUtils";
import UrlUtility from "../Common/UrlUtility";
+import { getErrorMessage } from "../Common/ErrorHandlingUtils";
export interface GitHubContentProviderParams {
gitHubClient: GitHubClient;
@@ -423,7 +424,7 @@ export class GitHubContentProvider implements IContentProvider {
request: {},
status: error.errno,
response: error,
- responseText: error.message,
+ responseText: getErrorMessage(error),
responseType: "json"
};
}
diff --git a/src/HostedExplorer.ts b/src/HostedExplorer.ts
index 77024a87c..ac24eeaea 100644
--- a/src/HostedExplorer.ts
+++ b/src/HostedExplorer.ts
@@ -21,6 +21,7 @@ import {
import { DialogComponentAdapter } from "./Explorer/Controls/DialogReactComponent/DialogComponentAdapter";
import { DialogProps } from "./Explorer/Controls/DialogReactComponent/DialogComponent";
import { DirectoryListProps } from "./Explorer/Controls/Directory/DirectoryListComponent";
+import { getErrorMessage } from "./Common/ErrorHandlingUtils";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import { LocalStorageUtility, StorageKey, SessionStorageUtility } from "./Shared/StorageUtility";
import * as Logger from "./Common/Logger";
@@ -509,12 +510,13 @@ class HostedExplorer {
}
});
} catch (error) {
- Logger.logError(error, "HostedExplorer/_getArcadiaToken");
+ const errorMessage = getErrorMessage(error);
+ Logger.logError(errorMessage, "HostedExplorer/_getArcadiaToken");
this._sendMessageToExplorerFrame({
actionType: ActionType.TransmitCachedData,
message: {
id: message && message.id,
- error: error.message
+ error: errorMessage
}
});
}
@@ -559,12 +561,9 @@ class HostedExplorer {
});
},
error => {
- if (typeof error !== "string") {
- error = JSON.stringify(error, Object.getOwnPropertyNames(error));
- }
this._sendMessageToExplorerFrame({
type: MessageTypes.GetAccessAadResponse,
- error
+ error: getErrorMessage(error)
});
}
);
@@ -1008,7 +1007,7 @@ class HostedExplorer {
return accounts;
} catch (error) {
- this._logConsoleMessage(ConsoleDataType.Error, `Failed to fetch accounts: ${error.message}`);
+ this._logConsoleMessage(ConsoleDataType.Error, `Failed to fetch accounts: ${getErrorMessage(error)}`);
this._clearInProgressMessageWithId(id);
throw error;
@@ -1047,7 +1046,7 @@ class HostedExplorer {
displayText: "Error loading account"
});
this._updateLoadingStatusText(`Failed to load selected account: ${newAccount.name}`);
- this._logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${error.message}`);
+ this._logConsoleMessage(ConsoleDataType.Error, `Failed to connect: ${getErrorMessage(error)}`);
this._clearInProgressMessageWithId(id);
throw error;
}
diff --git a/src/Platform/Hosted/Main.ts b/src/Platform/Hosted/Main.ts
index d424704f4..e218a136e 100644
--- a/src/Platform/Hosted/Main.ts
+++ b/src/Platform/Hosted/Main.ts
@@ -23,6 +23,7 @@ import "../../Explorer/Tables/DataTable/DataTableBindingManager";
import Explorer from "../../Explorer/Explorer";
import { updateUserContext } from "../../UserContext";
import { configContext } from "../../ConfigContext";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export default class Main {
private static _databaseAccountId: string;
@@ -245,7 +246,7 @@ export default class Main {
);
},
(error: any) => {
- deferred.reject(`Failed to generate encrypted token: ${error.message}`);
+ deferred.reject(`Failed to generate encrypted token: ${getErrorMessage(error)}`);
}
);
diff --git a/src/workers/upload/index.ts b/src/workers/upload/index.ts
index 7f5172db3..5e38b8d79 100644
--- a/src/workers/upload/index.ts
+++ b/src/workers/upload/index.ts
@@ -3,6 +3,7 @@ import { DocumentClientParams, UploadDetailsRecord, UploadDetails } from "./defi
import { client } from "../../Common/CosmosClient";
import { configContext, updateConfigContext } from "../../ConfigContext";
import { updateUserContext } from "../../UserContext";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
let numUploadsSuccessful = 0;
let numUploadsFailed = 0;
@@ -93,7 +94,7 @@ function createDocumentsFromFile(fileName: string, documentContent: string): voi
})
.catch(error => {
console.error(error);
- recordUploadDetailErrorForFile(fileName, error.message);
+ recordUploadDetailErrorForFile(fileName, getErrorMessage(error));
numUploadsFailed++;
})
.finally(() => {
diff --git a/tsconfig.strict.json b/tsconfig.strict.json
index 94bd2d8fa..89cfd09a2 100644
--- a/tsconfig.strict.json
+++ b/tsconfig.strict.json
@@ -15,13 +15,11 @@
"./src/Common/DeleteFeedback.ts",
"./src/Common/HashMap.ts",
"./src/Common/HeadersUtility.ts",
- "./src/Common/Logger.ts",
"./src/Common/MessageHandler.ts",
"./src/Common/MongoUtility.ts",
"./src/Common/ObjectCache.ts",
"./src/Common/ThemeUtility.ts",
"./src/Common/UrlUtility.ts",
- "./src/Common/dataAccess/sendNotificationForError.ts",
"./src/ConfigContext.ts",
"./src/Contracts/ActionContracts.ts",
"./src/Contracts/DataModels.ts",
From 473f722dcc163f950b39a00d1215bc703fba5e37 Mon Sep 17 00:00:00 2001
From: Zachary Foster
Date: Mon, 2 Nov 2020 14:33:14 -0500
Subject: [PATCH 02/13] E2E Test Rewrite (#300)
* Adds tables test
* Include .env var
* Adds asElement on again
* Add further loading states
* Format
* Hope to not lose focus
* Adds ID to shared key and modifies value of input directly
* Fix tables test
* Format
* Try uploading screenshots
* indent
* Fixes connection string
* Try wildcard upload path
* Rebuilds test structure, assertions, dependencies
* Wait longer for container create
* format
---
.github/workflows/ci.yml | 5 +
src/Explorer/Panes/AddCollectionPane.html | 2 +-
test/cassandra/container.spec.ts | 117 +++++++++++++++-----
test/mongo/container.spec.ts | 128 ++++++++++++++++------
test/sql/container.spec.ts | 124 +++++++++++++++------
test/tables/container.spec.ts | 111 +++++++++++++++++++
test/utils/shared.ts | 4 +-
7 files changed, 388 insertions(+), 103 deletions(-)
create mode 100644 test/tables/container.spec.ts
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 78a043e29..53a39150a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -150,6 +150,11 @@ jobs:
PORTAL_RUNNER_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
+ TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
+ - uses: actions/upload-artifact@v2
+ with:
+ name: screenshots
+ path: failed-*
nuget:
name: Publish Nuget
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
diff --git a/src/Explorer/Panes/AddCollectionPane.html b/src/Explorer/Panes/AddCollectionPane.html
index 775790fce..9167bcf93 100644
--- a/src/Explorer/Panes/AddCollectionPane.html
+++ b/src/Explorer/Panes/AddCollectionPane.html
@@ -288,7 +288,7 @@
range of values and is likely to have evenly distributed access patterns.
- {
it("creates a collection", async () => {
try {
- const keyspaceId = generateUniqueName("keyspaceid");
- const tableId = generateUniqueName("tableid");
+ const keyspaceId = generateUniqueName("key");
+ const tableId = generateUniqueName("tab");
const frame = await login(process.env.CASSANDRA_CONNECTION_STRING);
// create new table
@@ -31,38 +34,69 @@ describe("Collection Add and Delete Cassandra spec", () => {
// open database menu
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
-
- await frame.waitFor(`div[data-test="${keyspaceId}"]`, { visible: true });
- await frame.waitFor(LOADING_STATE_DELAY);
- await frame.waitFor(`div[data-test="${keyspaceId}"]`, { visible: true });
- await frame.click(`div[data-test="${keyspaceId}"]`);
- await frame.waitFor(`span[title="${tableId}"]`, { visible: true });
-
- // delete container
-
- // click context menu for container
- await frame.waitFor(`div[data-test="${tableId}"] > div > button`, { visible: true });
- await frame.click(`div[data-test="${tableId}"] > div > button`);
-
- // click delete container
- await frame.waitForSelector("body > div.ms-Layer.ms-Layer--fixed");
- await frame.waitFor(RENDER_DELAY);
- const elements = await frame.$$('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
- await elements[0].click();
-
- // confirm delete container
- await frame.type('input[data-test="confirmCollectionId"]', tableId.trim());
-
- // click delete
- await frame.click('input[data-test="deleteCollection"]');
- await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
await frame.waitFor(LOADING_STATE_DELAY);
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
- await expect(page).not.toMatchElement(`div[data-test="${tableId}"]`);
+ const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
+ const selectedDbId = await frame.evaluate(element => {
+ return element.attributes["data-test"].textContent;
+ }, databases[0]);
+
+ await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
+ await frame.waitFor(CREATE_DELAY);
+ await frame.waitFor("div[class='rowData'] > span[class='message']");
+
+ const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", elements => {
+ return elements.some(el => el.textContent.includes("Successfully created"));
+ });
+
+ expect(didCreateContainer).toBe(true);
+
+ await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
+ await frame.waitFor(LOADING_STATE_DELAY);
+
+ await clickDBMenu(selectedDbId, frame);
+
+ const collections = await frame.$$(
+ `div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`
+ );
+
+ if (collections.length) {
+ await frame.waitFor(`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`, {
+ visible: true
+ });
+
+ const textId = await frame.evaluate(element => {
+ return element.attributes["data-test"].textContent;
+ }, collections[0]);
+ await frame.waitFor(`div[data-test="${textId}"]`, { visible: true });
+ // delete container
+
+ // click context menu for container
+ await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true });
+ await frame.click(`div[data-test="${textId}"] > div > button`);
+
+ // click delete container
+ await frame.waitFor(RENDER_DELAY);
+ await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
+ await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
+
+ // confirm delete container
+ await frame.waitFor('input[data-test="confirmCollectionId"]', { visible: true });
+ await frame.type('input[data-test="confirmCollectionId"]', textId);
+
+ // click delete
+ await frame.waitFor('input[data-test="deleteCollection"]', { visible: true });
+ await frame.click('input[data-test="deleteCollection"]');
+ await frame.waitFor(LOADING_STATE_DELAY);
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+
+ await expect(page).not.toMatchElement(`div[data-test="${textId}"]`);
+ }
// click context menu for database
await frame.waitFor(`div[data-test="${keyspaceId}"] > div > button`);
+ await frame.waitFor(RENDER_DELAY);
const button = await frame.$(`div[data-test="${keyspaceId}"] > div > button`);
await button.focus();
await button.asElement().click();
@@ -80,12 +114,35 @@ describe("Collection Add and Delete Cassandra spec", () => {
// click delete
await frame.click('input[data-test="deleteDatabase"]');
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+ await frame.waitFor(LOADING_STATE_DELAY);
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
await expect(page).not.toMatchElement(`div[data-test="${keyspaceId}"]`);
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const testName = (expect as any).getState().currentTestName;
- await page.screenshot({ path: `Test Failed ${testName}.png` });
+ await page.screenshot({ path: `failed-${testName}.jpg` });
throw error;
}
});
});
+
+async function clickDBMenu(dbId: string, frame: Frame, retries = 0) {
+ const button = await frame.$(`div[data-test="${dbId}"]`);
+ await button.focus();
+ const handler = await button.asElement();
+ await handler.click();
+ await ensureMenuIsOpen(dbId, frame, retries);
+ return button;
+}
+
+async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) {
+ await frame.waitFor(RETRY_DELAY);
+ const button = await frame.$(`div[data-test="${dbId}"]`);
+ const classList = await frame.evaluate(button => {
+ return button.parentElement.classList;
+ }, button);
+ if (!Object.values(classList).includes("selected") && retries < 5) {
+ retries = retries + 1;
+ await clickDBMenu(dbId, frame, retries);
+ }
+}
diff --git a/test/mongo/container.spec.ts b/test/mongo/container.spec.ts
index 54febbcf1..b5e0599de 100644
--- a/test/mongo/container.spec.ts
+++ b/test/mongo/container.spec.ts
@@ -1,17 +1,19 @@
import "expect-puppeteer";
+import { Frame } from "puppeteer";
import { generateUniqueName, login } from "../utils/shared";
jest.setTimeout(300000);
-
const LOADING_STATE_DELAY = 2500;
+const RETRY_DELAY = 5000;
+const CREATE_DELAY = 10000;
const RENDER_DELAY = 1000;
describe("Collection Add and Delete Mongo spec", () => {
- it("creates and deletes a collection", async () => {
+ it("creates a collection", async () => {
try {
- const dbId = generateUniqueName("TestDatabase");
- const collectionId = generateUniqueName("TestCollection");
- const sharedKey = generateUniqueName("SharedKey");
+ const dbId = generateUniqueName("db");
+ const collectionId = generateUniqueName("col");
+ const sharedKey = `${generateUniqueName()}`;
const frame = await login(process.env.MONGO_CONNECTION_STRING);
// create new collection
@@ -52,39 +54,70 @@ describe("Collection Add and Delete Mongo spec", () => {
// validate created
// open database menu
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
-
- await frame.waitFor(`div[data-test="${dbId}"]`), { visible: true };
- await frame.waitFor(LOADING_STATE_DELAY);
- await frame.waitFor(`div[data-test="${dbId}"]`), { visible: true };
- await frame.click(`div[data-test="${dbId}"]`);
- await frame.waitFor(`div[data-test="${collectionId}"]`, { visible: true });
-
- // delete container
-
- // click context menu for container
- await frame.waitFor(`div[data-test="${collectionId}"] > div > button`, { visible: true });
- await frame.click(`div[data-test="${collectionId}"] > div > button`);
-
- // click delete container
- await frame.waitFor(RENDER_DELAY);
- await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]', { visible: true });
- await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
-
- // confirm delete container
- await frame.waitFor('input[data-test="confirmCollectionId"]', { visible: true });
- await frame.type('input[data-test="confirmCollectionId"]', collectionId.trim());
-
- // click delete
- await frame.waitFor('input[data-test="deleteCollection"]', { visible: true });
- await frame.click('input[data-test="deleteCollection"]');
await frame.waitFor(LOADING_STATE_DELAY);
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
- await expect(page).not.toMatchElement(`div[data-test="${collectionId}"]`);
+ const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
+ const selectedDbId = await frame.evaluate(element => {
+ return element.attributes["data-test"].textContent;
+ }, databases[0]);
+
+ await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
+ await frame.waitFor(CREATE_DELAY);
+ await frame.waitFor("div[class='rowData'] > span[class='message']");
+
+ const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", elements => {
+ return elements.some(el => el.textContent.includes("Successfully created"));
+ });
+
+ expect(didCreateContainer).toBe(true);
+
+ await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
+ await frame.waitFor(LOADING_STATE_DELAY);
+
+ await clickDBMenu(selectedDbId, frame);
+
+ const collections = await frame.$$(
+ `div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`
+ );
+
+ if (collections.length) {
+ await frame.waitFor(`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`, {
+ visible: true
+ });
+
+ const textId = await frame.evaluate(element => {
+ return element.attributes["data-test"].textContent;
+ }, collections[0]);
+ await frame.waitFor(`div[data-test="${textId}"]`, { visible: true });
+ // delete container
+
+ // click context menu for container
+ await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true });
+ await frame.click(`div[data-test="${textId}"] > div > button`);
+
+ // click delete container
+ await frame.waitFor(RENDER_DELAY);
+ await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
+ await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
+
+ // confirm delete container
+ await frame.waitFor('input[data-test="confirmCollectionId"]', { visible: true });
+ await frame.type('input[data-test="confirmCollectionId"]', textId);
+
+ // click delete
+ await frame.waitFor('input[data-test="deleteCollection"]', { visible: true });
+ await frame.click('input[data-test="deleteCollection"]');
+ await frame.waitFor(LOADING_STATE_DELAY);
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+
+ await expect(page).not.toMatchElement(`div[data-test="${textId}"]`);
+ }
// click context menu for database
- await frame.waitFor(`div[data-test="${dbId}"] > div > button`);
- const button = await frame.$(`div[data-test="${dbId}"] > div > button`);
+ await frame.waitFor(`div[data-test="${selectedDbId}"] > div > button`);
+ await frame.waitFor(RENDER_DELAY);
+ const button = await frame.$(`div[data-test="${selectedDbId}"] > div > button`);
await button.focus();
await button.asElement().click();
@@ -96,17 +129,40 @@ describe("Collection Add and Delete Mongo spec", () => {
// confirm delete database
await frame.waitForSelector('input[data-test="confirmDatabaseId"]', { visible: true });
await frame.waitFor(RENDER_DELAY);
- await frame.type('input[data-test="confirmDatabaseId"]', dbId.trim());
+ await frame.type('input[data-test="confirmDatabaseId"]', selectedDbId);
// click delete
await frame.click('input[data-test="deleteDatabase"]');
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
- await expect(page).not.toMatchElement(`div[data-test="${dbId}"]`);
+ await frame.waitFor(LOADING_STATE_DELAY);
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+ await expect(page).not.toMatchElement(`div[data-test="${selectedDbId}"]`);
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const testName = (expect as any).getState().currentTestName;
- await page.screenshot({ path: `Test Failed ${testName}.png` });
+ await page.screenshot({ path: `failed-${testName}.jpg` });
throw error;
}
});
});
+
+async function clickDBMenu(dbId: string, frame: Frame, retries = 0) {
+ const button = await frame.$(`div[data-test="${dbId}"]`);
+ await button.focus();
+ const handler = await button.asElement();
+ await handler.click();
+ await ensureMenuIsOpen(dbId, frame, retries);
+ return button;
+}
+
+async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) {
+ await frame.waitFor(RETRY_DELAY);
+ const button = await frame.$(`div[data-test="${dbId}"]`);
+ const classList = await frame.evaluate(button => {
+ return button.parentElement.classList;
+ }, button);
+ if (!Object.values(classList).includes("selected") && retries < 5) {
+ retries = retries + 1;
+ await clickDBMenu(dbId, frame, retries);
+ }
+}
diff --git a/test/sql/container.spec.ts b/test/sql/container.spec.ts
index 6b801c817..ad5d7f251 100644
--- a/test/sql/container.spec.ts
+++ b/test/sql/container.spec.ts
@@ -1,16 +1,19 @@
import "expect-puppeteer";
+import { Frame } from "puppeteer";
import { generateUniqueName, login } from "../utils/shared";
jest.setTimeout(300000);
const LOADING_STATE_DELAY = 2500;
+const RETRY_DELAY = 5000;
+const CREATE_DELAY = 10000;
const RENDER_DELAY = 1000;
describe("Collection Add and Delete SQL spec", () => {
it("creates a collection", async () => {
try {
- const dbId = generateUniqueName("TestDatabase");
- const collectionId = generateUniqueName("TestCollection");
- const sharedKey = generateUniqueName("SharedKey");
+ const dbId = generateUniqueName("db");
+ const collectionId = generateUniqueName("col");
+ const sharedKey = `/skey${generateUniqueName()}`;
const frame = await login(process.env.PORTAL_RUNNER_CONNECTION_STRING);
// create new collection
@@ -51,39 +54,69 @@ describe("Collection Add and Delete SQL spec", () => {
// validate created
// open database menu
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
-
- await frame.waitFor(`div[data-test="${dbId}"]`), { visible: true };
- await frame.waitFor(LOADING_STATE_DELAY);
- await frame.waitFor(`div[data-test="${dbId}"]`), { visible: true };
- await frame.click(`div[data-test="${dbId}"]`);
- await frame.waitFor(`div[data-test="${collectionId}"]`, { visible: true });
-
- // delete container
-
- // click context menu for container
- await frame.waitFor(`div[data-test="${collectionId}"] > div > button`, { visible: true });
- await frame.click(`div[data-test="${collectionId}"] > div > button`);
-
- // click delete container
- await frame.waitFor(RENDER_DELAY);
- await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
- await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
-
- // confirm delete container
- await frame.waitFor('input[data-test="confirmCollectionId"]', { visible: true });
- await frame.type('input[data-test="confirmCollectionId"]', collectionId);
-
- // click delete
- await frame.waitFor('input[data-test="deleteCollection"]', { visible: true });
- await frame.click('input[data-test="deleteCollection"]');
await frame.waitFor(LOADING_STATE_DELAY);
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+ const databases = await frame.$$(`div[class="databaseHeader main1 nodeItem "] > div[class="treeNodeHeader "]`);
+ const selectedDbId = await frame.evaluate(element => {
+ return element.attributes["data-test"].textContent;
+ }, databases[0]);
- await expect(page).not.toMatchElement(`div[data-test="${collectionId}"]`);
+ await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
+ await frame.waitFor(CREATE_DELAY);
+ await frame.waitFor("div[class='rowData'] > span[class='message']");
+
+ const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", elements => {
+ return elements.some(el => el.textContent.includes("Successfully created"));
+ });
+
+ expect(didCreateContainer).toBe(true);
+
+ await frame.waitFor(`div[data-test="${selectedDbId}"]`), { visible: true };
+ await frame.waitFor(LOADING_STATE_DELAY);
+
+ await clickDBMenu(selectedDbId, frame);
+
+ const collections = await frame.$$(
+ `div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`
+ );
+
+ if (collections.length) {
+ await frame.waitFor(`div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`, {
+ visible: true
+ });
+
+ const textId = await frame.evaluate(element => {
+ return element.attributes["data-test"].textContent;
+ }, collections[0]);
+ await frame.waitFor(`div[data-test="${textId}"]`, { visible: true });
+ // delete container
+
+ // click context menu for container
+ await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true });
+ await frame.click(`div[data-test="${textId}"] > div > button`);
+
+ // click delete container
+ await frame.waitFor(RENDER_DELAY);
+ await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
+ await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
+
+ // confirm delete container
+ await frame.waitFor('input[data-test="confirmCollectionId"]', { visible: true });
+ await frame.type('input[data-test="confirmCollectionId"]', textId);
+
+ // click delete
+ await frame.waitFor('input[data-test="deleteCollection"]', { visible: true });
+ await frame.click('input[data-test="deleteCollection"]');
+ await frame.waitFor(LOADING_STATE_DELAY);
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+
+ await expect(page).not.toMatchElement(`div[data-test="${textId}"]`);
+ }
// click context menu for database
- await frame.waitFor(`div[data-test="${dbId}"] > div > button`);
- const button = await frame.$(`div[data-test="${dbId}"] > div > button`);
+ await frame.waitFor(`div[data-test="${selectedDbId}"] > div > button`);
+ await frame.waitFor(RENDER_DELAY);
+ const button = await frame.$(`div[data-test="${selectedDbId}"] > div > button`);
await button.focus();
await button.asElement().click();
@@ -95,17 +128,40 @@ describe("Collection Add and Delete SQL spec", () => {
// confirm delete database
await frame.waitForSelector('input[data-test="confirmDatabaseId"]', { visible: true });
await frame.waitFor(RENDER_DELAY);
- await frame.type('input[data-test="confirmDatabaseId"]', dbId.trim());
+ await frame.type('input[data-test="confirmDatabaseId"]', selectedDbId);
// click delete
await frame.click('input[data-test="deleteDatabase"]');
await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
- await expect(page).not.toMatchElement(`div[data-test="${dbId}"]`);
+ await frame.waitFor(LOADING_STATE_DELAY);
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+ await expect(page).not.toMatchElement(`div[data-test="${selectedDbId}"]`);
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const testName = (expect as any).getState().currentTestName;
- await page.screenshot({ path: `Test Failed ${testName}.jpg` });
+ await page.screenshot({ path: `failed-${testName}.jpg` });
throw error;
}
});
});
+
+async function clickDBMenu(dbId: string, frame: Frame, retries = 0) {
+ const button = await frame.$(`div[data-test="${dbId}"]`);
+ await button.focus();
+ const handler = await button.asElement();
+ await handler.click();
+ await ensureMenuIsOpen(dbId, frame, retries);
+ return button;
+}
+
+async function ensureMenuIsOpen(dbId: string, frame: Frame, retries: number) {
+ await frame.waitFor(RETRY_DELAY);
+ const button = await frame.$(`div[data-test="${dbId}"]`);
+ const classList = await frame.evaluate(button => {
+ return button.parentElement.classList;
+ }, button);
+ if (!Object.values(classList).includes("selected") && retries < 5) {
+ retries = retries + 1;
+ await clickDBMenu(dbId, frame, retries);
+ }
+}
diff --git a/test/tables/container.spec.ts b/test/tables/container.spec.ts
new file mode 100644
index 000000000..0f1c07eef
--- /dev/null
+++ b/test/tables/container.spec.ts
@@ -0,0 +1,111 @@
+import "expect-puppeteer";
+import { Frame } from "puppeteer";
+import { generateUniqueName, login } from "../utils/shared";
+
+jest.setTimeout(300000);
+const RETRY_DELAY = 5000;
+const LOADING_STATE_DELAY = 2500;
+const RENDER_DELAY = 1000;
+
+describe("Collection Add and Delete Tables spec", () => {
+ it("creates a collection", async () => {
+ try {
+ const tableId = generateUniqueName("tab");
+ const frame = await login(process.env.TABLES_CONNECTION_STRING);
+
+ // create new collection
+ await frame.waitFor('button[data-test="New Table"]', { visible: true });
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+ await frame.click('button[data-test="New Table"]');
+
+ // type database id
+ await frame.waitFor('input[data-test="addCollection-newDatabaseId"]');
+ const dbInput = await frame.$('input[data-test="addCollection-newDatabaseId"]');
+ await dbInput.press("Backspace");
+ await dbInput.type(tableId);
+
+ // click submit
+ await frame.waitFor("#submitBtnAddCollection");
+ await frame.click("#submitBtnAddCollection");
+
+ // validate created
+ // open database menu
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+ await frame.waitFor(LOADING_STATE_DELAY);
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+
+ await frame.waitFor(`div[data-test="TablesDB"]`), { visible: true };
+ await frame.waitFor(LOADING_STATE_DELAY);
+
+ const didCreateContainer = await frame.$$eval("div[class='rowData'] > span[class='message']", elements => {
+ return elements.some(el => el.textContent.includes("Successfully created"));
+ });
+
+ expect(didCreateContainer).toBe(true);
+
+ await frame.waitFor(`div[data-test="TablesDB"]`), { visible: true };
+ await frame.waitFor(LOADING_STATE_DELAY);
+
+ await clickTablesMenu(frame);
+
+ const collections = await frame.$$(
+ `div[class="collectionHeader main2 nodeItem "] > div[class="treeNodeHeader "]`
+ );
+ const textId = await frame.evaluate(element => {
+ return element.attributes["data-test"].textContent;
+ }, collections[0]);
+ await frame.waitFor(`div[data-test="${textId}"]`, { visible: true });
+
+ // delete container
+
+ // click context menu for container
+ await frame.waitFor(`div[data-test="${textId}"] > div > button`, { visible: true });
+ await frame.click(`div[data-test="${textId}"] > div > button`);
+
+ // click delete container
+ await frame.waitFor(RENDER_DELAY);
+ await frame.waitFor('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
+ await frame.click('span[class="treeComponentMenuItemLabel deleteCollectionMenuItemLabel"]');
+
+ // confirm delete container
+ await frame.waitFor('input[data-test="confirmCollectionId"]', { visible: true });
+ await frame.type('input[data-test="confirmCollectionId"]', textId);
+
+ // click delete
+ await frame.waitFor('input[data-test="deleteCollection"]', { visible: true });
+ await frame.click('input[data-test="deleteCollection"]');
+ await frame.waitFor(LOADING_STATE_DELAY);
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+ await frame.waitFor(LOADING_STATE_DELAY);
+ await frame.waitForSelector('div[class="splashScreen"] > div[class="title"]', { visible: true });
+
+ await expect(page).not.toMatchElement(`div[data-test="${textId}"]`);
+ } catch (error) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const testName = (expect as any).getState().currentTestName;
+ await page.screenshot({ path: `failed-${testName}.jpg` });
+ throw error;
+ }
+ });
+});
+
+async function clickTablesMenu(frame: Frame, retries = 0) {
+ const button = await frame.$(`div[data-test="TablesDB"]`);
+ await button.focus();
+ const handler = await button.asElement();
+ await handler.click();
+ await ensureMenuIsOpen(frame, retries);
+ return button;
+}
+
+async function ensureMenuIsOpen(frame: Frame, retries: number) {
+ await frame.waitFor(RETRY_DELAY);
+ const button = await frame.$(`div[data-test="TablesDB"]`);
+ const classList = await frame.evaluate(button => {
+ return button.parentElement.classList;
+ }, button);
+ if (!Object.values(classList).includes("selected") && retries < 5) {
+ retries = retries + 1;
+ await clickTablesMenu(frame, retries);
+ }
+}
diff --git a/test/utils/shared.ts b/test/utils/shared.ts
index 458899700..f7c1f3faf 100644
--- a/test/utils/shared.ts
+++ b/test/utils/shared.ts
@@ -3,7 +3,7 @@ import { Frame } from "puppeteer";
export async function login(connectionString: string): Promise {
const prodUrl = "https://localhost:1234/hostedExplorer.html";
- page.goto(prodUrl);
+ page.goto(prodUrl, { waitUntil: "networkidle2" });
// log in with connection string
const handle = await page.waitForSelector("iframe");
@@ -16,6 +16,6 @@ export async function login(connectionString: string): Promise {
return frame;
}
-export function generateUniqueName(baseName: string, length = 8): string {
+export function generateUniqueName(baseName = "", length = 4): string {
return `${baseName}${crypto.randomBytes(length).toString("hex")}`;
}
From e6ca1d25c942811d10cf86f6620b6decf9e5a43a Mon Sep 17 00:00:00 2001
From: Srinath Narayanan
Date: Mon, 2 Nov 2020 13:19:45 -0800
Subject: [PATCH 03/13] Added index refresh to SQL API indexing policy editor
(#306)
* Index refresh component introduced
- Made all notifications in Mongo Index editor have 12 font size
- Added indexing policy refresh to sql indexing policy editor
- Added "you have unsaved changes" message, replace old message for lazy indexing policy changes
* formatting changes
* addressed PR comments
---
.../getIndexTransformationProgress.ts | 28 ++++++
.../dataAccess/readMongoDBCollection.tsx | 28 ------
.../Settings/SettingsComponent.test.tsx | 4 +-
.../Controls/Settings/SettingsComponent.tsx | 22 ++---
.../Settings/SettingsRenderUtils.test.tsx | 4 +-
.../Controls/Settings/SettingsRenderUtils.tsx | 20 ++---
.../IndexingPolicyComponent.test.tsx | 4 +-
.../IndexingPolicyComponent.tsx | 18 +++-
.../IndexingPolicyRefreshComponent.test.tsx | 15 ++++
.../IndexingPolicyRefreshComponent.tsx | 62 +++++++++++++
...dexingPolicyRefreshComponent.test.tsx.snap | 24 ++++++
.../MongoIndexingPolicyComponent.test.tsx | 27 ++----
.../MongoIndexingPolicyComponent.tsx | 86 +++++--------------
...MongoIndexingPolicyComponent.test.tsx.snap | 3 +
.../IndexingPolicyComponent.test.tsx.snap | 3 +
.../Controls/Settings/SettingsUtils.test.tsx | 10 ++-
.../Controls/Settings/SettingsUtils.tsx | 4 +
.../SettingsComponent.test.tsx.snap | 1 +
.../SettingsRenderUtils.test.tsx.snap | 42 ++++++---
19 files changed, 249 insertions(+), 156 deletions(-)
create mode 100644 src/Common/dataAccess/getIndexTransformationProgress.ts
create mode 100644 src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.test.tsx
create mode 100644 src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx
create mode 100644 src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/__snapshots__/IndexingPolicyRefreshComponent.test.tsx.snap
diff --git a/src/Common/dataAccess/getIndexTransformationProgress.ts b/src/Common/dataAccess/getIndexTransformationProgress.ts
new file mode 100644
index 000000000..1c08fe847
--- /dev/null
+++ b/src/Common/dataAccess/getIndexTransformationProgress.ts
@@ -0,0 +1,28 @@
+import { client } from "../CosmosClient";
+import { handleError } from "../ErrorHandlingUtils";
+import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
+import * as Constants from "../Constants";
+import { AuthType } from "../../AuthType";
+
+export async function getIndexTransformationProgress(databaseId: string, collectionId: string): Promise {
+ if (window.authType !== AuthType.AAD) {
+ return undefined;
+ }
+ let indexTransformationPercentage: number;
+ const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
+ try {
+ const response = await client()
+ .database(databaseId)
+ .container(collectionId)
+ .read({ populateQuotaInfo: true });
+
+ indexTransformationPercentage = parseInt(
+ response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
+ );
+ } catch (error) {
+ handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
+ throw error;
+ }
+ clearMessage();
+ return indexTransformationPercentage;
+}
diff --git a/src/Common/dataAccess/readMongoDBCollection.tsx b/src/Common/dataAccess/readMongoDBCollection.tsx
index 9d7123bda..80013cf94 100644
--- a/src/Common/dataAccess/readMongoDBCollection.tsx
+++ b/src/Common/dataAccess/readMongoDBCollection.tsx
@@ -2,8 +2,6 @@ import { userContext } from "../../UserContext";
import { getMongoDBCollection } from "../../Utils/arm/generatedClients/2020-04-01/mongoDBResources";
import { MongoDBCollectionResource } from "../../Utils/arm/generatedClients/2020-04-01/types";
import { logConsoleProgress } from "../../Utils/NotificationConsoleUtils";
-import * as Constants from "../Constants";
-import { client } from "../CosmosClient";
import { handleError } from "../ErrorHandlingUtils";
import { AuthType } from "../../AuthType";
@@ -30,29 +28,3 @@ export async function readMongoDBCollectionThroughRP(
clearMessage();
return collection;
}
-
-export async function getMongoDBCollectionIndexTransformationProgress(
- databaseId: string,
- collectionId: string
-): Promise {
- if (window.authType !== AuthType.AAD) {
- return undefined;
- }
- let indexTransformationPercentage: number;
- const clearMessage = logConsoleProgress(`Reading container ${collectionId}`);
- try {
- const response = await client()
- .database(databaseId)
- .container(collectionId)
- .read({ populateQuotaInfo: true });
-
- indexTransformationPercentage = parseInt(
- response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
- );
- } catch (error) {
- handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
- throw error;
- }
- clearMessage();
- return indexTransformationPercentage;
-}
diff --git a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx
index 9d862864c..d4a3b1ec1 100644
--- a/src/Explorer/Controls/Settings/SettingsComponent.test.tsx
+++ b/src/Explorer/Controls/Settings/SettingsComponent.test.tsx
@@ -8,8 +8,8 @@ import * as DataModels from "../../../Contracts/DataModels";
import ko from "knockout";
import { TtlType, isDirty } from "./SettingsUtils";
import Explorer from "../../Explorer";
-jest.mock("../../../Common/dataAccess/readMongoDBCollection", () => ({
- getMongoDBCollectionIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
+jest.mock("../../../Common/dataAccess/getIndexTransformationProgress", () => ({
+ getIndexTransformationProgress: jest.fn().mockReturnValue(undefined)
}));
import { updateCollection, updateMongoDBCollectionThroughRP } from "../../../Common/dataAccess/updateCollection";
jest.mock("../../../Common/dataAccess/updateCollection", () => ({
diff --git a/src/Explorer/Controls/Settings/SettingsComponent.tsx b/src/Explorer/Controls/Settings/SettingsComponent.tsx
index 2966e1cf8..878804b22 100644
--- a/src/Explorer/Controls/Settings/SettingsComponent.tsx
+++ b/src/Explorer/Controls/Settings/SettingsComponent.tsx
@@ -46,10 +46,8 @@ import { Pivot, PivotItem, IPivotProps, IPivotItemProps } from "office-ui-fabric
import "./SettingsComponent.less";
import { IndexingPolicyComponent, IndexingPolicyComponentProps } from "./SettingsSubComponents/IndexingPolicyComponent";
import { MongoDBCollectionResource, MongoIndex } from "../../../Utils/arm/generatedClients/2020-04-01/types";
-import {
- getMongoDBCollectionIndexTransformationProgress,
- readMongoDBCollectionThroughRP
-} from "../../../Common/dataAccess/readMongoDBCollection";
+import { readMongoDBCollectionThroughRP } from "../../../Common/dataAccess/readMongoDBCollection";
+import { getIndexTransformationProgress } from "../../../Common/dataAccess/getIndexTransformationProgress";
import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
interface SettingsV2TabInfo {
@@ -212,6 +210,7 @@ export class SettingsComponent extends React.Component => {
- const currentProgress = await getMongoDBCollectionIndexTransformationProgress(
- this.collection.databaseId,
- this.collection.id()
- );
+ const currentProgress = await getIndexTransformationProgress(this.collection.databaseId, this.collection.id());
this.setState({ indexTransformationProgress: currentProgress });
};
@@ -352,6 +346,7 @@ export class SettingsComponent extends React.Component
);
-export const indexingPolicyTTLWarningMessage: JSX.Element = (
+export const indexingPolicynUnsavedWarningMessage: JSX.Element = (
- Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and
- the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For
- more information see,{" "}
-
- Modifying Indexing Policies
-
- .
+ You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
);
@@ -410,8 +404,8 @@ export const mongoIndexingPolicyAADError: JSX.Element = (
export const mongoIndexTransformationRefreshingMessage: JSX.Element = (
- Refreshing index transformation progress
-
+ Refreshing index transformation progress
+
);
@@ -421,14 +415,14 @@ export const renderMongoIndexTransformationRefreshMessage = (
): JSX.Element => {
if (progress === 0) {
return (
-
+
{"You can make more indexing changes once the current index transformation is complete. "}
{"Refresh to check if it has completed."}
);
} else {
return (
-
+
{`You can make more indexing changes once the current index transformation has completed. It is ${progress}% complete. `}
{"Refresh to check the progress."}
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.test.tsx
index 38d192f6c..a232c6ee4 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.test.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.test.tsx
@@ -25,7 +25,9 @@ describe("IndexingPolicyComponent", () => {
},
onIndexingPolicyDirtyChange: () => {
return;
- }
+ },
+ indexTransformationProgress: undefined,
+ refreshIndexTransformationProgress: () => new Promise(jest.fn())
};
it("renders", () => {
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx
index f1dede13a..992a92b47 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyComponent.tsx
@@ -1,9 +1,10 @@
import * as React from "react";
import * as DataModels from "../../../../Contracts/DataModels";
import * as monaco from "monaco-editor";
-import { isDirty } from "../SettingsUtils";
+import { isDirty, isIndexTransforming } from "../SettingsUtils";
import { MessageBar, MessageBarType, Stack } from "office-ui-fabric-react";
-import { indexingPolicyTTLWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
+import { indexingPolicynUnsavedWarningMessage, titleAndInputStackProps } from "../SettingsRenderUtils";
+import { IndexingPolicyRefreshComponent } from "./IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
export interface IndexingPolicyComponentProps {
shouldDiscardIndexingPolicy: boolean;
@@ -12,6 +13,8 @@ export interface IndexingPolicyComponentProps {
indexingPolicyContentBaseline: DataModels.IndexingPolicy;
onIndexingPolicyContentChange: (newIndexingPolicy: DataModels.IndexingPolicy) => void;
logIndexingPolicySuccessMessage: () => void;
+ indexTransformationProgress: number;
+ refreshIndexTransformationProgress: () => Promise;
onIndexingPolicyDirtyChange: (isIndexingPolicyDirty: boolean) => void;
}
@@ -51,6 +54,9 @@ export class IndexingPolicyComponent extends React.Component<
if (!this.indexingPolicyEditor) {
this.createIndexingPolicyEditor();
} else {
+ this.indexingPolicyEditor.updateOptions({
+ readOnly: isIndexTransforming(this.props.indexTransformationProgress)
+ });
const indexingPolicyEditorModel = this.indexingPolicyEditor.getModel();
const value: string = JSON.stringify(this.props.indexingPolicyContent, undefined, 4);
indexingPolicyEditorModel.setValue(value);
@@ -84,7 +90,7 @@ export class IndexingPolicyComponent extends React.Component<
this.indexingPolicyEditor = monaco.editor.create(this.indexingPolicyDiv.current, {
value: value,
language: "json",
- readOnly: false,
+ readOnly: isIndexTransforming(this.props.indexTransformationProgress),
ariaLabel: "Indexing Policy"
});
if (this.indexingPolicyEditor) {
@@ -108,8 +114,12 @@ export class IndexingPolicyComponent extends React.Component<
public render(): JSX.Element {
return (
+
{isDirty(this.props.indexingPolicyContent, this.props.indexingPolicyContentBaseline) && (
- {indexingPolicyTTLWarningMessage}
+ {indexingPolicynUnsavedWarningMessage}
)}
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.test.tsx
new file mode 100644
index 000000000..2e0da86de
--- /dev/null
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.test.tsx
@@ -0,0 +1,15 @@
+import { shallow } from "enzyme";
+import React from "react";
+import { IndexingPolicyRefreshComponentProps, IndexingPolicyRefreshComponent } from "./IndexingPolicyRefreshComponent";
+
+describe("IndexingPolicyRefreshComponent", () => {
+ it("renders", () => {
+ const props: IndexingPolicyRefreshComponentProps = {
+ indexTransformationProgress: 90,
+ refreshIndexTransformationProgress: () => new Promise(jest.fn())
+ };
+
+ const wrapper = shallow( );
+ expect(wrapper).toMatchSnapshot();
+ });
+});
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx
new file mode 100644
index 000000000..0f497fc22
--- /dev/null
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/IndexingPolicyRefreshComponent.tsx
@@ -0,0 +1,62 @@
+import * as React from "react";
+import { MessageBar, MessageBarType } from "office-ui-fabric-react";
+import {
+ mongoIndexTransformationRefreshingMessage,
+ renderMongoIndexTransformationRefreshMessage
+} from "../../SettingsRenderUtils";
+import { handleError } from "../../../../../Common/ErrorHandlingUtils";
+import { isIndexTransforming } from "../../SettingsUtils";
+
+export interface IndexingPolicyRefreshComponentProps {
+ indexTransformationProgress: number;
+ refreshIndexTransformationProgress: () => Promise;
+}
+
+interface IndexingPolicyRefreshComponentState {
+ isRefreshing: boolean;
+}
+
+export class IndexingPolicyRefreshComponent extends React.Component<
+ IndexingPolicyRefreshComponentProps,
+ IndexingPolicyRefreshComponentState
+> {
+ constructor(props: IndexingPolicyRefreshComponentProps) {
+ super(props);
+ this.state = {
+ isRefreshing: false
+ };
+ }
+
+ private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
+
+ private renderIndexTransformationWarning = (): JSX.Element => {
+ if (this.state.isRefreshing) {
+ return mongoIndexTransformationRefreshingMessage;
+ } else if (isIndexTransforming(this.props.indexTransformationProgress)) {
+ return renderMongoIndexTransformationRefreshMessage(
+ this.props.indexTransformationProgress,
+ this.onClickRefreshIndexingTransformationLink
+ );
+ }
+ return undefined;
+ };
+
+ private refreshIndexTransformationProgress = async () => {
+ this.setState({ isRefreshing: true });
+ try {
+ await this.props.refreshIndexTransformationProgress();
+ } catch (error) {
+ handleError(error, "Refreshing index transformation progress failed.", "RefreshIndexTransformationProgress");
+ } finally {
+ this.setState({ isRefreshing: false });
+ }
+ };
+
+ public render(): JSX.Element {
+ return this.renderIndexTransformationWarning() ? (
+ {this.renderIndexTransformationWarning()}
+ ) : (
+ <>>
+ );
+ }
+}
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/__snapshots__/IndexingPolicyRefreshComponent.test.tsx.snap b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/__snapshots__/IndexingPolicyRefreshComponent.test.tsx.snap
new file mode 100644
index 000000000..1c44dd435
--- /dev/null
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/IndexingPolicyRefresh/__snapshots__/IndexingPolicyRefreshComponent.test.tsx.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`IndexingPolicyRefreshComponent renders 1`] = `
+
+
+ You can make more indexing changes once the current index transformation has completed. It is 90% complete.
+
+ Refresh to check the progress.
+
+
+
+`;
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.test.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.test.tsx
index 246bc8186..95ca5eef1 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.test.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.test.tsx
@@ -2,6 +2,7 @@ import { shallow } from "enzyme";
import React from "react";
import { MongoIndexTypes, MongoNotificationMessage, MongoNotificationType } from "../../SettingsUtils";
import { MongoIndexingPolicyComponent, MongoIndexingPolicyComponentProps } from "./MongoIndexingPolicyComponent";
+import { renderToString } from "react-dom/server";
describe("MongoIndexingPolicyComponent", () => {
const baseProps: MongoIndexingPolicyComponentProps = {
@@ -21,10 +22,7 @@ describe("MongoIndexingPolicyComponent", () => {
return;
},
indexTransformationProgress: undefined,
- refreshIndexTransformationProgress: () =>
- new Promise(() => {
- return;
- }),
+ refreshIndexTransformationProgress: () => new Promise(jest.fn()),
onMongoIndexingPolicySaveableChange: () => {
return;
},
@@ -38,16 +36,6 @@ describe("MongoIndexingPolicyComponent", () => {
expect(wrapper).toMatchSnapshot();
});
- it("isIndexingTransforming", () => {
- const wrapper = shallow( );
- const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
- expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
- wrapper.setProps({ indexTransformationProgress: 50 });
- expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(true);
- wrapper.setProps({ indexTransformationProgress: 100 });
- expect(mongoIndexingPolicyComponent.isIndexingTransforming()).toEqual(false);
- });
-
describe("AddMongoIndexProps test", () => {
const wrapper = shallow( );
const mongoIndexingPolicyComponent = wrapper.instance() as MongoIndexingPolicyComponent;
@@ -55,7 +43,7 @@ describe("MongoIndexingPolicyComponent", () => {
it("defaults", () => {
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicySaveable()).toEqual(false);
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(false);
- expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(undefined);
+ expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
});
const sampleWarning = "sampleWarning";
@@ -113,9 +101,12 @@ describe("MongoIndexingPolicyComponent", () => {
expect(mongoIndexingPolicyComponent.isMongoIndexingPolicyDiscardable()).toEqual(
isMongoIndexingPolicyDiscardable
);
- expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toEqual(
- mongoWarningNotificationMessage
- );
+ if (mongoWarningNotificationMessage) {
+ const elementAsString = renderToString(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage());
+ expect(elementAsString).toContain(mongoWarningNotificationMessage);
+ } else {
+ expect(mongoIndexingPolicyComponent.getMongoWarningNotificationMessage()).toBeUndefined();
+ }
}
);
});
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx
index cc9c25794..435cec265 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/MongoIndexingPolicyComponent.tsx
@@ -25,8 +25,8 @@ import {
createAndAddMongoIndexStackProps,
separatorStyles,
mongoIndexingPolicyAADError,
- mongoIndexTransformationRefreshingMessage,
- renderMongoIndexTransformationRefreshMessage
+ indexingPolicynUnsavedWarningMessage,
+ infoAndToolTipTextStyle
} from "../../SettingsRenderUtils";
import { MongoIndex } from "../../../../../Utils/arm/generatedClients/2020-04-01/types";
import {
@@ -35,12 +35,13 @@ import {
MongoIndexIdField,
MongoNotificationType,
getMongoIndexType,
- getMongoIndexTypeText
+ getMongoIndexTypeText,
+ isIndexTransforming
} from "../../SettingsUtils";
import { AddMongoIndexComponent } from "./AddMongoIndexComponent";
import { CollapsibleSectionComponent } from "../../../CollapsiblePanel/CollapsibleSectionComponent";
-import { handleError } from "../../../../../Common/ErrorHandlingUtils";
import { AuthType } from "../../../../../AuthType";
+import { IndexingPolicyRefreshComponent } from "../IndexingPolicyRefresh/IndexingPolicyRefreshComponent";
export interface MongoIndexingPolicyComponentProps {
mongoIndexes: MongoIndex[];
@@ -56,20 +57,13 @@ export interface MongoIndexingPolicyComponentProps {
onMongoIndexingPolicyDiscardableChange: (isMongoIndexingPolicyDiscardable: boolean) => void;
}
-interface MongoIndexingPolicyComponentState {
- isRefreshingIndexTransformationProgress: boolean;
-}
-
interface MongoIndexDisplayProps {
definition: JSX.Element;
type: JSX.Element;
actionButton: JSX.Element;
}
-export class MongoIndexingPolicyComponent extends React.Component<
- MongoIndexingPolicyComponentProps,
- MongoIndexingPolicyComponentState
-> {
+export class MongoIndexingPolicyComponent extends React.Component {
private shouldCheckComponentIsDirty = true;
private addMongoIndexComponentRefs: React.RefObject[] = [];
private initialIndexesColumns: IColumn[] = [
@@ -98,13 +92,6 @@ export class MongoIndexingPolicyComponent extends React.Component<
}
];
- constructor(props: MongoIndexingPolicyComponentProps) {
- super(props);
- this.state = {
- isRefreshingIndexTransformationProgress: false
- };
- }
-
componentDidUpdate(prevProps: MongoIndexingPolicyComponentProps): void {
if (this.props.indexesToAdd.length > prevProps.indexesToAdd.length) {
this.addMongoIndexComponentRefs[prevProps.indexesToAdd.length]?.current?.focus();
@@ -144,10 +131,15 @@ export class MongoIndexingPolicyComponent extends React.Component<
return this.props.indexesToAdd.length > 0 || this.props.indexesToDrop.length > 0;
};
- public getMongoWarningNotificationMessage = (): string => {
- return this.props.indexesToAdd.find(
+ public getMongoWarningNotificationMessage = (): JSX.Element => {
+ const warningMessage = this.props.indexesToAdd.find(
addMongoIndexProps => addMongoIndexProps.notification?.type === MongoNotificationType.Warning
)?.notification.message;
+
+ if (warningMessage) {
+ return {warningMessage} ;
+ }
+ return undefined;
};
private onRenderRow = (props: IDetailsRowProps): JSX.Element => {
@@ -159,7 +151,7 @@ export class MongoIndexingPolicyComponent extends React.Component<
{
this.props.onIndexDrop(arrayPosition);
}}
@@ -230,7 +222,7 @@ export class MongoIndexingPolicyComponent extends React.Component<
{
- this.setState({ isRefreshingIndexTransformationProgress: true });
- try {
- await this.props.refreshIndexTransformationProgress();
- } catch (error) {
- handleError(error, "Refreshing index transformation progress failed.", "RefreshIndexTransformationProgress");
- } finally {
- this.setState({ isRefreshingIndexTransformationProgress: false });
- }
- };
-
- public isIndexingTransforming = (): boolean =>
- // index transformation progress can be 0
- this.props.indexTransformationProgress !== undefined && this.props.indexTransformationProgress !== 100;
-
- private onClickRefreshIndexingTransformationLink = async () => await this.refreshIndexTransformationProgress();
-
- private renderIndexTransformationWarning = (): JSX.Element => {
- if (this.state.isRefreshingIndexTransformationProgress) {
- return mongoIndexTransformationRefreshingMessage;
- } else if (this.isIndexingTransforming()) {
- return renderMongoIndexTransformationRefreshMessage(
- this.props.indexTransformationProgress,
- this.onClickRefreshIndexingTransformationLink
- );
- }
- return undefined;
- };
-
private renderWarningMessage = (): JSX.Element => {
- let warningMessage: string;
+ let warningMessage: JSX.Element;
if (this.getMongoWarningNotificationMessage()) {
warningMessage = this.getMongoWarningNotificationMessage();
} else if (this.isMongoIndexingPolicySaveable()) {
- warningMessage =
- "You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.";
+ warningMessage = indexingPolicynUnsavedWarningMessage;
}
return (
<>
- {this.renderIndexTransformationWarning() && (
- {this.renderIndexTransformationWarning()}
- )}
-
- {warningMessage && (
-
- {warningMessage}
-
- )}
+
+ {warningMessage && {warningMessage} }
>
);
};
diff --git a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/__snapshots__/MongoIndexingPolicyComponent.test.tsx.snap b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/__snapshots__/MongoIndexingPolicyComponent.test.tsx.snap
index ab19cbb45..577fa2862 100644
--- a/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/__snapshots__/MongoIndexingPolicyComponent.test.tsx.snap
+++ b/src/Explorer/Controls/Settings/SettingsSubComponents/MongoIndexingPolicy/__snapshots__/MongoIndexingPolicyComponent.test.tsx.snap
@@ -8,6 +8,9 @@ exports[`MongoIndexingPolicyComponent renders 1`] = `
}
}
>
+
For queries that filter on multiple properties, create multiple single field indexes instead of a compound index.
+
{
expect(notification.type).toEqual(MongoNotificationType.Error);
});
});
+
+it("isIndexingTransforming", () => {
+ expect(isIndexTransforming(undefined)).toBeFalsy();
+ expect(isIndexTransforming(0)).toBeTruthy();
+ expect(isIndexTransforming(90)).toBeTruthy();
+ expect(isIndexTransforming(100)).toBeFalsy();
+});
diff --git a/src/Explorer/Controls/Settings/SettingsUtils.tsx b/src/Explorer/Controls/Settings/SettingsUtils.tsx
index 556df274f..9e29b73fe 100644
--- a/src/Explorer/Controls/Settings/SettingsUtils.tsx
+++ b/src/Explorer/Controls/Settings/SettingsUtils.tsx
@@ -250,3 +250,7 @@ export const getMongoIndexTypeText = (index: MongoIndexTypes): string => {
}
return WildcardText;
};
+
+export const isIndexTransforming = (indexTransformationProgress: number): boolean =>
+ // index transformation progress can be 0
+ indexTransformationProgress !== undefined && indexTransformationProgress !== 100;
diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap
index 1a0a9747b..db28fa5a8 100644
--- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap
+++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap
@@ -5189,6 +5189,7 @@ exports[`SettingsComponent renders 1`] = `
logIndexingPolicySuccessMessage={[Function]}
onIndexingPolicyContentChange={[Function]}
onIndexingPolicyDirtyChange={[Function]}
+ refreshIndexTransformationProgress={[Function]}
resetShouldDiscardIndexingPolicy={[Function]}
shouldDiscardIndexingPolicy={false}
/>
diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap
index 77b4fc444..90e5c8af8 100644
--- a/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap
+++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsRenderUtils.test.tsx.snap
@@ -166,15 +166,7 @@ exports[`SettingsUtils functions render 1`] = `
}
}
>
- Changing the Indexing Policy impacts query results while the index transformation occurs. When a change is made and the indexing mode is set to consistent or lazy, queries return eventual results until the operation completes. For more information see,
-
-
- Modifying Indexing Policies
-
- .
+ You have not saved the latest changes made to your indexing policy. Please click save to confirm the changes.
-
+
Refreshing index transformation progress
-
+
You can make more indexing changes once the current index transformation is complete.
-
+
You can make more indexing changes once the current index transformation has completed. It is 90% complete.
Date: Mon, 2 Nov 2020 16:59:08 -0800
Subject: [PATCH 04/13] Poll on Location header for operation status (#309)
---
src/Utils/arm/request.test.ts | 10 +++++-----
src/Utils/arm/request.ts | 36 ++++++++---------------------------
2 files changed, 13 insertions(+), 33 deletions(-)
diff --git a/src/Utils/arm/request.test.ts b/src/Utils/arm/request.test.ts
index 69f0be223..89b6d488a 100644
--- a/src/Utils/arm/request.test.ts
+++ b/src/Utils/arm/request.test.ts
@@ -21,13 +21,12 @@ describe("ARM request", () => {
it("should poll for async operations", async () => {
const headers = new Headers();
- headers.set("azure-asyncoperation", "https://foo.com/operationStatus");
+ headers.set("location", "https://foo.com/operationStatus");
window.fetch = jest.fn().mockResolvedValue({
ok: true,
headers,
- json: async () => {
- return { status: "Succeeded" };
- }
+ status: 200,
+ json: async () => ({})
});
await armRequest({ apiVersion: "2001-01-01", host: "https://foo.com", path: "foo", method: "GET" });
expect(window.fetch).toHaveBeenCalledTimes(2);
@@ -35,10 +34,11 @@ describe("ARM request", () => {
it("should throw for failed async operations", async () => {
const headers = new Headers();
- headers.set("azure-asyncoperation", "https://foo.com/operationStatus");
+ headers.set("location", "https://foo.com/operationStatus");
window.fetch = jest.fn().mockResolvedValue({
ok: true,
headers,
+ status: 200,
json: async () => {
return { status: "Failed" };
}
diff --git a/src/Utils/arm/request.ts b/src/Utils/arm/request.ts
index d41803e37..e40e38b8f 100644
--- a/src/Utils/arm/request.ts
+++ b/src/Utils/arm/request.ts
@@ -70,55 +70,35 @@ export async function armRequest({ host, path, apiVersion, method, body: requ
throw error;
}
- const operationStatusUrl = response.headers && response.headers.get("azure-asyncoperation");
+ const operationStatusUrl = response.headers && response.headers.get("location");
if (operationStatusUrl) {
- await promiseRetry(() => getOperationStatus(operationStatusUrl));
- // TODO: ARM is supposed to return a resourceLocation property, but it does not https://github.com/microsoft/api-guidelines/blob/vNext/Guidelines.md#target-resource-location
- // When Cosmos RP adds resourceLocation, we should use it instead
- // For now manually execute a GET if the operation was a mutation and not a deletion
- if (method === "POST" || method === "PATCH" || method === "PUT") {
- return armRequest({
- host,
- path,
- apiVersion,
- method: "GET"
- });
- }
+ return await promiseRetry(() => getOperationStatus(operationStatusUrl));
}
const responseBody = (await response.json()) as T;
return responseBody;
}
-const SUCCEEDED = "Succeeded" as const;
-const FAILED = "Failed" as const;
-const CANCELED = "Canceled" as const;
-
-type Status = typeof SUCCEEDED | typeof FAILED | typeof CANCELED;
-
-interface OperationResponse {
- status: Status;
- error: unknown;
-}
-
async function getOperationStatus(operationStatusUrl: string) {
const response = await window.fetch(operationStatusUrl, {
headers: {
Authorization: userContext.authorizationToken
}
});
+
if (!response.ok) {
const errorResponse = (await response.json()) as ErrorResponse;
const error = new Error(errorResponse.message) as ARMError;
error.code = errorResponse.code;
throw new AbortError(error);
}
- const body = (await response.json()) as OperationResponse;
+
+ const body = await response.json();
const status = body.status;
- if (status === SUCCEEDED) {
- return;
+ 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 error = new Error(errorMessage);
throw new AbortError(error);
From a009a8ba5f1e0a4b96e3a2f0b102c0b91667968f Mon Sep 17 00:00:00 2001
From: Zachary Foster
Date: Tue, 3 Nov 2020 14:05:54 -0500
Subject: [PATCH 05/13] Adds e2e readme and new endpoint env var (#314)
---
.env.example | 7 +++++--
.github/workflows/ci.yml | 1 +
README.md | 15 +++++++++------
test/utils/shared.ts | 2 +-
4 files changed, 16 insertions(+), 9 deletions(-)
diff --git a/.env.example b/.env.example
index b37aeff49..fdf920188 100644
--- a/.env.example
+++ b/.env.example
@@ -1,7 +1,10 @@
-# These options are only needed when if running end to end tests locally
PORTAL_RUNNER_USERNAME=
PORTAL_RUNNER_PASSWORD=
PORTAL_RUNNER_SUBSCRIPTION=
PORTAL_RUNNER_RESOURCE_GROUP=
PORTAL_RUNNER_DATABASE_ACCOUNT=
-PORTAL_RUNNER_CONNECTION_STRING=
\ No newline at end of file
+PORTAL_RUNNER_CONNECTION_STRING=
+CASSANDRA_CONNECTION_STRING=
+MONGO_CONNECTION_STRING=
+TABLES_CONNECTION_STRING=
+DATA_EXPLORER_ENDPOINT=https://localhost:1234/hostedExplorer.html
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 53a39150a..b04588330 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -151,6 +151,7 @@ jobs:
MONGO_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
CASSANDRA_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_CASSANDRA }}
TABLES_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_TABLE }}
+ DATA_EXPLORER_ENDPOINT: "https://localhost:1234/hostedExplorer.html"
- uses: actions/upload-artifact@v2
with:
name: screenshots
diff --git a/README.md b/README.md
index 791985365..f9a05488f 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ To run pure hosted mode, in `webpack.config.js` change index HtmlWebpackPlugin t
### Emulator Development
-In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows enironment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
+In a window environment, running `npm run build` will automatically copy the built files from `/dist` over to the default emulator install paths. In a non-windows environment you can specify an alternate endpoint using `EMULATOR_ENDPOINT` and webpack dev server will proxy requests for you.
`PLATFORM=Emulator EMULATOR_ENDPOINT=https://my-vm.azure.com:8081 npm run watch`
@@ -60,7 +60,7 @@ The Cosmos Portal that consumes this repo is not currently open source. If you h
You can however load a local running instance of data explorer in the production portal.
1. Turn off browser SSL validation for localhost: chrome://flags/#allow-insecure-localhost OR Install valid SSL certs for localhost (on IE, follow these [instructions](https://www.technipages.com/ie-bypass-problem-with-this-websites-security-certificate) to install the localhost certificate in the right place)
-2. Whitelist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
+2. Allowlist `https://localhost:1234` domain for CORS in the Azure Cosmos DB portal
3. Start the project in portal mode: `PLATFORM=Portal npm run watch`
4. Load the portal using the following link: https://ms.portal.azure.com/?dataExplorerSource=https%3A%2F%2Flocalhost%3A1234%2Fexplorer.html
@@ -84,16 +84,19 @@ Unit tests are located adjacent to the code under test and run with [Jest](https
4. Install dependencies: `npm install`
5. Run cypress headless(`npm run test`) or in interactive mode(`npm run test:debug`)
-#### End to End Production Runners
+#### End to End Production Tests
Jest and Puppeteer are used for end to end production runners and are contained in `test/`. To run these tests locally:
-1. Copy .env.example to .env and fill in all variables
-2. Run `npm run test:e2e`
+1. Copy .env.example to .env
+2. Update the values in .env including your local data explorer endpoint (ask a teammate/codeowner for help with .env values)
+3. Make sure all packages are installed `npm install`
+4. Run the server `npm run start` and wait for it to start
+5. Run `npm run test:e2e`
### Releasing
-We generally adhear to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
+We generally adhere to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
# Contributing
diff --git a/test/utils/shared.ts b/test/utils/shared.ts
index f7c1f3faf..352d14a6c 100644
--- a/test/utils/shared.ts
+++ b/test/utils/shared.ts
@@ -2,7 +2,7 @@ import crypto from "crypto";
import { Frame } from "puppeteer";
export async function login(connectionString: string): Promise {
- const prodUrl = "https://localhost:1234/hostedExplorer.html";
+ const prodUrl = process.env.DATA_EXPLORER_ENDPOINT;
page.goto(prodUrl, { waitUntil: "networkidle2" });
// log in with connection string
From 5f1f7a826654ca2d7081e4d52c5e954eb1f7e29a Mon Sep 17 00:00:00 2001
From: victor-meng <56978073+victor-meng@users.noreply.github.com>
Date: Tue, 3 Nov 2020 13:40:44 -0800
Subject: [PATCH 06/13] Refactor error handling part 2 (#313)
---
src/Common/DocumentClientUtilityBase.ts | 16 +--
src/Common/ErrorHandlingUtils.ts | 20 +--
src/Common/Logger.ts | 6 +-
src/Common/QueriesClient.ts | 15 +--
src/Common/dataAccess/createCollection.ts | 2 +-
src/Common/dataAccess/createDatabase.ts | 2 +-
.../dataAccess/createStoredProcedure.ts | 2 +-
src/Common/dataAccess/createTrigger.ts | 2 +-
.../dataAccess/createUserDefinedFunction.ts | 4 +-
src/Common/dataAccess/deleteCollection.ts | 2 +-
src/Common/dataAccess/deleteDatabase.ts | 2 +-
.../dataAccess/deleteStoredProcedure.ts | 2 +-
src/Common/dataAccess/deleteTrigger.ts | 2 +-
.../dataAccess/deleteUserDefinedFunction.ts | 2 +-
.../getIndexTransformationProgress.ts | 2 +-
src/Common/dataAccess/readCollection.ts | 2 +-
src/Common/dataAccess/readCollectionOffer.ts | 2 +-
.../dataAccess/readCollectionQuotaInfo.ts | 2 +-
src/Common/dataAccess/readCollections.ts | 2 +-
src/Common/dataAccess/readDatabaseOffer.ts | 2 +-
src/Common/dataAccess/readDatabases.ts | 2 +-
.../dataAccess/readMongoDBCollection.tsx | 2 +-
src/Common/dataAccess/readOffers.ts | 2 +-
src/Common/dataAccess/readStoredProcedures.ts | 2 +-
src/Common/dataAccess/readTriggers.ts | 2 +-
.../dataAccess/readUserDefinedFunctions.ts | 4 +-
src/Common/dataAccess/updateCollection.ts | 2 +-
src/Common/dataAccess/updateOffer.ts | 2 +-
.../updateOfferThroughputBeyondLimit.ts | 4 +-
.../dataAccess/updateStoredProcedure.ts | 2 +-
src/Common/dataAccess/updateTrigger.ts | 2 +-
.../dataAccess/updateUserDefinedFunction.ts | 4 +-
src/Contracts/Diagnostics.ts | 2 +-
.../Controls/Arcadia/ArcadiaMenuPicker.tsx | 9 +-
.../Notebook/NotebookTerminalComponent.tsx | 11 +-
.../CodeOfConductComponent.tsx | 7 +-
.../GalleryViewerComponent.tsx | 20 +--
.../NotebookViewerComponent.tsx | 5 +-
.../IndexingPolicyRefreshComponent.tsx | 2 +-
src/Explorer/Explorer.ts | 53 ++++----
.../GremlinClient.test.ts | 6 +-
.../GraphExplorerComponent/GremlinClient.ts | 32 ++---
.../Notebook/NotebookContainerClient.ts | 7 +-
src/Explorer/Notebook/NotebookManager.ts | 3 +-
src/Explorer/Panes/CopyNotebookPane.tsx | 14 +--
src/Explorer/Panes/GitHubReposPane.ts | 24 +---
.../Panes/PublishNotebookPaneAdapter.tsx | 24 ++--
src/Explorer/Tables/TableDataClient.ts | 114 +++++-------------
src/GitHub/GitHubClient.ts | 11 +-
src/GitHub/GitHubContentProvider.ts | 18 +--
src/GitHub/GitHubOAuthService.ts | 6 +-
src/HostedExplorer.ts | 8 +-
.../NotebookWorkspaceManager.ts | 13 +-
src/Platform/Hosted/ArmResourceUtils.ts | 11 +-
.../ArcadiaResourceManager.ts | 10 +-
src/Utils/AuthorizationUtils.ts | 3 +-
src/Utils/GalleryUtils.ts | 26 ++--
src/Utils/NotebookConfigurationUtils.ts | 5 +-
58 files changed, 229 insertions(+), 336 deletions(-)
diff --git a/src/Common/DocumentClientUtilityBase.ts b/src/Common/DocumentClientUtilityBase.ts
index bc5e3ed33..ded90cf05 100644
--- a/src/Common/DocumentClientUtilityBase.ts
+++ b/src/Common/DocumentClientUtilityBase.ts
@@ -58,8 +58,8 @@ export function executeStoredProcedure(
(error: any) => {
handleError(
error,
- `Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`,
- "ExecuteStoredProcedure"
+ "ExecuteStoredProcedure",
+ `Failed to execute stored procedure ${storedProcedure.id()} for container ${storedProcedure.collection.id()}`
);
deferred.reject(error);
}
@@ -88,7 +88,7 @@ export function queryDocumentsPage(
deferred.resolve(result);
},
(error: any) => {
- handleError(error, `Failed to query ${entityName} for container ${resourceName}`, "QueryDocumentsPage");
+ handleError(error, "QueryDocumentsPage", `Failed to query ${entityName} for container ${resourceName}`);
deferred.reject(error);
}
)
@@ -109,7 +109,7 @@ export function readDocument(collection: ViewModels.CollectionBase, documentId:
deferred.resolve(document);
},
(error: any) => {
- handleError(error, `Failed to read ${entityName} ${documentId.id()}`, "ReadDocument");
+ handleError(error, "ReadDocument", `Failed to read ${entityName} ${documentId.id()}`);
deferred.reject(error);
}
)
@@ -135,7 +135,7 @@ export function updateDocument(
deferred.resolve(updatedDocument);
},
(error: any) => {
- handleError(error, `Failed to update ${entityName} ${documentId.id()}`, "UpdateDocument");
+ handleError(error, "UpdateDocument", `Failed to update ${entityName} ${documentId.id()}`);
deferred.reject(error);
}
)
@@ -157,7 +157,7 @@ export function createDocument(collection: ViewModels.CollectionBase, newDocumen
deferred.resolve(savedDocument);
},
(error: any) => {
- handleError(error, `Error while creating new ${entityName} for container ${collection.id()}`, "CreateDocument");
+ handleError(error, "CreateDocument", `Error while creating new ${entityName} for container ${collection.id()}`);
deferred.reject(error);
}
)
@@ -179,7 +179,7 @@ export function deleteDocument(collection: ViewModels.CollectionBase, documentId
deferred.resolve(response);
},
(error: any) => {
- handleError(error, `Error while deleting ${entityName} ${documentId.id()}`, "DeleteDocument");
+ handleError(error, "DeleteDocument", `Error while deleting ${entityName} ${documentId.id()}`);
deferred.reject(error);
}
)
@@ -205,7 +205,7 @@ export function deleteConflict(
deferred.resolve(response);
},
(error: any) => {
- handleError(error, `Error while deleting conflict ${conflictId.id()}`, "DeleteConflict");
+ handleError(error, "DeleteConflict", `Error while deleting conflict ${conflictId.id()}`);
deferred.reject(error);
}
)
diff --git a/src/Common/ErrorHandlingUtils.ts b/src/Common/ErrorHandlingUtils.ts
index e5acc6b49..2e2f51365 100644
--- a/src/Common/ErrorHandlingUtils.ts
+++ b/src/Common/ErrorHandlingUtils.ts
@@ -1,3 +1,4 @@
+import { ARMError } from "../Utils/arm/request";
import { HttpStatusCodes } from "./Constants";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { SubscriptionType } from "../Contracts/ViewModels";
@@ -5,28 +6,27 @@ import { logConsoleError } from "../Utils/NotificationConsoleUtils";
import { logError } from "./Logger";
import { sendMessage } from "./MessageHandler";
-export interface CosmosError {
- code: number;
- message?: string;
-}
-
-export const handleError = (error: string | CosmosError, consoleErrorPrefix: string, area: string): void => {
+export const handleError = (error: string | ARMError | Error, area: string, consoleErrorPrefix?: string): void => {
const errorMessage = getErrorMessage(error);
- const errorCode = typeof error === "string" ? undefined : error.code;
+ const errorCode = error instanceof ARMError ? error.code : undefined;
+
// logs error to data explorer console
- logConsoleError(`${consoleErrorPrefix}:\n ${errorMessage}`);
+ const consoleErrorMessage = consoleErrorPrefix ? `${consoleErrorPrefix}:\n ${errorMessage}` : errorMessage;
+ logConsoleError(consoleErrorMessage);
+
// logs error to both app insight and kusto
logError(errorMessage, area, errorCode);
+
// checks for errors caused by firewall and sends them to portal to handle
sendNotificationForError(errorMessage, errorCode);
};
-export const getErrorMessage = (error: string | CosmosError | Error): string => {
+export const getErrorMessage = (error: string | Error): string => {
const errorMessage = typeof error === "string" ? error : error.message;
return replaceKnownError(errorMessage);
};
-const sendNotificationForError = (errorMessage: string, errorCode: number): void => {
+const sendNotificationForError = (errorMessage: string, errorCode: number | string): void => {
if (errorCode === HttpStatusCodes.Forbidden) {
if (errorMessage?.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
return;
diff --git a/src/Common/Logger.ts b/src/Common/Logger.ts
index 39b3196c5..58186d21e 100644
--- a/src/Common/Logger.ts
+++ b/src/Common/Logger.ts
@@ -1,4 +1,3 @@
-import { CosmosError, getErrorMessage } from "./ErrorHandlingUtils";
import { sendMessage } from "./MessageHandler";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { appInsights } from "../Shared/appInsights";
@@ -22,8 +21,7 @@ export function logWarning(message: string, area: string, code?: number): void {
return _logEntry(entry);
}
-export function logError(error: string | CosmosError | Error, area: string, code?: number): void {
- const errorMessage: string = getErrorMessage(error);
+export function logError(errorMessage: string, area: string, code?: number | string): void {
const entry: Diagnostics.LogEntry = _generateLogEntry(Diagnostics.LogEntryLevel.Error, errorMessage, area, code);
return _logEntry(entry);
}
@@ -55,7 +53,7 @@ function _generateLogEntry(
level: Diagnostics.LogEntryLevel,
message: string,
area: string,
- code?: number
+ code?: number | string
): Diagnostics.LogEntry {
return {
timestamp: new Date().getUTCSeconds(),
diff --git a/src/Common/QueriesClient.ts b/src/Common/QueriesClient.ts
index 7a3106890..fb81d1c65 100644
--- a/src/Common/QueriesClient.ts
+++ b/src/Common/QueriesClient.ts
@@ -14,7 +14,6 @@ import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } fr
import { createCollection } from "./dataAccess/createCollection";
import { handleError } from "./ErrorHandlingUtils";
import * as ErrorParserUtility from "./ErrorParserUtility";
-import * as Logger from "./Logger";
export class QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = {
@@ -54,7 +53,7 @@ export class QueriesClient {
return Promise.resolve(collection);
},
(error: any) => {
- handleError(error, "Failed to set up account for saving queries", "setupQueriesCollection");
+ handleError(error, "setupQueriesCollection", "Failed to set up account for saving queries");
return Promise.reject(error);
}
)
@@ -105,11 +104,7 @@ export class QueriesClient {
} else {
errorMessage = parsedError.message;
}
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Failed to save query ${query.queryName}: ${errorMessage}`
- );
- Logger.logError(JSON.stringify(parsedError), "saveQuery");
+ handleError(errorMessage, "saveQuery", `Failed to save query ${query.queryName}`);
return Promise.reject(errorMessage);
}
)
@@ -159,14 +154,14 @@ export class QueriesClient {
return Promise.resolve(queries);
},
(error: any) => {
- handleError(error, "Failed to fetch saved queries", "getSavedQueries");
+ handleError(error, "getSavedQueries", "Failed to fetch saved queries");
return Promise.reject(error);
}
);
},
(error: any) => {
// should never get into this state but we handle this regardless
- handleError(error, "Failed to fetch saved queries", "getSavedQueries");
+ handleError(error, "getSavedQueries", "Failed to fetch saved queries");
return Promise.reject(error);
}
)
@@ -218,7 +213,7 @@ export class QueriesClient {
return Promise.resolve();
},
(error: any) => {
- handleError(error, `Failed to delete query ${query.queryName}`, "deleteQuery");
+ handleError(error, "deleteQuery", `Failed to delete query ${query.queryName}`);
return Promise.reject(error);
}
)
diff --git a/src/Common/dataAccess/createCollection.ts b/src/Common/dataAccess/createCollection.ts
index 6209a4b86..97a0c2a62 100644
--- a/src/Common/dataAccess/createCollection.ts
+++ b/src/Common/dataAccess/createCollection.ts
@@ -55,7 +55,7 @@ export const createCollection = async (params: DataModels.CreateCollectionParams
logConsoleInfo(`Successfully created container ${params.collectionId}`);
return collection;
} catch (error) {
- handleError(error, `Error while creating container ${params.collectionId}`, "CreateCollection");
+ handleError(error, "CreateCollection", `Error while creating container ${params.collectionId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/createDatabase.ts b/src/Common/dataAccess/createDatabase.ts
index 22162e071..6f555b324 100644
--- a/src/Common/dataAccess/createDatabase.ts
+++ b/src/Common/dataAccess/createDatabase.ts
@@ -41,7 +41,7 @@ export async function createDatabase(params: DataModels.CreateDatabaseParams): P
logConsoleInfo(`Successfully created database ${params.databaseId}`);
return database;
} catch (error) {
- handleError(error, `Error while creating database ${params.databaseId}`, "CreateDatabase");
+ handleError(error, "CreateDatabase", `Error while creating database ${params.databaseId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/createStoredProcedure.ts b/src/Common/dataAccess/createStoredProcedure.ts
index a2281c213..d8f344ea3 100644
--- a/src/Common/dataAccess/createStoredProcedure.ts
+++ b/src/Common/dataAccess/createStoredProcedure.ts
@@ -70,7 +70,7 @@ export async function createStoredProcedure(
.scripts.storedProcedures.create(storedProcedure);
return response?.resource;
} catch (error) {
- handleError(error, `Error while creating stored procedure ${storedProcedure.id}`, "CreateStoredProcedure");
+ handleError(error, "CreateStoredProcedure", `Error while creating stored procedure ${storedProcedure.id}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/createTrigger.ts b/src/Common/dataAccess/createTrigger.ts
index 179d0103e..a8c3f448f 100644
--- a/src/Common/dataAccess/createTrigger.ts
+++ b/src/Common/dataAccess/createTrigger.ts
@@ -65,7 +65,7 @@ export async function createTrigger(
.scripts.triggers.create(trigger);
return response.resource;
} catch (error) {
- handleError(error, `Error while creating trigger ${trigger.id}`, "CreateTrigger");
+ handleError(error, "CreateTrigger", `Error while creating trigger ${trigger.id}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/createUserDefinedFunction.ts b/src/Common/dataAccess/createUserDefinedFunction.ts
index 30ff8be3a..bda4f0654 100644
--- a/src/Common/dataAccess/createUserDefinedFunction.ts
+++ b/src/Common/dataAccess/createUserDefinedFunction.ts
@@ -72,8 +72,8 @@ export async function createUserDefinedFunction(
} catch (error) {
handleError(
error,
- `Error while creating user defined function ${userDefinedFunction.id}`,
- "CreateUserupdateUserDefinedFunction"
+ "CreateUserupdateUserDefinedFunction",
+ `Error while creating user defined function ${userDefinedFunction.id}`
);
throw error;
} finally {
diff --git a/src/Common/dataAccess/deleteCollection.ts b/src/Common/dataAccess/deleteCollection.ts
index cbe91b70f..5a5c4fd7d 100644
--- a/src/Common/dataAccess/deleteCollection.ts
+++ b/src/Common/dataAccess/deleteCollection.ts
@@ -23,7 +23,7 @@ export async function deleteCollection(databaseId: string, collectionId: string)
}
logConsoleInfo(`Successfully deleted container ${collectionId}`);
} catch (error) {
- handleError(error, `Error while deleting container ${collectionId}`, "DeleteCollection");
+ handleError(error, "DeleteCollection", `Error while deleting container ${collectionId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/deleteDatabase.ts b/src/Common/dataAccess/deleteDatabase.ts
index e48f4ceb4..40029c0d2 100644
--- a/src/Common/dataAccess/deleteDatabase.ts
+++ b/src/Common/dataAccess/deleteDatabase.ts
@@ -25,7 +25,7 @@ export async function deleteDatabase(databaseId: string): Promise {
}
logConsoleInfo(`Successfully deleted database ${databaseId}`);
} catch (error) {
- handleError(error, `Error while deleting database ${databaseId}`, "DeleteDatabase");
+ handleError(error, "DeleteDatabase", `Error while deleting database ${databaseId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/deleteStoredProcedure.ts b/src/Common/dataAccess/deleteStoredProcedure.ts
index 317fb3167..fac47de33 100644
--- a/src/Common/dataAccess/deleteStoredProcedure.ts
+++ b/src/Common/dataAccess/deleteStoredProcedure.ts
@@ -34,7 +34,7 @@ export async function deleteStoredProcedure(
.delete();
}
} catch (error) {
- handleError(error, `Error while deleting stored procedure ${storedProcedureId}`, "DeleteStoredProcedure");
+ handleError(error, "DeleteStoredProcedure", `Error while deleting stored procedure ${storedProcedureId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/deleteTrigger.ts b/src/Common/dataAccess/deleteTrigger.ts
index 0ec6ef4e1..f8a5713db 100644
--- a/src/Common/dataAccess/deleteTrigger.ts
+++ b/src/Common/dataAccess/deleteTrigger.ts
@@ -30,7 +30,7 @@ export async function deleteTrigger(databaseId: string, collectionId: string, tr
.delete();
}
} catch (error) {
- handleError(error, `Error while deleting trigger ${triggerId}`, "DeleteTrigger");
+ handleError(error, "DeleteTrigger", `Error while deleting trigger ${triggerId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/deleteUserDefinedFunction.ts b/src/Common/dataAccess/deleteUserDefinedFunction.ts
index 83630169e..6160ac52c 100644
--- a/src/Common/dataAccess/deleteUserDefinedFunction.ts
+++ b/src/Common/dataAccess/deleteUserDefinedFunction.ts
@@ -30,7 +30,7 @@ export async function deleteUserDefinedFunction(databaseId: string, collectionId
.delete();
}
} catch (error) {
- handleError(error, `Error while deleting user defined function ${id}`, "DeleteUserDefinedFunction");
+ handleError(error, "DeleteUserDefinedFunction", `Error while deleting user defined function ${id}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/getIndexTransformationProgress.ts b/src/Common/dataAccess/getIndexTransformationProgress.ts
index 1c08fe847..fa0298fbc 100644
--- a/src/Common/dataAccess/getIndexTransformationProgress.ts
+++ b/src/Common/dataAccess/getIndexTransformationProgress.ts
@@ -20,7 +20,7 @@ export async function getIndexTransformationProgress(databaseId: string, collect
response.headers[Constants.HttpHeaders.collectionIndexTransformationProgress] as string
);
} catch (error) {
- handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
+ handleError(error, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
throw error;
}
clearMessage();
diff --git a/src/Common/dataAccess/readCollection.ts b/src/Common/dataAccess/readCollection.ts
index 0b434ba03..ce886328d 100644
--- a/src/Common/dataAccess/readCollection.ts
+++ b/src/Common/dataAccess/readCollection.ts
@@ -13,7 +13,7 @@ export async function readCollection(databaseId: string, collectionId: string):
.read();
collection = response.resource as DataModels.Collection;
} catch (error) {
- handleError(error, `Error while querying container ${collectionId}`, "ReadCollection");
+ handleError(error, "ReadCollection", `Error while querying container ${collectionId}`);
throw error;
}
clearMessage();
diff --git a/src/Common/dataAccess/readCollectionOffer.ts b/src/Common/dataAccess/readCollectionOffer.ts
index ad4c8eef5..b98da68c6 100644
--- a/src/Common/dataAccess/readCollectionOffer.ts
+++ b/src/Common/dataAccess/readCollectionOffer.ts
@@ -56,7 +56,7 @@ export const readCollectionOffer = async (
}
);
} catch (error) {
- handleError(error, `Error while querying offer for collection ${params.collectionId}`, "ReadCollectionOffer");
+ handleError(error, "ReadCollectionOffer", `Error while querying offer for collection ${params.collectionId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/readCollectionQuotaInfo.ts b/src/Common/dataAccess/readCollectionQuotaInfo.ts
index 574913f06..81565f821 100644
--- a/src/Common/dataAccess/readCollectionQuotaInfo.ts
+++ b/src/Common/dataAccess/readCollectionQuotaInfo.ts
@@ -37,7 +37,7 @@ export const readCollectionQuotaInfo = async (
return quota;
} catch (error) {
- handleError(error, `Error while querying quota info for container ${collection.id}`, "ReadCollectionQuotaInfo");
+ handleError(error, "ReadCollectionQuotaInfo", `Error while querying quota info for container ${collection.id}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/readCollections.ts b/src/Common/dataAccess/readCollections.ts
index 405232f49..a6b741d91 100644
--- a/src/Common/dataAccess/readCollections.ts
+++ b/src/Common/dataAccess/readCollections.ts
@@ -29,7 +29,7 @@ export async function readCollections(databaseId: string): Promise {
databases = sdkResponse.resources as DataModels.Database[];
}
} catch (error) {
- handleError(error, `Error while querying databases`, "ReadDatabases");
+ handleError(error, "ReadDatabases", `Error while querying databases`);
throw error;
}
clearMessage();
diff --git a/src/Common/dataAccess/readMongoDBCollection.tsx b/src/Common/dataAccess/readMongoDBCollection.tsx
index 80013cf94..b66f8dc9c 100644
--- a/src/Common/dataAccess/readMongoDBCollection.tsx
+++ b/src/Common/dataAccess/readMongoDBCollection.tsx
@@ -22,7 +22,7 @@ export async function readMongoDBCollectionThroughRP(
const response = await getMongoDBCollection(subscriptionId, resourceGroup, accountName, databaseId, collectionId);
collection = response.properties.resource;
} catch (error) {
- handleError(error, `Error while reading container ${collectionId}`, "ReadMongoDBCollection");
+ handleError(error, "ReadMongoDBCollection", `Error while reading container ${collectionId}`);
throw error;
}
clearMessage();
diff --git a/src/Common/dataAccess/readOffers.ts b/src/Common/dataAccess/readOffers.ts
index 8fc743c9c..4afc452aa 100644
--- a/src/Common/dataAccess/readOffers.ts
+++ b/src/Common/dataAccess/readOffers.ts
@@ -17,7 +17,7 @@ export const readOffers = async (): Promise => {
return [];
}
- handleError(error, `Error while querying offers`, "ReadOffers");
+ handleError(error, "ReadOffers", `Error while querying offers`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/readStoredProcedures.ts b/src/Common/dataAccess/readStoredProcedures.ts
index cb64654e6..75fb3111b 100644
--- a/src/Common/dataAccess/readStoredProcedures.ts
+++ b/src/Common/dataAccess/readStoredProcedures.ts
@@ -35,7 +35,7 @@ export async function readStoredProcedures(
.fetchAll();
return response?.resources;
} catch (error) {
- handleError(error, `Failed to query stored procedures for container ${collectionId}`, "ReadStoredProcedures");
+ handleError(error, "ReadStoredProcedures", `Failed to query stored procedures for container ${collectionId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/readTriggers.ts b/src/Common/dataAccess/readTriggers.ts
index d8b6f6469..85a96331f 100644
--- a/src/Common/dataAccess/readTriggers.ts
+++ b/src/Common/dataAccess/readTriggers.ts
@@ -35,7 +35,7 @@ export async function readTriggers(
.fetchAll();
return response?.resources;
} catch (error) {
- handleError(error, `Failed to query triggers for container ${collectionId}`, "ReadTriggers");
+ handleError(error, "ReadTriggers", `Failed to query triggers for container ${collectionId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/readUserDefinedFunctions.ts b/src/Common/dataAccess/readUserDefinedFunctions.ts
index 93d26f73a..f6990db7c 100644
--- a/src/Common/dataAccess/readUserDefinedFunctions.ts
+++ b/src/Common/dataAccess/readUserDefinedFunctions.ts
@@ -37,8 +37,8 @@ export async function readUserDefinedFunctions(
} catch (error) {
handleError(
error,
- `Failed to query user defined functions for container ${collectionId}`,
- "ReadUserDefinedFunctions"
+ "ReadUserDefinedFunctions",
+ `Failed to query user defined functions for container ${collectionId}`
);
throw error;
} finally {
diff --git a/src/Common/dataAccess/updateCollection.ts b/src/Common/dataAccess/updateCollection.ts
index 3f317c069..3a45af694 100644
--- a/src/Common/dataAccess/updateCollection.ts
+++ b/src/Common/dataAccess/updateCollection.ts
@@ -59,7 +59,7 @@ export async function updateCollection(
logConsoleInfo(`Successfully updated container ${collectionId}`);
return collection;
} catch (error) {
- handleError(error, `Failed to update container ${collectionId}`, "UpdateCollection");
+ handleError(error, "UpdateCollection", `Failed to update container ${collectionId}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/updateOffer.ts b/src/Common/dataAccess/updateOffer.ts
index f7568abd1..24cd150c8 100644
--- a/src/Common/dataAccess/updateOffer.ts
+++ b/src/Common/dataAccess/updateOffer.ts
@@ -72,7 +72,7 @@ export const updateOffer = async (params: UpdateOfferParams): Promise =>
logConsoleInfo(`Successfully updated offer for ${offerResourceText}`);
return updatedOffer;
} catch (error) {
- handleError(error, `Error updating offer for ${offerResourceText}`, "UpdateCollection");
+ handleError(error, "UpdateCollection", `Error updating offer for ${offerResourceText}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/updateOfferThroughputBeyondLimit.ts b/src/Common/dataAccess/updateOfferThroughputBeyondLimit.ts
index 1b713de85..93a8f8737 100644
--- a/src/Common/dataAccess/updateOfferThroughputBeyondLimit.ts
+++ b/src/Common/dataAccess/updateOfferThroughputBeyondLimit.ts
@@ -49,8 +49,8 @@ export async function updateOfferThroughputBeyondLimit(request: UpdateOfferThrou
const error = await response.json();
handleError(
error,
- `Failed to request an increase in throughput for ${request.throughput}`,
- "updateOfferThroughputBeyondLimit"
+ "updateOfferThroughputBeyondLimit",
+ `Failed to request an increase in throughput for ${request.throughput}`
);
clearMessage();
throw error;
diff --git a/src/Common/dataAccess/updateStoredProcedure.ts b/src/Common/dataAccess/updateStoredProcedure.ts
index 17c498d66..4ca900905 100644
--- a/src/Common/dataAccess/updateStoredProcedure.ts
+++ b/src/Common/dataAccess/updateStoredProcedure.ts
@@ -64,7 +64,7 @@ export async function updateStoredProcedure(
.replace(storedProcedure);
return response?.resource;
} catch (error) {
- handleError(error, `Error while updating stored procedure ${storedProcedure.id}`, "UpdateStoredProcedure");
+ handleError(error, "UpdateStoredProcedure", `Error while updating stored procedure ${storedProcedure.id}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/updateTrigger.ts b/src/Common/dataAccess/updateTrigger.ts
index 845e19274..49759bb2f 100644
--- a/src/Common/dataAccess/updateTrigger.ts
+++ b/src/Common/dataAccess/updateTrigger.ts
@@ -61,7 +61,7 @@ export async function updateTrigger(
.replace(trigger);
return response?.resource;
} catch (error) {
- handleError(error, `Error while updating trigger ${trigger.id}`, "UpdateTrigger");
+ handleError(error, "UpdateTrigger", `Error while updating trigger ${trigger.id}`);
throw error;
} finally {
clearMessage();
diff --git a/src/Common/dataAccess/updateUserDefinedFunction.ts b/src/Common/dataAccess/updateUserDefinedFunction.ts
index 373cb1dcd..3e2ebab1b 100644
--- a/src/Common/dataAccess/updateUserDefinedFunction.ts
+++ b/src/Common/dataAccess/updateUserDefinedFunction.ts
@@ -66,8 +66,8 @@ export async function updateUserDefinedFunction(
} catch (error) {
handleError(
error,
- `Error while updating user defined function ${userDefinedFunction.id}`,
- "UpdateUserupdateUserDefinedFunction"
+ "UpdateUserupdateUserDefinedFunction",
+ `Error while updating user defined function ${userDefinedFunction.id}`
);
throw error;
} finally {
diff --git a/src/Contracts/Diagnostics.ts b/src/Contracts/Diagnostics.ts
index 6a523bfd5..d6a54740a 100644
--- a/src/Contracts/Diagnostics.ts
+++ b/src/Contracts/Diagnostics.ts
@@ -46,7 +46,7 @@ export interface LogEntry {
/**
* The message code.
*/
- code?: number;
+ code?: number | string;
/**
* Any additional data to be logged.
*/
diff --git a/src/Explorer/Controls/Arcadia/ArcadiaMenuPicker.tsx b/src/Explorer/Controls/Arcadia/ArcadiaMenuPicker.tsx
index c46dbebab..e4165421f 100644
--- a/src/Explorer/Controls/Arcadia/ArcadiaMenuPicker.tsx
+++ b/src/Explorer/Controls/Arcadia/ArcadiaMenuPicker.tsx
@@ -1,12 +1,9 @@
import * as React from "react";
import { ArcadiaWorkspace, SparkPool } from "../../../Contracts/DataModels";
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
-import {
- IContextualMenuItem,
- IContextualMenuProps,
- ContextualMenuItemType
-} from "office-ui-fabric-react/lib/ContextualMenu";
+import { IContextualMenuItem, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
import * as Logger from "../../../Common/Logger";
+import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
export interface ArcadiaMenuPickerProps {
selectText?: string;
@@ -47,7 +44,7 @@ export class ArcadiaMenuPicker extends React.Component
): string {
if (!serverInfo.notebookServerEndpoint) {
- const error = "Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.";
- Logger.logError(error, "NotebookTerminalComponent/createNotebookAppSrc");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(
+ "Notebook server endpoint not defined. Terminal will fail to connect to jupyter server.",
+ "NotebookTerminalComponent/createNotebookAppSrc"
+ );
return "";
}
diff --git a/src/Explorer/Controls/NotebookGallery/CodeOfConductComponent.tsx b/src/Explorer/Controls/NotebookGallery/CodeOfConductComponent.tsx
index 02dc407e0..90ddf7b09 100644
--- a/src/Explorer/Controls/NotebookGallery/CodeOfConductComponent.tsx
+++ b/src/Explorer/Controls/NotebookGallery/CodeOfConductComponent.tsx
@@ -1,9 +1,8 @@
import * as React from "react";
import { JunoClient } from "../../../Juno/JunoClient";
import { HttpStatusCodes, CodeOfConductEndpoints } from "../../../Common/Constants";
-import * as Logger from "../../../Common/Logger";
-import { logConsoleError } from "../../../Utils/NotificationConsoleUtils";
import { Stack, Text, Checkbox, PrimaryButton, Link } from "office-ui-fabric-react";
+import { handleError } from "../../../Common/ErrorHandlingUtils";
export interface CodeOfConductComponentProps {
junoClient: JunoClient;
@@ -45,9 +44,7 @@ export class CodeOfConductComponent extends React.Component {
- Logger.logError(error, "Explorer/getArcadiaToken");
+ Logger.logError(getErrorMessage(error), "Explorer/getArcadiaToken");
resolve(undefined);
}
);
@@ -1551,7 +1551,7 @@ export default class Explorer {
workspaceItems[i] = { ...workspace, sparkPools: sparkpools };
},
error => {
- Logger.logError(error, "Explorer/this._arcadiaManager.listSparkPoolsAsync");
+ Logger.logError(getErrorMessage(error), "Explorer/this._arcadiaManager.listSparkPoolsAsync");
}
);
sparkPromises.push(promise);
@@ -1559,7 +1559,7 @@ export default class Explorer {
return Promise.all(sparkPromises).then(() => workspaceItems);
} catch (error) {
- handleError(error, "Get Arcadia workspaces failed", "Explorer/this._arcadiaManager.listWorkspacesAsync");
+ handleError(error, "Explorer/this._arcadiaManager.listWorkspacesAsync", "Get Arcadia workspaces failed");
return Promise.resolve([]);
}
}
@@ -1596,8 +1596,8 @@ export default class Explorer {
this._isInitializingNotebooks = false;
handleError(
error,
- `Failed to get notebook workspace connection info: ${getErrorMessage(error)}`,
- "initNotebooks/getNotebookConnectionInfoAsync"
+ "initNotebooks/getNotebookConnectionInfoAsync",
+ `Failed to get notebook workspace connection info: ${getErrorMessage(error)}`
);
throw error;
} finally {
@@ -1620,9 +1620,10 @@ export default class Explorer {
public resetNotebookWorkspace() {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookClient) {
- const error = "Attempt to reset notebook workspace, but notebook is not enabled";
- Logger.logError(error, "Explorer/resetNotebookWorkspace");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(
+ "Attempt to reset notebook workspace, but notebook is not enabled",
+ "Explorer/resetNotebookWorkspace"
+ );
return;
}
const resetConfirmationDialogProps: DialogProps = {
@@ -1647,7 +1648,7 @@ export default class Explorer {
const workspaces = await this.notebookWorkspaceManager.getNotebookWorkspacesAsync(databaseAccount?.id);
return workspaces && workspaces.length > 0 && workspaces.some(workspace => workspace.name === "default");
} catch (error) {
- Logger.logError(error, "Explorer/_containsDefaultNotebookWorkspace");
+ Logger.logError(getErrorMessage(error), "Explorer/_containsDefaultNotebookWorkspace");
return false;
}
}
@@ -1673,7 +1674,7 @@ export default class Explorer {
await this.notebookWorkspaceManager.startNotebookWorkspaceAsync(this.databaseAccount().id, "default");
}
} catch (error) {
- handleError(error, "Failed to initialize notebook workspace", "Explorer/ensureNotebookWorkspaceRunning");
+ handleError(error, "Explorer/ensureNotebookWorkspaceRunning", "Failed to initialize notebook workspace");
} finally {
clearMessage && clearMessage();
}
@@ -2221,8 +2222,7 @@ export default class Explorer {
public uploadFile(name: string, content: string, parent: NotebookContentItem): Promise {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to upload notebook, but notebook is not enabled";
- Logger.logError(error, "Explorer/uploadFile");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(error, "Explorer/uploadFile");
throw new Error(error);
}
@@ -2403,8 +2403,7 @@ export default class Explorer {
public renameNotebook(notebookFile: NotebookContentItem): Q.Promise {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to rename notebook, but notebook is not enabled";
- Logger.logError(error, "Explorer/renameNotebook");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(error, "Explorer/renameNotebook");
throw new Error(error);
}
@@ -2452,8 +2451,7 @@ export default class Explorer {
public onCreateDirectory(parent: NotebookContentItem): Q.Promise {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create notebook directory, but notebook is not enabled";
- Logger.logError(error, "Explorer/onCreateDirectory");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(error, "Explorer/onCreateDirectory");
throw new Error(error);
}
@@ -2474,8 +2472,7 @@ export default class Explorer {
public readFile(notebookFile: NotebookContentItem): Promise {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to read file, but notebook is not enabled";
- Logger.logError(error, "Explorer/downloadFile");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(error, "Explorer/downloadFile");
throw new Error(error);
}
@@ -2485,8 +2482,7 @@ export default class Explorer {
public downloadFile(notebookFile: NotebookContentItem): Promise {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to download file, but notebook is not enabled";
- Logger.logError(error, "Explorer/downloadFile");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(error, "Explorer/downloadFile");
throw new Error(error);
}
@@ -2567,7 +2563,7 @@ export default class Explorer {
);
this.isNotebooksEnabledForAccount(isAccountInAllowedLocation);
} catch (error) {
- Logger.logError(error, "Explorer/isNotebooksEnabledForAccount");
+ Logger.logError(getErrorMessage(error), "Explorer/isNotebooksEnabledForAccount");
this.isNotebooksEnabledForAccount(false);
}
}
@@ -2596,7 +2592,7 @@ export default class Explorer {
false;
this.isSparkEnabledForAccount(isEnabled);
} catch (error) {
- Logger.logError(error, "Explorer/isSparkEnabledForAccount");
+ Logger.logError(getErrorMessage(error), "Explorer/isSparkEnabledForAccount");
this.isSparkEnabledForAccount(false);
}
};
@@ -2621,7 +2617,7 @@ export default class Explorer {
(featureStatus && featureStatus.properties && featureStatus.properties.state === "Registered") || false;
return isEnabled;
} catch (error) {
- Logger.logError(error, "Explorer/isSparkEnabledForAccount");
+ Logger.logError(getErrorMessage(error), "Explorer/isSparkEnabledForAccount");
return false;
}
};
@@ -2640,8 +2636,7 @@ export default class Explorer {
public deleteNotebookFile(item: NotebookContentItem): Promise {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to delete notebook file, but notebook is not enabled";
- Logger.logError(error, "Explorer/deleteNotebookFile");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(error, "Explorer/deleteNotebookFile");
throw new Error(error);
}
@@ -2690,8 +2685,7 @@ export default class Explorer {
public onNewNotebookClicked(parent?: NotebookContentItem): void {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to create new notebook, but notebook is not enabled";
- Logger.logError(error, "Explorer/onNewNotebookClicked");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(error, "Explorer/onNewNotebookClicked");
throw new Error(error);
}
@@ -2776,8 +2770,7 @@ export default class Explorer {
public refreshContentItem(item: NotebookContentItem): Promise {
if (!this.isNotebookEnabled() || !this.notebookManager?.notebookContentClient) {
const error = "Attempt to refresh notebook list, but notebook is not enabled";
- Logger.logError(error, "Explorer/refreshContentItem");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ handleError(error, "Explorer/refreshContentItem");
return Promise.reject(new Error(error));
}
@@ -2963,7 +2956,7 @@ export default class Explorer {
}
return tokenRefreshInterval;
} catch (error) {
- Logger.logError(error, "Explorer/getTokenRefreshInterval");
+ Logger.logError(getErrorMessage(error), "Explorer/getTokenRefreshInterval");
return tokenRefreshInterval;
}
}
diff --git a/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts b/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
index f5256b9d5..5947c5439 100644
--- a/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
+++ b/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.test.ts
@@ -94,7 +94,7 @@ describe("Gremlin Client", () => {
it("should log and display error out on unknown requestId", () => {
const gremlinClient = new GremlinClient();
- const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleMessage");
+ const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleError");
const logErrorSpy = sinon.spy(Logger, "logError");
gremlinClient.initialize(emptyParams);
@@ -122,7 +122,7 @@ describe("Gremlin Client", () => {
});
it("should not aggregate RU if not a number and reset totalRequestCharge to undefined", done => {
- const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleMessage");
+ const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleError");
const logErrorSpy = sinon.spy(Logger, "logError");
const gremlinClient = new GremlinClient();
@@ -165,7 +165,7 @@ describe("Gremlin Client", () => {
});
it("should not aggregate RU if undefined and reset totalRequestCharge to undefined", done => {
- const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleMessage");
+ const logConsoleSpy = sinon.spy(NotificationConsoleUtils, "logConsoleError");
const logErrorSpy = sinon.spy(Logger, "logError");
const gremlinClient = new GremlinClient();
diff --git a/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts b/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
index 7350f71d5..e66ae5cf2 100644
--- a/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
+++ b/src/Explorer/Graph/GraphExplorerComponent/GremlinClient.ts
@@ -7,8 +7,7 @@ import { GremlinSimpleClient, Result } from "./GremlinSimpleClient";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { HashMap } from "../../../Common/HashMap";
-import * as Logger from "../../../Common/Logger";
-import { getErrorMessage } from "../../../Common/ErrorHandlingUtils";
+import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils";
export interface GremlinClientParameters {
endpoint: string;
@@ -63,10 +62,10 @@ export class GremlinClient {
const requestId = result.requestId;
if (!requestId || !this.pendingResults.has(requestId)) {
- const msg = `Error: ${errorMessage}, unknown requestId:${requestId} ${GremlinClient.getRequestChargeString(
+ const errorMsg = `Error: ${errorMessage}, unknown requestId:${requestId} ${GremlinClient.getRequestChargeString(
result.requestCharge
)}`;
- GremlinClient.reportError(msg);
+ handleError(errorMsg, GremlinClient.LOG_AREA);
// Fail all pending requests if no request id (fatal)
if (!requestId) {
@@ -130,15 +129,16 @@ export class GremlinClient {
deferred.reject(error);
this.pendingResults.delete(requestId);
- GremlinClient.reportError(
- `Aborting pending request ${requestId}. Error:${error} ${GremlinClient.getRequestChargeString(requestCharge)}`
- );
+ const errorMsg = `Aborting pending request ${requestId}. Error:${error} ${GremlinClient.getRequestChargeString(
+ requestCharge
+ )}`;
+ handleError(errorMsg, GremlinClient.LOG_AREA);
}
private flushResult(requestId: string) {
if (!this.pendingResults.has(requestId)) {
- const msg = `Unknown requestId:${requestId}`;
- GremlinClient.reportError(msg);
+ const errorMsg = `Unknown requestId:${requestId}`;
+ handleError(errorMsg, GremlinClient.LOG_AREA);
return;
}
@@ -156,8 +156,8 @@ export class GremlinClient {
*/
private storePendingResult(result: Result): boolean {
if (!this.pendingResults.has(result.requestId)) {
- const msg = `Dropping result for unknown requestId:${result.requestId}`;
- GremlinClient.reportError(msg);
+ const errorMsg = `Dropping result for unknown requestId:${result.requestId}`;
+ handleError(errorMsg, GremlinClient.LOG_AREA);
return false;
}
const pendingResults = this.pendingResults.get(result.requestId).result;
@@ -177,9 +177,8 @@ export class GremlinClient {
if (result.requestCharge === undefined || typeof result.requestCharge !== "number") {
// Clear totalRequestCharge, even if it was a valid number as the total might be incomplete therefore incorrect
pendingResults.totalRequestCharge = undefined;
- GremlinClient.reportError(
- `Unable to perform RU aggregation calculation with non numbers. Result request charge: ${result.requestCharge}. RequestId: ${result.requestId}`
- );
+ const errorMsg = `Unable to perform RU aggregation calculation with non numbers. Result request charge: ${result.requestCharge}. RequestId: ${result.requestId}`;
+ handleError(errorMsg, GremlinClient.LOG_AREA);
} else {
if (pendingResults.totalRequestCharge === undefined) {
pendingResults.totalRequestCharge = 0;
@@ -188,9 +187,4 @@ export class GremlinClient {
}
return pendingResults.isIncomplete;
}
-
- private static reportError(msg: string): void {
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
- Logger.logError(msg, GremlinClient.LOG_AREA);
- }
}
diff --git a/src/Explorer/Notebook/NotebookContainerClient.ts b/src/Explorer/Notebook/NotebookContainerClient.ts
index 78cfa0fa3..90f8a9a74 100644
--- a/src/Explorer/Notebook/NotebookContainerClient.ts
+++ b/src/Explorer/Notebook/NotebookContainerClient.ts
@@ -6,6 +6,7 @@ import * as Constants from "../../Common/Constants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as Logger from "../../Common/Logger";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export class NotebookContainerClient {
private reconnectingNotificationId: string;
@@ -74,7 +75,7 @@ export class NotebookContainerClient {
}
return undefined;
} catch (error) {
- Logger.logError(error, "NotebookContainerClient/getMemoryUsage");
+ Logger.logError(getErrorMessage(error), "NotebookContainerClient/getMemoryUsage");
if (!this.reconnectingNotificationId) {
this.reconnectingNotificationId = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
@@ -110,7 +111,7 @@ export class NotebookContainerClient {
headers: { Authorization: authToken }
});
} catch (error) {
- Logger.logError(error, "NotebookContainerClient/resetWorkspace");
+ Logger.logError(getErrorMessage(error), "NotebookContainerClient/resetWorkspace");
await this.recreateNotebookWorkspaceAsync();
}
}
@@ -140,7 +141,7 @@ export class NotebookContainerClient {
await notebookWorkspaceManager.deleteNotebookWorkspaceAsync(explorer.databaseAccount().id, "default");
await notebookWorkspaceManager.createNotebookWorkspaceAsync(explorer.databaseAccount().id, "default");
} catch (error) {
- Logger.logError(error, "NotebookContainerClient/recreateNotebookWorkspaceAsync");
+ Logger.logError(getErrorMessage(error), "NotebookContainerClient/recreateNotebookWorkspaceAsync");
return Promise.reject(error);
}
}
diff --git a/src/Explorer/Notebook/NotebookManager.ts b/src/Explorer/Notebook/NotebookManager.ts
index 2fe8748d0..932d80e13 100644
--- a/src/Explorer/Notebook/NotebookManager.ts
+++ b/src/Explorer/Notebook/NotebookManager.ts
@@ -26,6 +26,7 @@ import { ImmutableNotebook } from "@nteract/commutable";
import Explorer from "../Explorer";
import { ContextualPaneBase } from "../Panes/ContextualPaneBase";
import { CopyNotebookPaneAdapter } from "../Panes/CopyNotebookPane";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
export interface NotebookManagerOptions {
container: Explorer;
@@ -147,7 +148,7 @@ export default class NotebookManager {
// Octokit's error handler uses any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private onGitHubClientError = (error: any): void => {
- Logger.logError(error, "NotebookManager/onGitHubClientError");
+ Logger.logError(getErrorMessage(error), "NotebookManager/onGitHubClientError");
if (error.status === HttpStatusCodes.Unauthorized) {
this.gitHubOAuthService.resetToken();
diff --git a/src/Explorer/Panes/CopyNotebookPane.tsx b/src/Explorer/Panes/CopyNotebookPane.tsx
index 586af6318..1ef61eb3a 100644
--- a/src/Explorer/Panes/CopyNotebookPane.tsx
+++ b/src/Explorer/Panes/CopyNotebookPane.tsx
@@ -1,7 +1,6 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
-import * as Logger from "../../Common/Logger";
import { JunoClient, IPinnedRepo } from "../../Juno/JunoClient";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import Explorer from "../Explorer";
@@ -13,6 +12,7 @@ import { HttpStatusCodes } from "../../Common/Constants";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import { NotebookContentItemType, NotebookContentItem } from "../Notebook/NotebookContentItem";
import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter";
+import { handleError, getErrorMessage } from "../../Common/ErrorHandlingUtils";
interface Location {
type: "MyNotebooks" | "GitHub";
@@ -90,9 +90,7 @@ export class CopyNotebookPaneAdapter implements ReactAdapter {
if (this.gitHubOAuthService.isLoggedIn()) {
const response = await this.junoClient.getPinnedRepos(this.gitHubOAuthService.getTokenObservable()()?.scope);
if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) {
- const message = `Received HTTP ${response.status} when fetching pinned repos`;
- Logger.logError(message, "CopyNotebookPaneAdapter/submit");
- NotificationConsoleUtils.logConsoleError(message);
+ handleError(`Received HTTP ${response.status} when fetching pinned repos`, "CopyNotebookPaneAdapter/submit");
}
if (response.data?.length > 0) {
@@ -134,12 +132,10 @@ export class CopyNotebookPaneAdapter implements ReactAdapter {
NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${this.name} to ${destination}`);
} catch (error) {
+ const errorMessage = getErrorMessage(error);
this.formError = `Failed to copy ${this.name} to ${destination}`;
- this.formErrorDetail = `${error}`;
-
- const message = `${this.formError}: ${this.formErrorDetail}`;
- Logger.logError(message, "CopyNotebookPaneAdapter/submit");
- NotificationConsoleUtils.logConsoleError(message);
+ this.formErrorDetail = `${errorMessage}`;
+ handleError(errorMessage, "CopyNotebookPaneAdapter/submit", this.formError);
return;
} finally {
clearMessage && clearMessage();
diff --git a/src/Explorer/Panes/GitHubReposPane.ts b/src/Explorer/Panes/GitHubReposPane.ts
index a18f7d867..403abfd75 100644
--- a/src/Explorer/Panes/GitHubReposPane.ts
+++ b/src/Explorer/Panes/GitHubReposPane.ts
@@ -1,6 +1,5 @@
import _ from "underscore";
import { Areas, HttpStatusCodes } from "../../Common/Constants";
-import * as Logger from "../../Common/Logger";
import * as ViewModels from "../../Contracts/ViewModels";
import { GitHubClient, IGitHubPageInfo, IGitHubRepo } from "../../GitHub/GitHubClient";
import { IPinnedRepo, JunoClient } from "../../Juno/JunoClient";
@@ -8,13 +7,12 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as GitHubUtils from "../../Utils/GitHubUtils";
import { JunoUtils } from "../../Utils/JunoUtils";
-import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import { AuthorizeAccessComponent } from "../Controls/GitHub/AuthorizeAccessComponent";
import { GitHubReposComponent, GitHubReposComponentProps, RepoListItem } from "../Controls/GitHub/GitHubReposComponent";
import { GitHubReposComponentAdapter } from "../Controls/GitHub/GitHubReposComponentAdapter";
import { BranchesProps, PinnedReposProps, UnpinnedReposProps } from "../Controls/GitHub/ReposListComponent";
-import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
+import { handleError } from "../../Common/ErrorHandlingUtils";
interface GitHubReposPaneOptions extends ViewModels.PaneOptions {
gitHubClient: GitHubClient;
@@ -105,9 +103,7 @@ export class GitHubReposPane extends ContextualPaneBase {
throw new Error(`Received HTTP ${response.status} when saving pinned repos`);
}
} catch (error) {
- const message = `Failed to save pinned repos: ${error}`;
- Logger.logError(message, "GitHubReposPane/submit");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GitHubReposPane/submit", "Failed to save pinned repos");
}
}
}
@@ -206,9 +202,7 @@ export class GitHubReposPane extends ContextualPaneBase {
branchesProps.lastPageInfo = response.pageInfo;
}
} catch (error) {
- const message = `Failed to fetch branches: ${error}`;
- Logger.logError(message, "GitHubReposPane/loadMoreBranches");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GitHubReposPane/loadMoreBranches", "Failed to fetch branches");
}
branchesProps.isLoading = false;
@@ -236,9 +230,7 @@ export class GitHubReposPane extends ContextualPaneBase {
this.unpinnedReposProps.repos = this.calculateUnpinnedRepos();
}
} catch (error) {
- const message = `Failed to fetch unpinned repos: ${error}`;
- Logger.logError(message, "GitHubReposPane/loadMoreUnpinnedRepos");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GitHubReposPane/loadMoreUnpinnedRepos", "Failed to fetch unpinned repos");
}
this.unpinnedReposProps.isLoading = false;
@@ -255,9 +247,7 @@ export class GitHubReposPane extends ContextualPaneBase {
return response.data;
} catch (error) {
- const message = `Failed to fetch repo: ${error}`;
- Logger.logError(message, "GitHubReposPane/getRepo");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GitHubReposPane/getRepo", "Failed to fetch repo");
return Promise.resolve(undefined);
}
}
@@ -320,9 +310,7 @@ export class GitHubReposPane extends ContextualPaneBase {
this.triggerRender();
}
} catch (error) {
- const message = `Failed to fetch pinned repos: ${error}`;
- Logger.logError(message, "GitHubReposPane/refreshPinnedReposListItems");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GitHubReposPane/refreshPinnedReposListItems", "Failed to fetch pinned repos");
}
}
diff --git a/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx b/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx
index c8f94f7d2..967355251 100644
--- a/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx
+++ b/src/Explorer/Panes/PublishNotebookPaneAdapter.tsx
@@ -1,17 +1,16 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
-import * as Logger from "../../Common/Logger";
import Explorer from "../Explorer";
import { JunoClient } from "../../Juno/JunoClient";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
-import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { GenericRightPaneComponent, GenericRightPaneProps } from "./GenericRightPaneComponent";
import { PublishNotebookPaneComponent, PublishNotebookPaneProps } from "./PublishNotebookPaneComponent";
import { ImmutableNotebook } from "@nteract/commutable/src";
import { toJS } from "@nteract/commutable";
import { CodeOfConductComponent } from "../Controls/NotebookGallery/CodeOfConductComponent";
import { HttpStatusCodes } from "../../Common/Constants";
+import { handleError, getErrorMessage } from "../../Common/ErrorHandlingUtils";
export class PublishNotebookPaneAdapter implements ReactAdapter {
parameters: ko.Observable;
@@ -111,9 +110,11 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
this.isCodeOfConductAccepted = response.data;
} catch (error) {
- const message = `Failed to check if code of conduct was accepted: ${error}`;
- Logger.logError(message, "PublishNotebookPaneAdapter/isCodeOfConductAccepted");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(
+ error,
+ "PublishNotebookPaneAdapter/isCodeOfConductAccepted",
+ "Failed to check if code of conduct was accepted"
+ );
}
} else {
this.isCodeOfConductAccepted = true;
@@ -170,12 +171,10 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
}
}
} catch (error) {
+ const errorMessage = getErrorMessage(error);
this.formError = `Failed to publish ${this.name} to gallery`;
- this.formErrorDetail = `${error}`;
-
- const message = `${this.formError}: ${this.formErrorDetail}`;
- Logger.logError(message, "PublishNotebookPaneAdapter/submit");
- NotificationConsoleUtils.logConsoleError(message);
+ this.formErrorDetail = `${errorMessage}`;
+ handleError(errorMessage, "PublishNotebookPaneAdapter/submit", this.formError);
return;
} finally {
clearPublishingMessage();
@@ -189,10 +188,7 @@ export class PublishNotebookPaneAdapter implements ReactAdapter {
private createFormErrorForLargeImageSelection = (formError: string, formErrorDetail: string, area: string): void => {
this.formError = formError;
this.formErrorDetail = formErrorDetail;
-
- const message = `${this.formError}: ${this.formErrorDetail}`;
- Logger.logError(message, area);
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(formErrorDetail, area, formError);
this.triggerRender();
};
diff --git a/src/Explorer/Tables/TableDataClient.ts b/src/Explorer/Tables/TableDataClient.ts
index 1dda5b9b4..ba6ae246d 100644
--- a/src/Explorer/Tables/TableDataClient.ts
+++ b/src/Explorer/Tables/TableDataClient.ts
@@ -7,16 +7,14 @@ import { ConsoleDataType } from "../../Explorer/Menus/NotificationConsole/Notifi
import * as Constants from "../../Common/Constants";
import * as Entities from "./Entities";
import * as HeadersUtility from "../../Common/HeadersUtility";
-import * as Logger from "../../Common/Logger";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as TableConstants from "./Constants";
import * as TableEntityProcessor from "./TableEntityProcessor";
import * as ViewModels from "../../Contracts/ViewModels";
-import { MessageTypes } from "../../Contracts/ExplorerContracts";
-import { sendMessage } from "../../Common/MessageHandler";
import Explorer from "../Explorer";
import { queryDocuments, deleteDocument, updateDocument, createDocument } from "../../Common/DocumentClientUtilityBase";
import { configContext } from "../../ConfigContext";
+import { handleError } from "../../Common/ErrorHandlingUtils";
export interface CassandraTableKeys {
partitionKeys: CassandraTableKey[];
@@ -188,14 +186,9 @@ export class CassandraAPIDataClient extends TableDataClient {
);
deferred.resolve(entity);
},
- reason => {
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Error while adding new row to table ${collection.id()}:\n ${JSON.stringify(reason)}`
- );
- Logger.logError(JSON.stringify(reason), "AddRowCassandra", reason.code);
- this._checkForbiddenError(reason);
- deferred.reject(reason);
+ error => {
+ handleError(error, "AddRowCassandra", `Error while adding new row to table ${collection.id()}`);
+ deferred.reject(error);
}
)
.finally(() => {
@@ -267,14 +260,9 @@ export class CassandraAPIDataClient extends TableDataClient {
);
deferred.resolve(newEntity);
},
- reason => {
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Failed to update row ${newEntity.RowKey._}: ${JSON.stringify(reason)}`
- );
- Logger.logError(JSON.stringify(reason), "UpdateRowCassandra", reason.code);
- this._checkForbiddenError(reason);
- deferred.reject(reason);
+ error => {
+ handleError(error, "UpdateRowCassandra", `Failed to update row ${newEntity.RowKey._}`);
+ deferred.reject(error);
}
)
.finally(() => {
@@ -332,16 +320,11 @@ export class CassandraAPIDataClient extends TableDataClient {
ContinuationToken: data.paginationToken
});
},
- reason => {
+ (error: any) => {
if (shouldNotify) {
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Failed to query rows for table ${collection.id()}: ${JSON.stringify(reason)}`
- );
- Logger.logError(JSON.stringify(reason), "QueryDocumentsCassandra", reason.status);
- this._checkForbiddenError(reason);
+ handleError(error, "QueryDocumentsCassandra", `Failed to query rows for table ${collection.id()}`);
}
- deferred.reject(reason);
+ deferred.reject(error);
}
)
.done(() => {
@@ -379,13 +362,8 @@ export class CassandraAPIDataClient extends TableDataClient {
`Successfully deleted row ${currEntityToDelete.RowKey._}`
);
},
- reason => {
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Error while deleting row ${currEntityToDelete.RowKey._}:\n ${JSON.stringify(reason)}`
- );
- Logger.logError(JSON.stringify(reason), "DeleteRowCassandra", reason.code);
- this._checkForbiddenError(reason);
+ error => {
+ handleError(error, "DeleteRowCassandra", `Error while deleting row ${currEntityToDelete.RowKey._}`);
}
)
.finally(() => {
@@ -420,14 +398,13 @@ export class CassandraAPIDataClient extends TableDataClient {
);
deferred.resolve();
},
- reason => {
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Error while creating a keyspace with query ${createKeyspaceQuery}:\n ${JSON.stringify(reason)}`
+ error => {
+ handleError(
+ error,
+ "CreateKeyspaceCassandra",
+ `Error while creating a keyspace with query ${createKeyspaceQuery}`
);
- Logger.logError(JSON.stringify(reason), "CreateKeyspaceCassandra", reason.code);
- this._checkForbiddenError(reason);
- deferred.reject(reason);
+ deferred.reject(error);
}
)
.finally(() => {
@@ -467,14 +444,9 @@ export class CassandraAPIDataClient extends TableDataClient {
);
deferred.resolve();
},
- reason => {
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Error while creating a table with query ${createTableQuery}:\n ${JSON.stringify(reason)}`
- );
- Logger.logError(JSON.stringify(reason), "CreateTableCassandra", reason.code);
- this._checkForbiddenError(reason);
- deferred.reject(reason);
+ error => {
+ handleError(error, "CreateTableCassandra", `Error while creating a table with query ${createTableQuery}`);
+ deferred.reject(error);
}
)
.finally(() => {
@@ -508,14 +480,13 @@ export class CassandraAPIDataClient extends TableDataClient {
);
deferred.resolve();
},
- reason => {
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Error while deleting resource with query ${deleteQuery}:\n ${JSON.stringify(reason)}`
+ error => {
+ handleError(
+ error,
+ "DeleteKeyspaceOrTableCassandra",
+ `Error while deleting resource with query ${deleteQuery}`
);
- Logger.logError(JSON.stringify(reason), "DeleteKeyspaceOrTableCassandra", reason.code);
- this._checkForbiddenError(reason);
- deferred.reject(reason);
+ deferred.reject(error);
}
)
.finally(() => {
@@ -563,14 +534,9 @@ export class CassandraAPIDataClient extends TableDataClient {
);
deferred.resolve(data);
},
- reason => {
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Error fetching keys for table ${collection.id()}:\n ${JSON.stringify(reason)}`
- );
- Logger.logError(JSON.stringify(reason), "FetchKeysCassandra", reason.status);
- this._checkForbiddenError(reason);
- deferred.reject(reason);
+ (error: any) => {
+ handleError(error, "FetchKeysCassandra", `Error fetching keys for table ${collection.id()}`);
+ deferred.reject(error);
}
)
.done(() => {
@@ -618,14 +584,9 @@ export class CassandraAPIDataClient extends TableDataClient {
);
deferred.resolve(data.columns);
},
- reason => {
- NotificationConsoleUtils.logConsoleMessage(
- ConsoleDataType.Error,
- `Error fetching schema for table ${collection.id()}:\n ${JSON.stringify(reason)}`
- );
- Logger.logError(JSON.stringify(reason), "FetchSchemaCassandra", reason.status);
- this._checkForbiddenError(reason);
- deferred.reject(reason);
+ (error: any) => {
+ handleError(error, "FetchSchemaCassandra", `Error fetching schema for table ${collection.id()}`);
+ deferred.reject(error);
}
)
.done(() => {
@@ -712,13 +673,4 @@ export class CassandraAPIDataClient extends TableDataClient {
displayTokenRenewalPromptForStatus(xhrObj.status);
};
-
- private _checkForbiddenError(reason: any) {
- if (reason && reason.code === Constants.HttpStatusCodes.Forbidden) {
- sendMessage({
- type: MessageTypes.ForbiddenError,
- reason: typeof reason === "string" ? "reason" : JSON.stringify(reason)
- });
- }
- }
}
diff --git a/src/GitHub/GitHubClient.ts b/src/GitHub/GitHubClient.ts
index 64ed9e2d1..6125836cf 100644
--- a/src/GitHub/GitHubClient.ts
+++ b/src/GitHub/GitHubClient.ts
@@ -3,6 +3,7 @@ import { HttpStatusCodes } from "../Common/Constants";
import * as Logger from "../Common/Logger";
import UrlUtility from "../Common/UrlUtility";
import { NotebookUtil } from "../Explorer/Notebook/NotebookUtil";
+import { getErrorMessage } from "../Common/ErrorHandlingUtils";
export interface IGitHubPageInfo {
endCursor: string;
@@ -244,7 +245,7 @@ export class GitHubClient {
data: GitHubClient.toGitHubRepo(response.repository)
};
} catch (error) {
- GitHubClient.log(Logger.logError, `GitHubClient.getRepoAsync failed: ${error}`);
+ Logger.logError(getErrorMessage(error), "GitHubClient.Octokit", "GitHubClient.getRepoAsync failed");
return {
status: GitHubClient.SelfErrorCode,
data: undefined
@@ -265,7 +266,7 @@ export class GitHubClient {
pageInfo: GitHubClient.toGitHubPageInfo(response.viewer.repositories.pageInfo)
};
} catch (error) {
- GitHubClient.log(Logger.logError, `GitHubClient.getReposAsync failed: ${error}`);
+ Logger.logError(getErrorMessage(error), "GitHubClient.Octokit", "GitHubClient.getRepoAsync failed");
return {
status: GitHubClient.SelfErrorCode,
data: undefined
@@ -294,7 +295,7 @@ export class GitHubClient {
pageInfo: GitHubClient.toGitHubPageInfo(response.repository.refs.pageInfo)
};
} catch (error) {
- GitHubClient.log(Logger.logError, `GitHubClient.getBranchesAsync failed: ${error}`);
+ Logger.logError(getErrorMessage(error), "GitHubClient.Octokit", "GitHubClient.getBranchesAsync failed");
return {
status: GitHubClient.SelfErrorCode,
data: undefined
@@ -359,7 +360,7 @@ export class GitHubClient {
data
};
} catch (error) {
- GitHubClient.log(Logger.logError, `GitHubClient.getContentsAsync failed: ${error}`);
+ Logger.logError(getErrorMessage(error), "GitHubClient.Octokit", "GitHubClient.getContentsAsync failed");
return {
status: GitHubClient.SelfErrorCode,
data: undefined
@@ -503,7 +504,7 @@ export class GitHubClient {
debug: () => {},
info: (message?: any) => GitHubClient.log(Logger.logInfo, message),
warn: (message?: any) => GitHubClient.log(Logger.logWarning, message),
- error: (message?: any) => GitHubClient.log(Logger.logError, message)
+ error: (error?: any) => Logger.logError(getErrorMessage(error), "GitHubClient.Octokit")
}
});
diff --git a/src/GitHub/GitHubContentProvider.ts b/src/GitHub/GitHubContentProvider.ts
index 6142b7a6f..c406be440 100644
--- a/src/GitHub/GitHubContentProvider.ts
+++ b/src/GitHub/GitHubContentProvider.ts
@@ -41,7 +41,7 @@ export class GitHubContentProvider implements IContentProvider {
return this.createSuccessAjaxResponse(HttpStatusCodes.NoContent, undefined);
} catch (error) {
- Logger.logError(error, "GitHubContentProvider/remove", error.errno);
+ Logger.logError(getErrorMessage(error), "GitHubContentProvider/remove", error.errno);
return this.createErrorAjaxResponse(error);
}
})
@@ -65,7 +65,7 @@ export class GitHubContentProvider implements IContentProvider {
return this.createSuccessAjaxResponse(HttpStatusCodes.OK, this.createContentModel(uri, content.data, params));
} catch (error) {
- Logger.logError(error, "GitHubContentProvider/get", error.errno);
+ Logger.logError(getErrorMessage(error), "GitHubContentProvider/get", error.errno);
return this.createErrorAjaxResponse(error);
}
})
@@ -105,7 +105,7 @@ export class GitHubContentProvider implements IContentProvider {
this.createContentModel(newUri, gitHubFile, { content: 0 })
);
} catch (error) {
- Logger.logError(error, "GitHubContentProvider/update", error.errno);
+ Logger.logError(getErrorMessage(error), "GitHubContentProvider/update", error.errno);
return this.createErrorAjaxResponse(error);
}
})
@@ -182,7 +182,7 @@ export class GitHubContentProvider implements IContentProvider {
this.createContentModel(newUri, newGitHubFile, { content: 0 })
);
} catch (error) {
- Logger.logError(error, "GitHubContentProvider/create", error.errno);
+ Logger.logError(getErrorMessage(error), "GitHubContentProvider/create", error.errno);
return this.createErrorAjaxResponse(error);
}
})
@@ -260,7 +260,7 @@ export class GitHubContentProvider implements IContentProvider {
this.createContentModel(uri, gitHubFile, { content: 0 })
);
} catch (error) {
- Logger.logError(error, "GitHubContentProvider/update", error.errno);
+ Logger.logError(getErrorMessage(error), "GitHubContentProvider/update", error.errno);
return this.createErrorAjaxResponse(error);
}
})
@@ -269,25 +269,25 @@ export class GitHubContentProvider implements IContentProvider {
public listCheckpoints(_: ServerConfig, path: string): Observable {
const error = new GitHubContentProviderError("Not implemented");
- Logger.logError(error, "GitHubContentProvider/listCheckpoints", error.errno);
+ Logger.logError(error.message, "GitHubContentProvider/listCheckpoints", error.errno);
return of(this.createErrorAjaxResponse(error));
}
public createCheckpoint(_: ServerConfig, path: string): Observable {
const error = new GitHubContentProviderError("Not implemented");
- Logger.logError(error, "GitHubContentProvider/createCheckpoint", error.errno);
+ Logger.logError(error.message, "GitHubContentProvider/createCheckpoint", error.errno);
return of(this.createErrorAjaxResponse(error));
}
public deleteCheckpoint(_: ServerConfig, path: string, checkpointID: string): Observable {
const error = new GitHubContentProviderError("Not implemented");
- Logger.logError(error, "GitHubContentProvider/deleteCheckpoint", error.errno);
+ Logger.logError(error.message, "GitHubContentProvider/deleteCheckpoint", error.errno);
return of(this.createErrorAjaxResponse(error));
}
public restoreFromCheckpoint(_: ServerConfig, path: string, checkpointID: string): Observable {
const error = new GitHubContentProviderError("Not implemented");
- Logger.logError(error, "GitHubContentProvider/restoreFromCheckpoint", error.errno);
+ Logger.logError(error.message, "GitHubContentProvider/restoreFromCheckpoint", error.errno);
return of(this.createErrorAjaxResponse(error));
}
diff --git a/src/GitHub/GitHubOAuthService.ts b/src/GitHub/GitHubOAuthService.ts
index 746e79152..80a6d2c42 100644
--- a/src/GitHub/GitHubOAuthService.ts
+++ b/src/GitHub/GitHubOAuthService.ts
@@ -1,6 +1,5 @@
import ko from "knockout";
import { HttpStatusCodes } from "../Common/Constants";
-import * as Logger from "../Common/Logger";
import { configContext } from "../ConfigContext";
import { AuthorizeAccessComponent } from "../Explorer/Controls/GitHub/AuthorizeAccessComponent";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
@@ -8,6 +7,7 @@ import { JunoClient } from "../Juno/JunoClient";
import { isInvalidParentFrameOrigin } from "../Utils/MessageValidation";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { GitHubConnectorMsgType, IGitHubConnectorParams } from "./GitHubConnector";
+import { handleError } from "../Common/ErrorHandlingUtils";
window.addEventListener("message", (event: MessageEvent) => {
if (isInvalidParentFrameOrigin(event)) {
@@ -99,9 +99,7 @@ export class GitHubOAuthService {
this.resetToken();
return true;
} catch (error) {
- const message = `Failed to delete app authorization: ${error}`;
- Logger.logError(message, "GitHubOAuthService/logout");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GitHubOAuthService/logout", "Failed to delete app authorization");
return false;
}
}
diff --git a/src/HostedExplorer.ts b/src/HostedExplorer.ts
index ac24eeaea..30e97cf74 100644
--- a/src/HostedExplorer.ts
+++ b/src/HostedExplorer.ts
@@ -611,7 +611,7 @@ class HostedExplorer {
return loadAccountResult;
} catch (error) {
LocalStorageUtility.removeEntry(StorageKey.DatabaseAccountId);
- Logger.logError(error, "HostedExplorer/_getAccessCached");
+ Logger.logError(getErrorMessage(error), "HostedExplorer/_getAccessCached");
throw error;
}
}
@@ -637,7 +637,7 @@ class HostedExplorer {
const accountResponse = this._getAccessAfterTenantSelection(defaultTenant.tenantId);
return accountResponse;
} catch (error) {
- Logger.logError(error, "HostedExplorer/_getAccessNew");
+ Logger.logError(getErrorMessage(error), "HostedExplorer/_getAccessNew");
throw error;
}
}
@@ -658,7 +658,7 @@ class HostedExplorer {
const keys = await this._getAccountKeysHelper(defaultAccount, true);
return [defaultAccount, keys, authToken];
} catch (error) {
- Logger.logError(error, "HostedExplorer/_getAccessAfterTenantSelection");
+ Logger.logError(getErrorMessage(error), "HostedExplorer/_getAccessAfterTenantSelection");
throw error;
}
}
@@ -1131,7 +1131,7 @@ class HostedExplorer {
});
},
error => {
- Logger.logError(error, "HostedExplorer/_onNewDirectorySelected");
+ Logger.logError(getErrorMessage(error), "HostedExplorer/_onNewDirectorySelected");
}
);
TelemetryProcessor.trace(Action.TenantSwitch);
diff --git a/src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts b/src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts
index 6e5a5d0a9..563645f25 100644
--- a/src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts
+++ b/src/NotebookWorkspaceManager/NotebookWorkspaceManager.ts
@@ -7,6 +7,7 @@ import {
NotebookWorkspaceFeedResponse
} from "../Contracts/DataModels";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
+import { getErrorMessage } from "../Common/ErrorHandlingUtils";
export class NotebookWorkspaceManager {
private resourceProviderClientFactory: IResourceProviderClientFactory;
@@ -24,7 +25,7 @@ export class NotebookWorkspaceManager {
)) as NotebookWorkspaceFeedResponse;
return response && response.value;
} catch (error) {
- Logger.logError(error, "NotebookWorkspaceManager/getNotebookWorkspacesAsync");
+ Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/getNotebookWorkspacesAsync");
throw error;
}
}
@@ -37,7 +38,7 @@ export class NotebookWorkspaceManager {
try {
return (await this.rpClient(uri).getAsync(uri, ArmApiVersions.documentDB)) as NotebookWorkspace;
} catch (error) {
- Logger.logError(error, "NotebookWorkspaceManager/getNotebookWorkspaceAsync");
+ Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/getNotebookWorkspaceAsync");
throw error;
}
}
@@ -47,7 +48,7 @@ export class NotebookWorkspaceManager {
try {
await this.rpClient(uri).putAsync(uri, ArmApiVersions.documentDB, { name: notebookWorkspaceId });
} catch (error) {
- Logger.logError(error, "NotebookWorkspaceManager/createNotebookWorkspaceAsync");
+ Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/createNotebookWorkspaceAsync");
throw error;
}
}
@@ -57,7 +58,7 @@ export class NotebookWorkspaceManager {
try {
await this.rpClient(uri).deleteAsync(uri, ArmApiVersions.documentDB);
} catch (error) {
- Logger.logError(error, "NotebookWorkspaceManager/deleteNotebookWorkspaceAsync");
+ Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/deleteNotebookWorkspaceAsync");
throw error;
}
}
@@ -74,7 +75,7 @@ export class NotebookWorkspaceManager {
undefined
);
} catch (error) {
- Logger.logError(error, "NotebookWorkspaceManager/getNotebookConnectionInfoAsync");
+ Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/getNotebookConnectionInfoAsync");
throw error;
}
}
@@ -86,7 +87,7 @@ export class NotebookWorkspaceManager {
skipResourceValidation: true
});
} catch (error) {
- Logger.logError(error, "NotebookWorkspaceManager/startNotebookWorkspaceAsync");
+ Logger.logError(getErrorMessage(error), "NotebookWorkspaceManager/startNotebookWorkspaceAsync");
throw error;
}
}
diff --git a/src/Platform/Hosted/ArmResourceUtils.ts b/src/Platform/Hosted/ArmResourceUtils.ts
index 998fbe868..dbe9a5f64 100644
--- a/src/Platform/Hosted/ArmResourceUtils.ts
+++ b/src/Platform/Hosted/ArmResourceUtils.ts
@@ -3,6 +3,7 @@ import * as Constants from "../../Common/Constants";
import * as Logger from "../../Common/Logger";
import { Tenant, Subscription, DatabaseAccount, AccountKeys } from "../../Contracts/DataModels";
import { configContext } from "../../ConfigContext";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
// TODO: 421864 - add a fetch wrapper
export abstract class ArmResourceUtils {
@@ -30,7 +31,7 @@ export abstract class ArmResourceUtils {
}
return tenants;
} catch (error) {
- Logger.logError(error, "ArmResourceUtils/listTenants");
+ Logger.logError(getErrorMessage(error), "ArmResourceUtils/listTenants");
throw error;
}
}
@@ -58,7 +59,7 @@ export abstract class ArmResourceUtils {
}
return subscriptions;
} catch (error) {
- Logger.logError(error, "ArmResourceUtils/listSubscriptions");
+ Logger.logError(getErrorMessage(error), "ArmResourceUtils/listSubscriptions");
throw error;
}
}
@@ -92,7 +93,7 @@ export abstract class ArmResourceUtils {
}
return accounts;
} catch (error) {
- Logger.logError(error, "ArmResourceUtils/listAccounts");
+ Logger.logError(getErrorMessage(error), "ArmResourceUtils/listAccounts");
throw error;
}
}
@@ -140,7 +141,7 @@ export abstract class ArmResourceUtils {
}
return result;
} catch (error) {
- Logger.logError(error, "ArmResourceUtils/getAccountKeys");
+ Logger.logError(getErrorMessage(error), "ArmResourceUtils/getAccountKeys");
throw error;
}
}
@@ -150,7 +151,7 @@ export abstract class ArmResourceUtils {
const token = await AuthHeadersUtil.getAccessToken(ArmResourceUtils._armAuthArea, tenantId);
return token;
} catch (error) {
- Logger.logError(error, "ArmResourceUtils/getAuthToken");
+ Logger.logError(getErrorMessage(error), "ArmResourceUtils/getAuthToken");
throw error;
}
}
diff --git a/src/SparkClusterManager/ArcadiaResourceManager.ts b/src/SparkClusterManager/ArcadiaResourceManager.ts
index 28a699f64..68fa2318b 100644
--- a/src/SparkClusterManager/ArcadiaResourceManager.ts
+++ b/src/SparkClusterManager/ArcadiaResourceManager.ts
@@ -1,4 +1,3 @@
-import * as ViewModels from "../Contracts/ViewModels";
import {
ArcadiaWorkspace,
ArcadiaWorkspaceFeedResponse,
@@ -10,6 +9,7 @@ import { IResourceProviderClient, IResourceProviderClientFactory } from "../Reso
import * as Logger from "../Common/Logger";
import { ResourceProviderClientFactory } from "../ResourceProvider/ResourceProviderClientFactory";
import { configContext } from "../ConfigContext";
+import { getErrorMessage } from "../Common/ErrorHandlingUtils";
export class ArcadiaResourceManager {
private resourceProviderClientFactory: IResourceProviderClientFactory;
@@ -27,7 +27,7 @@ export class ArcadiaResourceManager {
)) as ArcadiaWorkspaceFeedResponse;
return response && response.value;
} catch (error) {
- Logger.logError(error, "ArcadiaResourceManager/getWorkspaceAsync");
+ Logger.logError(getErrorMessage(error), "ArcadiaResourceManager/getWorkspaceAsync");
throw error;
}
}
@@ -37,7 +37,7 @@ export class ArcadiaResourceManager {
try {
return (await this._rpClient(uri).getAsync(uri, ArmApiVersions.arcadia)) as ArcadiaWorkspace;
} catch (error) {
- Logger.logError(error, "ArcadiaResourceManager/getWorkspaceAsync");
+ Logger.logError(getErrorMessage(error), "ArcadiaResourceManager/getWorkspaceAsync");
throw error;
}
}
@@ -56,7 +56,7 @@ export class ArcadiaResourceManager {
)) as ArcadiaWorkspaceFeedResponse;
return response && response.value;
} catch (error) {
- Logger.logError(error, "ArcadiaManager/listWorkspacesAsync");
+ Logger.logError(getErrorMessage(error), "ArcadiaManager/listWorkspacesAsync");
throw error;
}
}
@@ -68,7 +68,7 @@ export class ArcadiaResourceManager {
const response = (await this._rpClient(uri).getAsync(uri, ArmApiVersions.arcadia)) as SparkPoolFeedResponse;
return response && response.value;
} catch (error) {
- Logger.logError(error, "ArcadiaManager/listSparkPoolsAsync");
+ Logger.logError(getErrorMessage(error), "ArcadiaManager/listSparkPoolsAsync");
throw error;
}
}
diff --git a/src/Utils/AuthorizationUtils.ts b/src/Utils/AuthorizationUtils.ts
index 73c66d0a3..48f023dc9 100644
--- a/src/Utils/AuthorizationUtils.ts
+++ b/src/Utils/AuthorizationUtils.ts
@@ -5,6 +5,7 @@ import { AuthType } from "../AuthType";
import * as Logger from "../Common/Logger";
import { configContext, Platform } from "../ConfigContext";
import { userContext } from "../UserContext";
+import { getErrorMessage } from "../Common/ErrorHandlingUtils";
export function getAuthorizationHeader(): ViewModels.AuthorizationTokenHeaderMetadata {
if (window.authType === AuthType.EncryptedToken) {
@@ -28,7 +29,7 @@ export async function getArcadiaAuthToken(
const token = await AuthHeadersUtil.getAccessToken(arcadiaEndpoint, tenantId);
return token;
} catch (error) {
- Logger.logError(error, "AuthorizationUtils/getArcadiaAuthToken");
+ Logger.logError(getErrorMessage(error), "AuthorizationUtils/getArcadiaAuthToken");
throw error;
}
}
diff --git a/src/Utils/GalleryUtils.ts b/src/Utils/GalleryUtils.ts
index 723b04144..99dee0f07 100644
--- a/src/Utils/GalleryUtils.ts
+++ b/src/Utils/GalleryUtils.ts
@@ -1,7 +1,6 @@
import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import * as NotificationConsoleUtils from "./NotificationConsoleUtils";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
-import * as Logger from "../Common/Logger";
import {
GalleryTab,
SortBy,
@@ -10,6 +9,7 @@ import {
import Explorer from "../Explorer/Explorer";
import { IChoiceGroupOption, IChoiceGroupProps } from "office-ui-fabric-react";
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
+import { handleError } from "../Common/ErrorHandlingUtils";
const defaultSelectedAbuseCategory = "Other";
const abuseCategories: IChoiceGroupOption[] = [
@@ -122,9 +122,11 @@ export function reportAbuse(
);
onComplete(response.data);
} catch (error) {
- const message = `Failed to submit report on ${data.name} violating code of conduct: ${error}`;
- Logger.logError(message, "GalleryUtils/reportAbuse");
- NotificationConsoleUtils.logConsoleInfo(message);
+ handleError(
+ error,
+ "GalleryUtils/reportAbuse",
+ `Failed to submit report on ${data.name} violating code of conduct`
+ );
}
clearSubmitReportNotification();
@@ -185,9 +187,7 @@ export function downloadItem(
onComplete(increaseDownloadResponse.data);
}
} catch (error) {
- const message = `Failed to download ${data.name}: ${error}`;
- Logger.logError(message, "GalleryUtils/downloadItem");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GalleryUtils/downloadItem", `Failed to download ${data.name}`);
}
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
@@ -212,9 +212,7 @@ export async function favoriteItem(
onComplete(response.data);
} catch (error) {
- const message = `Failed to favorite ${data.name}: ${error}`;
- Logger.logError(message, "GalleryUtils/favoriteItem");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GalleryUtils/favoriteItem", `Failed to favorite ${data.name}`);
}
}
}
@@ -234,9 +232,7 @@ export async function unfavoriteItem(
onComplete(response.data);
} catch (error) {
- const message = `Failed to unfavorite ${data.name}: ${error}`;
- Logger.logError(message, "GalleryUtils/unfavoriteItem");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GalleryUtils/unfavoriteItem", `Failed to unfavorite ${data.name}`);
}
}
}
@@ -268,9 +264,7 @@ export function deleteItem(
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Info, `Successfully removed ${name} from gallery`);
onComplete(response.data);
} catch (error) {
- const message = `Failed to remove ${name} from gallery: ${error}`;
- Logger.logError(message, "GalleryUtils/deleteItem");
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
+ handleError(error, "GalleryUtils/deleteItem", `Failed to remove ${name} from gallery`);
}
NotificationConsoleUtils.clearInProgressMessageWithId(notificationId);
diff --git a/src/Utils/NotebookConfigurationUtils.ts b/src/Utils/NotebookConfigurationUtils.ts
index 815fa435e..20a10fc76 100644
--- a/src/Utils/NotebookConfigurationUtils.ts
+++ b/src/Utils/NotebookConfigurationUtils.ts
@@ -1,5 +1,6 @@
import * as DataModels from "../Contracts/DataModels";
import * as Logger from "../Common/Logger";
+import { getErrorMessage } from "../Common/ErrorHandlingUtils";
interface KernelConnectionMetadata {
name: string;
@@ -78,13 +79,13 @@ export class NotebookConfigurationUtils {
if (!response.ok) {
const responseMessage = await response.json();
Logger.logError(
- JSON.stringify(responseMessage),
+ getErrorMessage(responseMessage),
"NotebookConfigurationUtils/configureServiceEndpoints",
response.status
);
}
} catch (error) {
- Logger.logError(error, "NotebookConfigurationUtils/configureServiceEndpoints");
+ Logger.logError(getErrorMessage(error), "NotebookConfigurationUtils/configureServiceEndpoints");
}
}
}
From 53a8cea95e5ded17525895cc5ca1aa0c1d1555d3 Mon Sep 17 00:00:00 2001
From: Tanuj Mittal
Date: Wed, 4 Nov 2020 16:11:36 -0800
Subject: [PATCH 07/13] Hide Settings for Cassandra Serverless accounts (#311)
In case of Serverless Cassandra accounts we don't have any Settings to tweak in the Settings Tab. So this change hides the option to open Settings tab in those cases.
---
src/Explorer/Tree/ResourceTreeAdapter.tsx | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx
index 6ef9f5623..b5bf40448 100644
--- a/src/Explorer/Tree/ResourceTreeAdapter.tsx
+++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx
@@ -280,12 +280,14 @@ export class ResourceTreeAdapter implements ReactAdapter {
contextMenu: ResourceTreeContextMenuButtonFactory.createCollectionContextMenuButton(this.container, collection)
});
- children.push({
- label: database.isDatabaseShared() || this.container.isServerlessEnabled() ? "Settings" : "Scale & Settings",
- onClick: collection.onSettingsClick.bind(collection),
- isSelected: () =>
- this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings])
- });
+ if (!this.container.isPreferredApiCassandra() || !this.container.isServerlessEnabled()) {
+ children.push({
+ label: database.isDatabaseShared() || this.container.isServerlessEnabled() ? "Settings" : "Scale & Settings",
+ onClick: collection.onSettingsClick.bind(collection),
+ isSelected: () =>
+ this.isDataNodeSelected(collection.databaseId, collection.id(), [ViewModels.CollectionTabKind.Settings])
+ });
+ }
if (ResourceTreeAdapter.showScriptNodes(this.container)) {
children.push(this.buildStoredProcedureNode(collection));
From 79dec6a8a8ac9da17ff80bf50cd95f61579c952c Mon Sep 17 00:00:00 2001
From: victor-meng <56978073+victor-meng@users.noreply.github.com>
Date: Thu, 5 Nov 2020 20:02:57 -0800
Subject: [PATCH 08/13] Refactor error handling in data explorer Part 3 (#315)
- Make sure we pass the error message string instead of an error object when we call `TelemetryProcessor.traceFailure` since TelemetryProcessor will call `JSON.stringify` on the error object which would result in an empty object
- Removed ErrorParserUtility since it only works on specific error types. We can just log the full error message and manually derive information we need from the message.
- Added option to include stack trace in `getErrorMessage`. This is useful for figuring out where the client side script errors are coming from.
- Some minor refactors
---
.eslintignore | 2 -
src/Common/ErrorHandlingUtils.ts | 4 ++
src/Common/ErrorParserUtility.test.ts | 24 --------
src/Common/ErrorParserUtility.ts | 55 -------------------
src/Common/QueriesClient.ts | 13 ++---
src/Contracts/DataModels.ts | 12 ----
.../QueriesGridComponent.tsx | 5 +-
.../Controls/Settings/SettingsComponent.tsx | 12 ++--
src/Explorer/Explorer.ts | 31 +++++++----
src/Explorer/Panes/AddCollectionPane.ts | 6 +-
src/Explorer/Panes/AddDatabasePane.ts | 17 +++---
src/Explorer/Panes/BrowseQueriesPane.ts | 5 +-
.../Panes/CassandraAddCollectionPane.ts | 9 ++-
.../Panes/DeleteCollectionConfirmationPane.ts | 14 +++--
.../Panes/DeleteDatabaseConfirmationPane.ts | 15 ++---
src/Explorer/Panes/SaveQueryPane.ts | 8 ++-
src/Explorer/Panes/SetupNotebooksPane.ts | 5 +-
src/Explorer/Panes/UploadItemsPane.ts | 8 +--
src/Explorer/Panes/UploadItemsPaneAdapter.tsx | 8 +--
.../DataTable/TableEntityListViewModel.ts | 18 ++----
src/Explorer/Tabs/ConflictsTab.ts | 33 ++++++-----
src/Explorer/Tabs/DatabaseSettingsTab.ts | 8 ++-
src/Explorer/Tabs/DocumentsTab.ts | 42 ++++++++------
src/Explorer/Tabs/MongoDocumentsTab.ts | 28 ++++++----
src/Explorer/Tabs/QueryTab.html | 18 +++---
src/Explorer/Tabs/QueryTab.ts | 28 ++++------
src/Explorer/Tabs/SettingsTab.ts | 5 +-
src/Explorer/Tabs/SettingsTabV2.tsx | 9 +--
src/Explorer/Tabs/StoredProcedureTab.ts | 11 +++-
src/Explorer/Tabs/TriggerTab.ts | 9 ++-
src/Explorer/Tabs/UserDefinedFunctionTab.ts | 9 ++-
src/Explorer/Tree/Collection.ts | 9 ++-
src/Explorer/Tree/Database.ts | 5 +-
33 files changed, 218 insertions(+), 267 deletions(-)
delete mode 100644 src/Common/ErrorParserUtility.test.ts
delete mode 100644 src/Common/ErrorParserUtility.ts
diff --git a/.eslintignore b/.eslintignore
index 65d47bd0e..5d9d08948 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -15,8 +15,6 @@ src/Common/DeleteFeedback.ts
src/Common/DocumentClientUtilityBase.ts
src/Common/EditableUtility.ts
src/Common/EnvironmentUtility.ts
-src/Common/ErrorParserUtility.test.ts
-src/Common/ErrorParserUtility.ts
src/Common/HashMap.test.ts
src/Common/HashMap.ts
src/Common/HeadersUtility.test.ts
diff --git a/src/Common/ErrorHandlingUtils.ts b/src/Common/ErrorHandlingUtils.ts
index 2e2f51365..f384f6b73 100644
--- a/src/Common/ErrorHandlingUtils.ts
+++ b/src/Common/ErrorHandlingUtils.ts
@@ -26,6 +26,10 @@ export const getErrorMessage = (error: string | Error): string => {
return replaceKnownError(errorMessage);
};
+export const getErrorStack = (error: string | Error): string => {
+ return typeof error === "string" ? undefined : error.stack;
+};
+
const sendNotificationForError = (errorMessage: string, errorCode: number | string): void => {
if (errorCode === HttpStatusCodes.Forbidden) {
if (errorMessage?.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
diff --git a/src/Common/ErrorParserUtility.test.ts b/src/Common/ErrorParserUtility.test.ts
deleted file mode 100644
index a08c5f575..000000000
--- a/src/Common/ErrorParserUtility.test.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import * as ErrorParserUtility from "./ErrorParserUtility";
-
-describe("Error Parser Utility", () => {
- describe("shouldEnableCrossPartitionKeyForResourceWithPartitionKey()", () => {
- it("should parse a backend error correctly", () => {
- // A fake error matching what is thrown by the SDK on a bad collection create request
- const innerMessage =
- "The partition key component definition path '/asdwqr31 @#$#$WRadf' could not be accepted, failed near position '10'. Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
- const message = `Message: {\"Errors\":[\"${innerMessage}\"]}\r\nActivityId: 97b2e684-7505-4921-85f6-2513b9b28220, Request URI: /apps/89fdcf25-2a0b-4d2a-aab6-e161e565b26f/services/54911149-7bb1-4e7d-a1fa-22c8b36a4bb9/partitions/cc2a7a04-5f5a-4709-bcf7-8509b264963f/replicas/132304018743619218p, RequestStats: , SDK: Microsoft.Azure.Documents.Common/2.10.0`;
- const err = new Error(message) as any;
- err.code = 400;
- err.body = {
- code: "BadRequest",
- message
- };
- err.headers = {};
- err.activityId = "97b2e684-7505-4921-85f6-2513b9b28220";
-
- const parsedError = ErrorParserUtility.parse(err);
- expect(parsedError.length).toBe(1);
- expect(parsedError[0].message).toBe(innerMessage);
- });
- });
-});
diff --git a/src/Common/ErrorParserUtility.ts b/src/Common/ErrorParserUtility.ts
deleted file mode 100644
index 968737f5c..000000000
--- a/src/Common/ErrorParserUtility.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import * as DataModels from "../Contracts/DataModels";
-
-export function parse(err: any): DataModels.ErrorDataModel[] {
- try {
- return _parse(err);
- } catch (e) {
- return [{ message: JSON.stringify(err) }];
- }
-}
-
-function _parse(err: any): DataModels.ErrorDataModel[] {
- var normalizedErrors: DataModels.ErrorDataModel[] = [];
- if (err.message && !err.code) {
- normalizedErrors.push(err);
- } else {
- const innerErrors: any[] = _getInnerErrors(err.message);
- normalizedErrors = innerErrors.map(innerError =>
- typeof innerError === "string" ? { message: innerError } : innerError
- );
- }
-
- return normalizedErrors;
-}
-
-function _getInnerErrors(message: string): any[] {
- /*
- The backend error message has an inner-message which is a stringified object.
-
- For SQL errors, the "errors" property is an array of SqlErrorDataModel.
- Example:
- "Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
- For non-SQL errors the "Errors" propery is an array of string.
- Example:
- "Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s"
- */
-
- let innerMessage: any = null;
-
- const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, "");
- try {
- // Multi-Partition error flavor
- const regExp = /^(.*)ActivityId: (.*)/g;
- const regString = regExp.exec(singleLineMessage);
- const innerMessageString = regString[1];
- innerMessage = JSON.parse(innerMessageString);
- } catch (e) {
- // Single-partition error flavor
- const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g;
- const regString = regExp.exec(singleLineMessage);
- const innerMessageString = regString[1];
- innerMessage = JSON.parse(innerMessageString);
- }
-
- return innerMessage.errors ? innerMessage.errors : innerMessage.Errors;
-}
diff --git a/src/Common/QueriesClient.ts b/src/Common/QueriesClient.ts
index fb81d1c65..a7f28f7b7 100644
--- a/src/Common/QueriesClient.ts
+++ b/src/Common/QueriesClient.ts
@@ -13,7 +13,6 @@ import { userContext } from "../UserContext";
import { createDocument, deleteDocument, queryDocuments, queryDocumentsPage } from "./DocumentClientUtilityBase";
import { createCollection } from "./dataAccess/createCollection";
import { handleError } from "./ErrorHandlingUtils";
-import * as ErrorParserUtility from "./ErrorParserUtility";
export class QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = {
@@ -97,15 +96,11 @@ export class QueriesClient {
return Promise.resolve();
},
(error: any) => {
- let errorMessage: string;
- const parsedError: DataModels.ErrorDataModel = ErrorParserUtility.parse(error)[0];
- if (parsedError.code === HttpStatusCodes.Conflict.toString()) {
- errorMessage = `Query ${query.queryName} already exists`;
- } else {
- errorMessage = parsedError.message;
+ if (error.code === HttpStatusCodes.Conflict.toString()) {
+ error = `Query ${query.queryName} already exists`;
}
- handleError(errorMessage, "saveQuery", `Failed to save query ${query.queryName}`);
- return Promise.reject(errorMessage);
+ handleError(error, "saveQuery", `Failed to save query ${query.queryName}`);
+ return Promise.reject(error);
}
)
.finally(() => NotificationConsoleUtils.clearInProgressMessageWithId(id));
diff --git a/src/Contracts/DataModels.ts b/src/Contracts/DataModels.ts
index b1a467b63..3aadf14cd 100644
--- a/src/Contracts/DataModels.ts
+++ b/src/Contracts/DataModels.ts
@@ -216,18 +216,6 @@ export interface UniqueKey {
paths: string[];
}
-// Returned by DocumentDb client proxy
-// Inner errors in BackendErrorDataModel when error is in SQL syntax
-export interface ErrorDataModel {
- message: string;
- severity?: string;
- location?: {
- start: string;
- end: string;
- };
- code?: string;
-}
-
export interface CreateDatabaseAndCollectionRequest {
databaseId: string;
collectionId: string;
diff --git a/src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponent.tsx b/src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponent.tsx
index 636736577..2b732ce89 100644
--- a/src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponent.tsx
+++ b/src/Explorer/Controls/QueriesGridReactComponent/QueriesGridComponent.tsx
@@ -28,6 +28,7 @@ import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcesso
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
import { QueriesClient } from "../../../Common/QueriesClient";
+import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
export interface QueriesGridComponentProps {
queriesClient: QueriesClient;
@@ -244,7 +245,9 @@ export class QueriesGridComponent extends React.Component {
+ error => {
if (resourceTreeStartKey != null) {
TelemetryProcessor.traceFailure(
Action.LoadResourceTree,
@@ -1484,7 +1485,8 @@ export default class Explorer {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
- error: reason
+ error: getErrorMessage(error),
+ errorStack: getErrorStack(error)
},
resourceTreeStartKey
);
@@ -1689,7 +1691,10 @@ export default class Explorer {
TelemetryProcessor.traceSuccess(Action.ResetNotebookWorkspace);
} catch (error) {
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, `Failed to reset notebook workspace: ${error}`);
- TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, error);
+ TelemetryProcessor.traceFailure(Action.ResetNotebookWorkspace, {
+ error: getErrorMessage(error),
+ errorStack: getErrorStack(error)
+ });
throw error;
} finally {
NotificationConsoleUtils.clearInProgressMessageWithId(id);
@@ -2056,7 +2061,8 @@ export default class Explorer {
databaseAccountName: this.databaseAccount() && this.databaseAccount().name,
defaultExperience: this.defaultExperience && this.defaultExperience(),
dataExplorerArea: Constants.Areas.ResourceTree,
- trace: getErrorMessage(error)
+ error: getErrorMessage(error),
+ errorStack: getErrorStack(error)
},
startKey
);
@@ -2718,16 +2724,17 @@ export default class Explorer {
return this.openNotebook(newFile);
})
.then(() => this.resourceTree.triggerRender())
- .catch((reason: any) => {
- const error = `Failed to create a new notebook: ${reason}`;
- NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, error);
+ .catch((error: any) => {
+ const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`;
+ NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, errorMessage);
TelemetryProcessor.traceFailure(
Action.CreateNewNotebook,
{
databaseAccountName: this.databaseAccount().name,
defaultExperience: this.defaultExperience(),
dataExplorerArea: Constants.Areas.Notebook,
- error
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
diff --git a/src/Explorer/Panes/AddCollectionPane.ts b/src/Explorer/Panes/AddCollectionPane.ts
index 8c38a7809..204435904 100644
--- a/src/Explorer/Panes/AddCollectionPane.ts
+++ b/src/Explorer/Panes/AddCollectionPane.ts
@@ -3,7 +3,6 @@ import * as AddCollectionUtility from "../../Shared/AddCollectionUtility";
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import * as ko from "knockout";
import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants";
@@ -15,7 +14,7 @@ import { configContext, Platform } from "../../ConfigContext";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { DynamicListItem } from "../Controls/DynamicList/DynamicListComponent";
import { createCollection } from "../../Common/dataAccess/createCollection";
-import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export interface AddCollectionPaneOptions extends ViewModels.PaneOptions {
isPreferredApiTable: ko.Computed;
@@ -912,7 +911,8 @@ export default class AddCollectionPane extends ContextualPaneBase {
flight: this.container.flight()
},
dataExplorerArea: Constants.Areas.ContextualPane,
- error: errorMessage
+ error: errorMessage,
+ errorStack: getErrorStack(error)
};
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
}
diff --git a/src/Explorer/Panes/AddDatabasePane.ts b/src/Explorer/Panes/AddDatabasePane.ts
index b91496161..2010a1c60 100644
--- a/src/Explorer/Panes/AddDatabasePane.ts
+++ b/src/Explorer/Panes/AddDatabasePane.ts
@@ -1,7 +1,6 @@
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import * as ko from "knockout";
import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants";
@@ -12,6 +11,7 @@ import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstan
import { ContextualPaneBase } from "./ContextualPaneBase";
import { createDatabase } from "../../Common/dataAccess/createDatabase";
import { configContext, Platform } from "../../ConfigContext";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export default class AddDatabasePane extends ContextualPaneBase {
public defaultExperience: ko.Computed;
@@ -306,8 +306,8 @@ export default class AddDatabasePane extends ContextualPaneBase {
(database: DataModels.Database) => {
this._onCreateDatabaseSuccess(offerThroughput, startKey);
},
- (reason: any) => {
- this._onCreateDatabaseFailure(reason, offerThroughput, reason);
+ (error: any) => {
+ this._onCreateDatabaseFailure(error, offerThroughput, startKey);
}
);
}
@@ -356,11 +356,11 @@ export default class AddDatabasePane extends ContextualPaneBase {
this.resetData();
}
- private _onCreateDatabaseFailure(reason: any, offerThroughput: number, startKey: number): void {
+ private _onCreateDatabaseFailure(error: any, offerThroughput: number, startKey: number): void {
this.isExecuting(false);
- const message = ErrorParserUtility.parse(reason);
- this.formErrors(message[0].message);
- this.formErrorsDetails(message[0].message);
+ const errorMessage = getErrorMessage(error);
+ this.formErrors(errorMessage);
+ this.formErrorsDetails(errorMessage);
const addDatabasePaneFailedMessage = {
databaseAccountName: this.container.databaseAccount().name,
defaultExperience: this.container.defaultExperience(),
@@ -375,7 +375,8 @@ export default class AddDatabasePane extends ContextualPaneBase {
flight: this.container.flight()
},
dataExplorerArea: Constants.Areas.ContextualPane,
- error: reason
+ error: errorMessage,
+ errorStack: getErrorStack(error)
};
TelemetryProcessor.traceFailure(Action.CreateDatabase, addDatabasePaneFailedMessage, startKey);
}
diff --git a/src/Explorer/Panes/BrowseQueriesPane.ts b/src/Explorer/Panes/BrowseQueriesPane.ts
index af3ef0a15..06ad64eef 100644
--- a/src/Explorer/Panes/BrowseQueriesPane.ts
+++ b/src/Explorer/Panes/BrowseQueriesPane.ts
@@ -7,7 +7,7 @@ import * as Logger from "../../Common/Logger";
import { QueriesGridComponentAdapter } from "../Controls/QueriesGridReactComponent/QueriesGridComponentAdapter";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import QueryTab from "../Tabs/QueryTab";
-import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export class BrowseQueriesPane extends ContextualPaneBase {
public queriesGridComponentAdapter: QueriesGridComponentAdapter;
@@ -69,7 +69,8 @@ export class BrowseQueriesPane extends ContextualPaneBase {
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(),
- error: errorMessage
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
diff --git a/src/Explorer/Panes/CassandraAddCollectionPane.ts b/src/Explorer/Panes/CassandraAddCollectionPane.ts
index 5ba82c627..1fa823a75 100644
--- a/src/Explorer/Panes/CassandraAddCollectionPane.ts
+++ b/src/Explorer/Panes/CassandraAddCollectionPane.ts
@@ -13,6 +13,7 @@ import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { HashMap } from "../../Common/HashMap";
import { configContext, Platform } from "../../ConfigContext";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export default class CassandraAddCollectionPane extends ContextualPaneBase {
public createTableQuery: ko.Observable;
@@ -429,8 +430,9 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
};
TelemetryProcessor.traceSuccess(Action.CreateCollection, addCollectionPaneSuccessMessage, startKey);
},
- reason => {
- this.formErrors(reason.exceptionMessage);
+ error => {
+ const errorMessage = getErrorMessage(error);
+ this.formErrors(errorMessage);
this.isExecuting(false);
const addCollectionPaneFailedMessage = {
databaseAccountName: this.container.databaseAccount().name,
@@ -456,7 +458,8 @@ export default class CassandraAddCollectionPane extends ContextualPaneBase {
toCreateKeyspace: toCreateKeyspace,
createKeyspaceQuery: createKeyspaceQuery,
createTableQuery: createTableQuery,
- error: reason
+ error: errorMessage,
+ errorStack: getErrorStack(error)
};
TelemetryProcessor.traceFailure(Action.CreateCollection, addCollectionPaneFailedMessage, startKey);
}
diff --git a/src/Explorer/Panes/DeleteCollectionConfirmationPane.ts b/src/Explorer/Panes/DeleteCollectionConfirmationPane.ts
index 5af4cd9d4..2c5e67e1e 100644
--- a/src/Explorer/Panes/DeleteCollectionConfirmationPane.ts
+++ b/src/Explorer/Panes/DeleteCollectionConfirmationPane.ts
@@ -3,7 +3,6 @@ import Q from "q";
import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
@@ -12,6 +11,7 @@ import DeleteFeedback from "../../Common/DeleteFeedback";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { deleteCollection } from "../../Common/dataAccess/deleteCollection";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export default class DeleteCollectionConfirmationPane extends ContextualPaneBase {
public collectionIdConfirmationText: ko.Observable;
@@ -99,11 +99,11 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
this.containerDeleteFeedback("");
}
},
- (reason: any) => {
+ (error: any) => {
this.isExecuting(false);
- const message = ErrorParserUtility.parse(reason);
- this.formErrors(message[0].message);
- this.formErrorsDetails(message[0].message);
+ const errorMessage = getErrorMessage(error);
+ this.formErrors(errorMessage);
+ this.formErrorsDetails(errorMessage);
TelemetryProcessor.traceFailure(
Action.DeleteCollection,
{
@@ -111,7 +111,9 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
defaultExperience: this.container.defaultExperience(),
collectionId: selectedCollection.id(),
dataExplorerArea: Constants.Areas.ContextualPane,
- paneTitle: this.title()
+ paneTitle: this.title(),
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
diff --git a/src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts b/src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
index 980d41ee8..4ec595601 100644
--- a/src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
+++ b/src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
@@ -3,7 +3,6 @@ import Q from "q";
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import { CassandraAPIDataClient } from "../Tables/TableDataClient";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
@@ -14,6 +13,7 @@ import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils"
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { deleteDatabase } from "../../Common/dataAccess/deleteDatabase";
import { ARMError } from "../../Utils/arm/request";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
public databaseIdConfirmationText: ko.Observable;
@@ -108,12 +108,11 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
this.databaseDeleteFeedback("");
}
},
- (reason: unknown) => {
+ (error: any) => {
this.isExecuting(false);
-
- const message = reason instanceof ARMError ? reason.message : ErrorParserUtility.parse(reason)[0].message;
- this.formErrors(message);
- this.formErrorsDetails(message);
+ const errorMessage = getErrorMessage(error);
+ this.formErrors(errorMessage);
+ this.formErrorsDetails(errorMessage);
TelemetryProcessor.traceFailure(
Action.DeleteDatabase,
{
@@ -121,7 +120,9 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase {
defaultExperience: this.container.defaultExperience(),
databaseId: selectedDatabase.id(),
dataExplorerArea: Constants.Areas.ContextualPane,
- paneTitle: this.title()
+ paneTitle: this.title(),
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
diff --git a/src/Explorer/Panes/SaveQueryPane.ts b/src/Explorer/Panes/SaveQueryPane.ts
index b379f6d3a..3ddc6756d 100644
--- a/src/Explorer/Panes/SaveQueryPane.ts
+++ b/src/Explorer/Panes/SaveQueryPane.ts
@@ -8,7 +8,7 @@ import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsol
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import QueryTab from "../Tabs/QueryTab";
-import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export class SaveQueryPane extends ContextualPaneBase {
public queryName: ko.Observable;
@@ -98,7 +98,8 @@ export class SaveQueryPane extends ContextualPaneBase {
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
- error: errorMessage
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
@@ -140,7 +141,8 @@ export class SaveQueryPane extends ContextualPaneBase {
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.ContextualPane,
paneTitle: this.title(),
- error: errorMessage
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
diff --git a/src/Explorer/Panes/SetupNotebooksPane.ts b/src/Explorer/Panes/SetupNotebooksPane.ts
index a95bcb667..8f00572ea 100644
--- a/src/Explorer/Panes/SetupNotebooksPane.ts
+++ b/src/Explorer/Panes/SetupNotebooksPane.ts
@@ -6,7 +6,7 @@ import { ContextualPaneBase } from "./ContextualPaneBase";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import * as ko from "knockout";
-import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export class SetupNotebooksPane extends ContextualPaneBase {
private description: ko.Observable;
@@ -94,7 +94,8 @@ export class SetupNotebooksPane extends ContextualPaneBase {
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(),
- error: errorMessage
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
diff --git a/src/Explorer/Panes/UploadItemsPane.ts b/src/Explorer/Panes/UploadItemsPane.ts
index 7b1f227a9..a1d4255eb 100644
--- a/src/Explorer/Panes/UploadItemsPane.ts
+++ b/src/Explorer/Panes/UploadItemsPane.ts
@@ -4,8 +4,8 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import { UploadDetailsRecord, UploadDetails } from "../../workers/upload/definitions";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
const UPLOAD_FILE_SIZE_LIMIT = 2097152;
@@ -61,9 +61,9 @@ export class UploadItemsPane extends ContextualPaneBase {
this._resetFileInput();
},
(error: any) => {
- const message = ErrorParserUtility.parse(error);
- this.formErrors(message[0].message);
- this.formErrorsDetails(message[0].message);
+ const errorMessage = getErrorMessage(error);
+ this.formErrors(errorMessage);
+ this.formErrorsDetails(errorMessage);
}
)
.finally(() => {
diff --git a/src/Explorer/Panes/UploadItemsPaneAdapter.tsx b/src/Explorer/Panes/UploadItemsPaneAdapter.tsx
index 4c78501cc..37e514c61 100644
--- a/src/Explorer/Panes/UploadItemsPaneAdapter.tsx
+++ b/src/Explorer/Panes/UploadItemsPaneAdapter.tsx
@@ -1,4 +1,3 @@
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import * as ko from "knockout";
import * as NotificationConsoleUtils from "../../Utils/NotificationConsoleUtils";
import * as React from "react";
@@ -9,6 +8,7 @@ import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import { UploadDetailsRecord, UploadDetails } from "../../workers/upload/definitions";
import { UploadItemsPaneComponent, UploadItemsPaneProps } from "./UploadItemsPaneComponent";
import Explorer from "../Explorer";
+import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
const UPLOAD_FILE_SIZE_LIMIT = 2097152;
@@ -107,9 +107,9 @@ export class UploadItemsPaneAdapter implements ReactAdapter {
this.selectedFilesTitle = "";
},
error => {
- const message = ErrorParserUtility.parse(error);
- this.formError = message[0].message;
- this.formErrorDetail = message[0].message;
+ const errorMessage = getErrorMessage(error);
+ this.formError = errorMessage;
+ this.formErrorDetail = errorMessage;
}
)
.finally(() => {
diff --git a/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts b/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
index 0ae172404..1991d7012 100644
--- a/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
+++ b/src/Explorer/Tables/DataTable/TableEntityListViewModel.ts
@@ -16,9 +16,9 @@ import * as Entities from "../Entities";
import QueryTablesTab from "../../Tabs/QueryTablesTab";
import * as TableEntityProcessor from "../TableEntityProcessor";
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
-import * as ErrorParserUtility from "../../../Common/ErrorParserUtility";
import * as DataModels from "../../../Contracts/DataModels";
import * as ViewModels from "../../../Contracts/ViewModels";
+import { getErrorMessage, getErrorStack } from "../../../Common/ErrorHandlingUtils";
interface IListTableEntitiesSegmentedResult extends Entities.IListTableEntitiesResult {
ExceedMaximumRetries?: boolean;
@@ -387,17 +387,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
}
})
.catch((error: any) => {
- const parsedErrors = ErrorParserUtility.parse(error);
- var errors = parsedErrors.map((error: DataModels.ErrorDataModel) => {
- return {
- message: error.message,
- start: error.location ? error.location.start : undefined,
- end: error.location ? error.location.end : undefined,
- code: error.code,
- severity: error.severity
- };
- });
- this.queryErrorMessage(errors[0].message);
+ const errorMessage = getErrorMessage(error);
+ this.queryErrorMessage(errorMessage);
if (this.queryTablesTab.onLoadStartKey != null && this.queryTablesTab.onLoadStartKey != undefined) {
TelemetryProcessor.traceFailure(
Action.Tab,
@@ -408,7 +399,8 @@ export default class TableEntityListViewModel extends DataTableViewModel {
defaultExperience: this.queryTablesTab.collection.container.defaultExperience(),
dataExplorerArea: Areas.Tab,
tabTitle: this.queryTablesTab.tabTitle(),
- error: error
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
this.queryTablesTab.onLoadStartKey
);
diff --git a/src/Explorer/Tabs/ConflictsTab.ts b/src/Explorer/Tabs/ConflictsTab.ts
index a5c188805..bbe5370c0 100644
--- a/src/Explorer/Tabs/ConflictsTab.ts
+++ b/src/Explorer/Tabs/ConflictsTab.ts
@@ -6,7 +6,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { AccessibleVerticalList } from "../Tree/AccessibleVerticalList";
import { KeyCodes } from "../../Common/Constants";
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import ConflictId from "../Tree/ConflictId";
import editable from "../../Common/EditableUtility";
import * as HeadersUtility from "../../Common/HeadersUtility";
@@ -28,6 +27,7 @@ import {
updateDocument
} from "../../Common/DocumentClientUtilityBase";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export default class ConflictsTab extends TabsBase {
public selectedConflictId: ko.Observable;
@@ -241,9 +241,8 @@ export default class ConflictsTab extends TabsBase {
return this.loadNextPage();
}
)
- .catch(reason => {
- const message = ErrorParserUtility.parse(reason)[0].message;
- window.alert(message);
+ .catch(error => {
+ window.alert(getErrorMessage(error));
});
}
@@ -336,10 +335,10 @@ export default class ConflictsTab extends TabsBase {
);
});
},
- reason => {
+ error => {
this.isExecutionError(true);
- const message = ErrorParserUtility.parse(reason)[0].message;
- window.alert(message);
+ const errorMessage = getErrorMessage(error);
+ window.alert(errorMessage);
TelemetryProcessor.traceFailure(
Action.ResolveConflict,
{
@@ -349,7 +348,9 @@ export default class ConflictsTab extends TabsBase {
tabTitle: this.tabTitle(),
conflictResourceType: selectedConflict.resourceType,
conflictOperationType: selectedConflict.operationType,
- conflictResourceId: selectedConflict.resourceId
+ conflictResourceId: selectedConflict.resourceId,
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
@@ -396,10 +397,10 @@ export default class ConflictsTab extends TabsBase {
startKey
);
},
- reason => {
+ error => {
this.isExecutionError(true);
- const message = ErrorParserUtility.parse(reason)[0].message;
- window.alert(message);
+ const errorMessage = getErrorMessage(error);
+ window.alert(errorMessage);
TelemetryProcessor.traceFailure(
Action.DeleteConflict,
{
@@ -409,7 +410,9 @@ export default class ConflictsTab extends TabsBase {
tabTitle: this.tabTitle(),
conflictResourceType: selectedConflict.resourceType,
conflictOperationType: selectedConflict.operationType,
- conflictResourceId: selectedConflict.resourceId
+ conflictResourceId: selectedConflict.resourceId,
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
@@ -470,7 +473,8 @@ export default class ConflictsTab extends TabsBase {
defaultExperience: this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
- error: error
+ error: getErrorMessage(error),
+ errorStack: getErrorStack(error)
},
this.onLoadStartKey
);
@@ -545,7 +549,8 @@ export default class ConflictsTab extends TabsBase {
defaultExperience: this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
- error: error
+ error: getErrorMessage(error),
+ errorStack: getErrorStack(error)
},
this.onLoadStartKey
);
diff --git a/src/Explorer/Tabs/DatabaseSettingsTab.ts b/src/Explorer/Tabs/DatabaseSettingsTab.ts
index cd32dc301..085d7d3be 100644
--- a/src/Explorer/Tabs/DatabaseSettingsTab.ts
+++ b/src/Explorer/Tabs/DatabaseSettingsTab.ts
@@ -1,7 +1,6 @@
import * as AutoPilotUtils from "../../Utils/AutoPilotUtils";
import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels";
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import * as ko from "knockout";
import * as PricingUtils from "../../Utils/PricingUtils";
import * as SharedConstants from "../../Shared/Constants";
@@ -20,6 +19,7 @@ import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandBu
import { userContext } from "../../UserContext";
import { updateOfferThroughputBeyondLimit } from "../../Common/dataAccess/updateOfferThroughputBeyondLimit";
import { configContext, Platform } from "../../ConfigContext";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
const updateThroughputBeyondLimitWarningMessage: string = `
You are about to request an increase in throughput beyond the pre-allocated capacity.
@@ -490,7 +490,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
this.container.isRefreshingExplorer(false);
this.isExecutionError(true);
console.error(error);
- this.displayedError(ErrorParserUtility.parse(error)[0].message);
+ const errorMessage = getErrorMessage(error);
+ this.displayedError(errorMessage);
TelemetryProcessor.traceFailure(
Action.UpdateSettings,
{
@@ -499,7 +500,8 @@ export default class DatabaseSettingsTab extends TabsBase implements ViewModels.
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
- error: error
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
diff --git a/src/Explorer/Tabs/DocumentsTab.ts b/src/Explorer/Tabs/DocumentsTab.ts
index 368c85ada..03fd9777c 100644
--- a/src/Explorer/Tabs/DocumentsTab.ts
+++ b/src/Explorer/Tabs/DocumentsTab.ts
@@ -6,7 +6,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { AccessibleVerticalList } from "../Tree/AccessibleVerticalList";
import { KeyCodes } from "../../Common/Constants";
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import DocumentId from "../Tree/DocumentId";
import editable from "../../Common/EditableUtility";
import * as HeadersUtility from "../../Common/HeadersUtility";
@@ -32,7 +31,7 @@ import {
createDocument
} from "../../Common/DocumentClientUtilityBase";
import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent";
-import { getErrorMessage } from "../../Common/ErrorHandlingUtils";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export default class DocumentsTab extends TabsBase {
public selectedDocumentId: ko.Observable;
@@ -393,9 +392,8 @@ export default class DocumentsTab extends TabsBase {
const focusElement = document.getElementById("errorStatusIcon");
focusElement && focusElement.focus();
})
- .catch(reason => {
- const message = ErrorParserUtility.parse(reason)[0].message;
- window.alert(message);
+ .catch(error => {
+ window.alert(getErrorMessage(error));
});
}
@@ -475,17 +473,19 @@ export default class DocumentsTab extends TabsBase {
startKey
);
},
- reason => {
+ error => {
this.isExecutionError(true);
- const message = ErrorParserUtility.parse(reason)[0].message;
- window.alert(message);
+ const errorMessage = getErrorMessage(error);
+ window.alert(errorMessage);
TelemetryProcessor.traceFailure(
Action.CreateDocument,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
- tabTitle: this.tabTitle()
+ tabTitle: this.tabTitle(),
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
@@ -542,17 +542,19 @@ export default class DocumentsTab extends TabsBase {
startKey
);
},
- reason => {
+ error => {
this.isExecutionError(true);
- const message = ErrorParserUtility.parse(reason)[0].message;
- window.alert(message);
+ const errorMessage = getErrorMessage(error);
+ window.alert(errorMessage);
TelemetryProcessor.traceFailure(
Action.UpdateDocument,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
- tabTitle: this.tabTitle()
+ tabTitle: this.tabTitle(),
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
@@ -643,7 +645,8 @@ export default class DocumentsTab extends TabsBase {
defaultExperience: this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
- error: error
+ error: getErrorMessage(error),
+ errorStack: getErrorStack(error)
},
this.onLoadStartKey
);
@@ -697,16 +700,18 @@ export default class DocumentsTab extends TabsBase {
startKey
);
},
- reason => {
+ error => {
this.isExecutionError(true);
- console.error(reason);
+ console.error(error);
TelemetryProcessor.traceFailure(
Action.DeleteDocument,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
- tabTitle: this.tabTitle()
+ tabTitle: this.tabTitle(),
+ error: getErrorMessage(error),
+ errorStack: getErrorStack(error)
},
startKey
);
@@ -787,7 +792,8 @@ export default class DocumentsTab extends TabsBase {
defaultExperience: this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
- error: errorMessage
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
this.onLoadStartKey
);
diff --git a/src/Explorer/Tabs/MongoDocumentsTab.ts b/src/Explorer/Tabs/MongoDocumentsTab.ts
index 491f3adf0..e685d5b9c 100644
--- a/src/Explorer/Tabs/MongoDocumentsTab.ts
+++ b/src/Explorer/Tabs/MongoDocumentsTab.ts
@@ -4,7 +4,6 @@ import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import DocumentId from "../Tree/DocumentId";
import DocumentsTab from "./DocumentsTab";
-import * as ErrorParserUtility from "../../Common/ErrorParserUtility";
import MongoUtility from "../../Common/MongoUtility";
import ObjectId from "../Tree/ObjectId";
import Q from "q";
@@ -20,6 +19,7 @@ import {
import { extractPartitionKey } from "@azure/cosmos";
import * as Logger from "../../Common/Logger";
import { PartitionKeyDefinition } from "@azure/cosmos";
+import { getErrorMessage, getErrorStack } from "../../Common/ErrorHandlingUtils";
export default class MongoDocumentsTab extends DocumentsTab {
public collection: ViewModels.Collection;
@@ -72,7 +72,8 @@ export default class MongoDocumentsTab extends DocumentsTab {
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
- tabTitle: this.tabTitle()
+ tabTitle: this.tabTitle(),
+ error: message
},
startKey
);
@@ -114,17 +115,19 @@ export default class MongoDocumentsTab extends DocumentsTab {
startKey
);
},
- reason => {
+ error => {
this.isExecutionError(true);
- const message = ErrorParserUtility.parse(reason)[0].message;
- window.alert(message);
+ const errorMessage = getErrorMessage(error);
+ window.alert(errorMessage);
TelemetryProcessor.traceFailure(
Action.CreateDocument,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
- tabTitle: this.tabTitle()
+ tabTitle: this.tabTitle(),
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
@@ -176,17 +179,19 @@ export default class MongoDocumentsTab extends DocumentsTab {
startKey
);
},
- reason => {
+ error => {
this.isExecutionError(true);
- const message = ErrorParserUtility.parse(reason)[0].message;
- window.alert(message);
+ const errorMessage = getErrorMessage(error);
+ window.alert(errorMessage);
TelemetryProcessor.traceFailure(
Action.UpdateDocument,
{
databaseAccountName: this.collection && this.collection.container.databaseAccount().name,
defaultExperience: this.collection && this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
- tabTitle: this.tabTitle()
+ tabTitle: this.tabTitle(),
+ error: errorMessage,
+ errorStack: getErrorStack(error)
},
startKey
);
@@ -267,7 +272,8 @@ export default class MongoDocumentsTab extends DocumentsTab {
defaultExperience: this.collection.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle(),
- error: error
+ error: getErrorMessage(error),
+ errorStack: getErrorStack(error)
},
this.onLoadStartKey
);
diff --git a/src/Explorer/Tabs/QueryTab.html b/src/Explorer/Tabs/QueryTab.html
index 2f0483d03..0d16152da 100644
--- a/src/Explorer/Tabs/QueryTab.html
+++ b/src/Explorer/Tabs/QueryTab.html
@@ -47,7 +47,7 @@
-