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] 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",