From 1006617e99d86de395cc875c53c5bddd12b87253 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Sat, 1 Oct 2022 00:58:58 +0530 Subject: [PATCH] chore: add additional telemetry for teams errors --- packages/hoppscotch-app/src/components.d.ts | 10 ++++ .../src/helpers/backend/GQLClient.ts | 52 +++++++++++++++---- packages/hoppscotch-app/src/modules/sentry.ts | 31 +++++++++++ 3 files changed, 84 insertions(+), 9 deletions(-) diff --git a/packages/hoppscotch-app/src/components.d.ts b/packages/hoppscotch-app/src/components.d.ts index d3d257403..e31270467 100644 --- a/packages/hoppscotch-app/src/components.d.ts +++ b/packages/hoppscotch-app/src/components.d.ts @@ -93,6 +93,16 @@ declare module '@vue/runtime-core' { HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default'] HttpTests: typeof import('./components/http/Tests.vue')['default'] HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default'] + IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] + IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] + IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] + IconLucideInbox: typeof import('~icons/lucide/inbox')['default'] + IconLucideInfo: typeof import('~icons/lucide/info')['default'] + IconLucideLayers: typeof import('~icons/lucide/layers')['default'] + IconLucideLoader: typeof import('~icons/lucide/loader')['default'] + IconLucideMinus: typeof import('~icons/lucide/minus')['default'] + IconLucideSearch: typeof import('~icons/lucide/search')['default'] + IconLucideUsers: typeof import('~icons/lucide/users')['default'] LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default'] LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default'] LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default'] diff --git a/packages/hoppscotch-app/src/helpers/backend/GQLClient.ts b/packages/hoppscotch-app/src/helpers/backend/GQLClient.ts index 8b14006c2..a703d618a 100644 --- a/packages/hoppscotch-app/src/helpers/backend/GQLClient.ts +++ b/packages/hoppscotch-app/src/helpers/backend/GQLClient.ts @@ -11,6 +11,7 @@ import { errorExchange, CombinedError, Operation, + OperationResult, } from "@urql/core" import { authExchange } from "@urql/exchange-auth" import { devtoolsExchange } from "@urql/devtools" @@ -34,12 +35,18 @@ const BACKEND_GQL_URL = const BACKEND_WS_URL = import.meta.env.VITE_BACKEND_WS_URL ?? "wss://api.hoppscotch.io/graphql" +type GQLOpType = "query" | "mutation" | "subscription" /** * A type that defines error events that are possible during backend operations on the GQLCLient */ export type GQLClientErrorEvent = | { type: "SUBSCRIPTION_CONN_CALLBACK_ERR_REPORT"; errors: Error[] } | { type: "CLIENT_REPORTED_ERROR"; error: CombinedError; op: Operation } + | { + type: "GQL_CLIENT_REPORTED_ERROR" + opType: GQLOpType + opResult: OperationResult + } /** * A stream of the errors that occur during GQLClient operations. @@ -178,11 +185,20 @@ export const runGQLQuery = ( E.fromNullable(res.error?.message), E.match( // The left case (network error was null) - (gqlErr) => - >{ + (gqlErr) => { + if (res.error) { + gqlClientError$.next({ + type: "GQL_CLIENT_REPORTED_ERROR", + opType: "query", + opResult: res, + }) + } + + return >{ type: "gql_error", error: parseGQLErrorString(gqlErr ?? "") as DocErrorType, - }, + } + }, // The right case (it was a GraphQL Error) (networkErr) => >{ @@ -230,11 +246,20 @@ export const runGQLSubscription = < E.fromNullable(res.error?.message), E.match( // The left case (network error was null) - (gqlErr) => - >{ + (gqlErr) => { + if (res.error) { + gqlClientError$.next({ + type: "GQL_CLIENT_REPORTED_ERROR", + opType: "subscription", + opResult: res, + }) + } + + return >{ type: "gql_error", error: parseGQLErrorString(gqlErr ?? "") as DocErrorType, - }, + } + }, // The right case (it was a GraphQL Error) (networkErr) => >{ @@ -311,11 +336,20 @@ export const runMutation = < E.fromNullable(result.error?.message), E.match( // The left case (network error was null) - (gqlErr) => - >{ + (gqlErr) => { + if (result.error) { + gqlClientError$.next({ + type: "GQL_CLIENT_REPORTED_ERROR", + opType: "mutation", + opResult: result, + }) + } + + return >{ type: "gql_error", error: parseGQLErrorString(gqlErr ?? ""), - }, + } + }, // The right case (it was a network error) (networkErr) => >{ diff --git a/packages/hoppscotch-app/src/modules/sentry.ts b/packages/hoppscotch-app/src/modules/sentry.ts index 4fb4f5b98..8b3fd7488 100644 --- a/packages/hoppscotch-app/src/modules/sentry.ts +++ b/packages/hoppscotch-app/src/modules/sentry.ts @@ -7,6 +7,7 @@ import { settingsStore } from "~/newstore/settings" import { App } from "vue" import { APP_IS_IN_DEV_MODE } from "~/helpers/dev" import { gqlClientError$ } from "~/helpers/backend/GQLClient" +import { currentUser$ } from "~/helpers/fb/auth" /** * The tag names we allow giving to Sentry @@ -97,6 +98,12 @@ function reportErrors( } if (extras !== null && extras === undefined) scope.setExtras(extras) + scope.addAttachment({ + filename: "extras-dump.json", + data: JSON.stringify(extras), + contentType: "application/json", + }) + errs.forEach((err) => Sentry.captureException(err)) }) } @@ -137,6 +144,29 @@ function subscribeToAppEventsForReporting() { { op: ev.op } ) break + + case "GQL_CLIENT_REPORTED_ERROR": + reportError( + new Error("Backend Query Failed"), + "BACKEND_OPERATIONS", + { opType: ev.opType }, + { + opResult: ev.opResult, + } + ) + break + } + }) +} + +/** + * Subscribe to app system events for adding + * additional data tags for the error reporting + */ +function subscribeForAppDataTags() { + currentUser$.subscribe((user) => { + if (sentryActive) { + Sentry.setTag("user_logged_in", !!user) } }) } @@ -163,5 +193,6 @@ export default { }) subscribeToAppEventsForReporting() + subscribeForAppDataTags() }, }