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] 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 @@ -
+
Errors
@@ -56,16 +56,16 @@

Execute Query Watermark

Execute a query to see the results

-