From eca9d0470fc47f3a05d241e93b2ef4ba945718b7 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Mon, 9 Dec 2024 22:57:12 +0100 Subject: [PATCH 1/4] reporting errors as a correct TRPC Error Message --- .../api/extension-server/WebviewController.ts | 41 ++++++++++++++++++- src/webviews/api/webview-client/vscodeLink.ts | 7 +++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/webviews/api/extension-server/WebviewController.ts b/src/webviews/api/extension-server/WebviewController.ts index 38fac6222..56f259cf1 100644 --- a/src/webviews/api/extension-server/WebviewController.ts +++ b/src/webviews/api/extension-server/WebviewController.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getTRPCErrorFromUnknown } from '@trpc/server'; import * as vscode from 'vscode'; import { type API } from '../../../AzureDBExperiences'; import { appRouter, type BaseRouterContext } from '../configuration/appRouter'; @@ -95,7 +96,8 @@ export class WebviewController extends WebviewBaseController extends WebviewBaseController Date: Mon, 9 Dec 2024 22:59:06 +0100 Subject: [PATCH 2/4] added missing todo tags --- src/webviews/api/extension-server/trpc.ts | 2 +- src/webviews/api/webview-client/vscodeLink.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/webviews/api/extension-server/trpc.ts b/src/webviews/api/extension-server/trpc.ts index 9f390210c..300972ca8 100644 --- a/src/webviews/api/extension-server/trpc.ts +++ b/src/webviews/api/extension-server/trpc.ts @@ -16,7 +16,7 @@ import { callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils'; import { initTRPC } from '@trpc/server'; // eslint-disable-next-line import/no-internal-modules -import { type MiddlewareResult } from '@trpc/server/dist/unstable-core-do-not-import/middleware'; +import { type MiddlewareResult } from '@trpc/server/dist/unstable-core-do-not-import/middleware'; //TODO: the API for v11 is not stable and will change, revisit when upgrading TRPC /** * Initialization of tRPC backend. diff --git a/src/webviews/api/webview-client/vscodeLink.ts b/src/webviews/api/webview-client/vscodeLink.ts index c666b4360..c8303b718 100644 --- a/src/webviews/api/webview-client/vscodeLink.ts +++ b/src/webviews/api/webview-client/vscodeLink.ts @@ -63,7 +63,7 @@ function vscodeLink(options: VSCodeLinkOptions): TRPCLink { * Notes to maintainers: * - types of messages have been derived from node_modules/@trpc/client/src/links/types.ts * It was not straightforward to import them directly due to the use of `@trpc/server/unstable-core-do-not-import` - * Fell free to revisit once tRPC reaches version 11.0.0 + * TODO: Fell free to revisit once tRPC reaches version 11.0.0 */ // The link function required by tRPC client From ebf31458d293729e4a50eb883b6eb9f69421da7f Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 10 Dec 2024 11:14:39 +0100 Subject: [PATCH 3/4] Handling TRPCErrors in webviews --- src/webviews/api/configuration/appRouter.ts | 20 +++++ src/webviews/api/extension-server/trpc.ts | 12 ++- .../collectionView/CollectionView.tsx | 85 ++++++++++++------- .../components/toolbar/ToolbarMainView.tsx | 4 +- .../toolbar/ToolbarTableNavigation.tsx | 8 +- .../toolbar/ToolbarViewNavigation.tsx | 8 +- .../documentView/documentView.tsx | 36 ++++++-- 7 files changed, 121 insertions(+), 52 deletions(-) diff --git a/src/webviews/api/configuration/appRouter.ts b/src/webviews/api/configuration/appRouter.ts index 1a904845c..e45e9d976 100644 --- a/src/webviews/api/configuration/appRouter.ts +++ b/src/webviews/api/configuration/appRouter.ts @@ -7,6 +7,7 @@ * This a minimal tRPC server */ import { callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils'; +import * as vscode from 'vscode'; import { z } from 'zod'; import { type API } from '../../../AzureDBExperiences'; import { collectionsViewRouter as collectionViewRouter } from '../../mongoClusters/collectionView/collectionViewRouter'; @@ -88,6 +89,25 @@ const commonRouter = router({ }, ); }), + displayErrorMessage: publicProcedure + .input( + z.object({ + message: z.string(), + modal: z.boolean(), + cause: z.string(), + }), + ) + .mutation(({ input }) => { + let message = input.message; + if (input.cause && !input.modal) { + message += ` (${input.cause})`; + } + + void vscode.window.showErrorMessage(message, { + modal: input.modal, + detail: input.modal ? input.cause : undefined, // The content of the 'detail' field is only shown when modal is true + }); + }), hello: publicProcedure // This is the input schema of your procedure, no parameters .query(async () => { diff --git a/src/webviews/api/extension-server/trpc.ts b/src/webviews/api/extension-server/trpc.ts index 300972ca8..18de61a48 100644 --- a/src/webviews/api/extension-server/trpc.ts +++ b/src/webviews/api/extension-server/trpc.ts @@ -44,13 +44,17 @@ export const trpcToTelemetry = t.middleware(async ({ path, type, next }) => { const result = await next(); if (!result.ok) { - context.telemetry.properties.result = 'Failed'; - context.telemetry.properties.error = result.error.message; - /** - * we're not any error here as we just want to log it here and let the + * we're not handling any error here as we just want to log it here and let the * caller of the RPC call handle the error there. */ + + context.telemetry.properties.result = 'Failed'; + context.telemetry.properties.error = result.error.message; + context.telemetry.properties.errorStack = result.error.stack; + if (result.error.cause) { + context.telemetry.properties.errorCause = JSON.stringify(result.error.cause, null, 0); + } } return result; diff --git a/src/webviews/mongoClusters/collectionView/CollectionView.tsx b/src/webviews/mongoClusters/collectionView/CollectionView.tsx index 0dc07fe7b..9ba2ce8e7 100644 --- a/src/webviews/mongoClusters/collectionView/CollectionView.tsx +++ b/src/webviews/mongoClusters/collectionView/CollectionView.tsx @@ -109,7 +109,14 @@ export const CollectionView = (): JSX.Element => { setCurrentContext((prev) => ({ ...prev, isLoading: false, isFirstTimeLoad: false })); }) - .catch((_error) => { + .catch((error) => { + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error while running the query', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); + }) + .finally(() => { setCurrentContext((prev) => ({ ...prev, isLoading: false, isFirstTimeLoad: false })); }); }, [currentContext.currrentQueryDefinition]); @@ -181,7 +188,11 @@ export const CollectionView = (): JSX.Element => { })); }) .catch((error) => { - console.debug('Failed to perform an action:', error); + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error while loading the data', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); }); break; } @@ -195,7 +206,11 @@ export const CollectionView = (): JSX.Element => { })); }) .catch((error) => { - console.debug('Failed to perform an action:', error); + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error while loading the data', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); }); break; case Views.JSON: @@ -208,7 +223,11 @@ export const CollectionView = (): JSX.Element => { })); }) .catch((error) => { - console.debug('Failed to perform an action:', error); + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error while loading the data', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); }); break; default: @@ -223,7 +242,11 @@ export const CollectionView = (): JSX.Element => { void (await currentContextRef.current.queryEditor?.setJsonSchema(schema)); }) .catch((error) => { - console.debug('Failed to perform an action:', error); + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error while loading the autocompletion data', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); }); } @@ -259,46 +282,46 @@ export const CollectionView = (): JSX.Element => { }, })); }) - .catch((error: unknown) => { - if (error instanceof Error) { - console.error('Error deleting the document:', error.message); - } else { - console.error('Unexpected error when deleting a document:', error); - } + .catch((error) => { + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error deleting selected documents', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); }); } function handleViewDocumentRequest(): void { trpcClient.mongoClusters.collectionView.viewDocumentById .mutate(currentContext.dataSelection.selectedDocumentObjectIds[0]) - .catch((error: unknown) => { - if (error instanceof Error) { - console.error('Error opening document:', error.message); - } else { - console.error('Unexpected error opening document:', error); - } + .catch((error) => { + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error opening the document view', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); }); } function handleEditDocumentRequest(): void { trpcClient.mongoClusters.collectionView.editDocumentById .mutate(currentContext.dataSelection.selectedDocumentObjectIds[0]) - .catch((error: unknown) => { - if (error instanceof Error) { - console.error('Error opening document:', error.message); - } else { - console.error('Unexpected error opening document:', error); - } + .catch((error) => { + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error opening the document view', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); }); } function handleAddDocumentRequest(): void { - trpcClient.mongoClusters.collectionView.addDocument.mutate().catch((error: unknown) => { - if (error instanceof Error) { - console.error('Error adding document:', error.message); - } else { - console.error('Unexpected error adding document:', error); - } + trpcClient.mongoClusters.collectionView.addDocument.mutate().catch((error) => { + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error opening the document view', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); }); } @@ -340,7 +363,7 @@ export const CollectionView = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report query event:', error); + console.debug('Failed to report an event:', error); }); } @@ -371,7 +394,7 @@ export const CollectionView = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report query event:', error); + console.debug('Failed to report an event:', error); }); }} /> diff --git a/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarMainView.tsx b/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarMainView.tsx index 4a3c546c7..bcc351bf5 100644 --- a/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarMainView.tsx +++ b/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarMainView.tsx @@ -56,7 +56,7 @@ const ToolbarQueryOperations = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report query event:', error); + console.debug('Failed to report an event:', error); }); }; @@ -81,7 +81,7 @@ const ToolbarQueryOperations = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report query event:', error); + console.debug('Failed to report an event:', error); }); }; diff --git a/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarTableNavigation.tsx b/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarTableNavigation.tsx index 96d2906f4..3e5f16492 100644 --- a/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarTableNavigation.tsx +++ b/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarTableNavigation.tsx @@ -49,8 +49,8 @@ export const ToolbarTableNavigation = (): JSX.Element => { depth: newPath.length ?? 0, }, }) - .catch((_error) => { - console.debug(_error); + .catch((error) => { + console.debug('Failed to report an event:', error); }); } @@ -75,8 +75,8 @@ export const ToolbarTableNavigation = (): JSX.Element => { depth: newPath.length ?? 0, }, }) - .catch((_error) => { - console.debug(_error); + .catch((error) => { + console.debug('Failed to report an event:', error); }); } diff --git a/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarViewNavigation.tsx b/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarViewNavigation.tsx index 1630e2445..92b079e58 100644 --- a/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarViewNavigation.tsx +++ b/src/webviews/mongoClusters/collectionView/components/toolbar/ToolbarViewNavigation.tsx @@ -44,7 +44,7 @@ export const ToolbarViewNavigation = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report pagination event:', error); + console.debug('Failed to report an event:', error); }); } @@ -73,7 +73,7 @@ export const ToolbarViewNavigation = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report pagination event:', error); + console.debug('Failed to report an event:', error); }); } @@ -97,7 +97,7 @@ export const ToolbarViewNavigation = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report pagination event:', error); + console.debug('Failed to report an event:', error); }); } @@ -125,7 +125,7 @@ export const ToolbarViewNavigation = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report pagination event:', error); + console.debug('Failed to report an event:', error); }); } diff --git a/src/webviews/mongoClusters/documentView/documentView.tsx b/src/webviews/mongoClusters/documentView/documentView.tsx index e7473e61f..752f86bb1 100644 --- a/src/webviews/mongoClusters/documentView/documentView.tsx +++ b/src/webviews/mongoClusters/documentView/documentView.tsx @@ -53,11 +53,22 @@ export const DocumentView = (): JSX.Element => { const documentId: string = configuration.documentId; setIsLoading(true); - void trpcClient.mongoClusters.documentView.getDocumentById.query(documentId).then((response) => { - setContent(response); - }); - setIsLoading(false); + void trpcClient.mongoClusters.documentView.getDocumentById + .query(documentId) + .then((response) => { + setContent(response); + }) + .catch((error) => { + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error while loading the document', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); + }) + .finally(() => { + setIsLoading(false); + }); } }, []); @@ -180,6 +191,13 @@ export const DocumentView = (): JSX.Element => { documentLength = response.length ?? 0; setContent(response); }) + .catch((error) => { + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error while refreshing the document', + modal: false, + cause: error instanceof Error ? error.message : String(error), + }); + }) .finally(() => { setIsLoading(false); }); @@ -195,7 +213,7 @@ export const DocumentView = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report event:', error); + console.debug('Failed to report an event:', error); }); } @@ -221,7 +239,11 @@ export const DocumentView = (): JSX.Element => { setIsDirty(false); }) .catch((error) => { - console.debug('Error saving document:', error); + void trpcClient.common.displayErrorMessage.mutate({ + message: 'Error saving the document', + modal: true, // we want to show the error in a modal dialog as it's an important one, failed to save the document + cause: error instanceof Error ? error.message : String(error), + }); }) .finally(() => { setIsLoading(false); @@ -238,7 +260,7 @@ export const DocumentView = (): JSX.Element => { }, }) .catch((error) => { - console.debug('Failed to report event:', error); + console.debug('Failed to report an event:', error); }); } From 1cc510ce23390c7e424285845b5ec3aa530f17c2 Mon Sep 17 00:00:00 2001 From: Tomasz Naumowicz Date: Tue, 10 Dec 2024 11:20:40 +0100 Subject: [PATCH 4/4] Minor telemetry error reporting update --- src/webviews/api/extension-server/trpc.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/webviews/api/extension-server/trpc.ts b/src/webviews/api/extension-server/trpc.ts index 18de61a48..20487a7e1 100644 --- a/src/webviews/api/extension-server/trpc.ts +++ b/src/webviews/api/extension-server/trpc.ts @@ -50,7 +50,8 @@ export const trpcToTelemetry = t.middleware(async ({ path, type, next }) => { */ context.telemetry.properties.result = 'Failed'; - context.telemetry.properties.error = result.error.message; + context.telemetry.properties.error = result.error.name; + context.telemetry.properties.errorMessage = result.error.message; context.telemetry.properties.errorStack = result.error.stack; if (result.error.cause) { context.telemetry.properties.errorCause = JSON.stringify(result.error.cause, null, 0);