diff --git a/src/webviews/api/configuration/appRouter.ts b/src/webviews/api/configuration/appRouter.ts index 1a904845..e45e9d97 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/WebviewController.ts b/src/webviews/api/extension-server/WebviewController.ts index 38fac622..56f259cf 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 { 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.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); + } } return result; diff --git a/src/webviews/api/webview-client/vscodeLink.ts b/src/webviews/api/webview-client/vscodeLink.ts index 66800f2d..c8303b71 100644 --- a/src/webviews/api/webview-client/vscodeLink.ts +++ b/src/webviews/api/webview-client/vscodeLink.ts @@ -27,10 +27,13 @@ export interface VsCodeLinkResponseMessage { id: string; result?: unknown; error?: { - code?: number; + name: string; message: string; + + code?: number; + stack?: string; + cause?: unknown; data?: unknown; - name: string; }; complete?: boolean; } @@ -60,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 diff --git a/src/webviews/mongoClusters/collectionView/CollectionView.tsx b/src/webviews/mongoClusters/collectionView/CollectionView.tsx index 0dc07fe7..9ba2ce8e 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 4a3c546c..bcc351bf 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 96d2906f..3e5f1649 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 1630e244..92b079e5 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 e7473e61..752f86bb 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); }); }