fix(fe2): error msg templating + Filters undefined error fix (#2411)

* fix(fe2): error msg templating + Filters undefined error fix

* fixx

* fixx
This commit is contained in:
Kristaps Fabians Geikins
2024-06-20 13:11:05 +03:00
committed by GitHub
parent 4ab1afc190
commit f074be89aa
18 changed files with 126 additions and 96 deletions
@@ -18,10 +18,6 @@
</div>
<PromoBannersWrapper v-if="promoBanners.length" :banners="promoBanners" />
<div v-if="showErrorTest" class="w-full">
<FormButton @click="testError">Test error</FormButton>
</div>
<div
v-if="!showEmptyState"
class="flex flex-col space-y-2 md:flex-row md:items-center mb-8 pt-4"
@@ -134,7 +130,6 @@ const promoBanners = ref<PromoBanner[]>([
}
])
const route = useRoute()
const { activeUser, isGuest } = useActiveUser()
const { triggerNotification } = useGlobalToast()
const areQueriesLoading = useQueryLoading()
@@ -160,8 +155,6 @@ const { onResult: onUserProjectsUpdate } = useSubscription(
onUserProjectsUpdateSubscription
)
const showErrorTest = computed(() => route.query.showErrorButton === '1')
const projects = computed(() => projectsPanelResult.value?.activeUser?.projects)
const showEmptyState = computed(() => {
const isFiltering =
@@ -340,8 +333,4 @@ const clearSearch = () => {
selectedRoles.value = []
updateSearchImmediately()
}
const testError = () => {
throw new Error('what duhh hell')
}
</script>
@@ -66,7 +66,7 @@ import { ChevronLeftIcon } from '@heroicons/vue/24/solid'
import { VisualDiffMode } from '@speckle/viewer'
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
import { uniqBy, debounce } from 'lodash-es'
import type { SpeckleObject } from '~~/lib/common/helpers/sceneExplorer'
import type { SpeckleObject } from '~~/lib/viewer/helpers/sceneExplorer'
import { useMixpanel } from '~~/lib/core/composables/mp'
defineEmits<{
@@ -75,7 +75,7 @@ import {
CodeBracketIcon
} from '@heroicons/vue/24/solid'
import { ViewerEvent } from '@speckle/viewer'
import type { ExplorerNode } from '~~/lib/common/helpers/sceneExplorer'
import type { ExplorerNode } from '~~/lib/viewer/helpers/sceneExplorer'
import {
useInjectedViewer,
useInjectedViewerLoadedResources,
@@ -82,11 +82,11 @@
</div>
<div v-if="activeFilter">
<ViewerExplorerStringFilter
v-if="activeFilter.type === 'string'"
v-if="stringActiveFilter"
:filter="stringActiveFilter"
/>
<ViewerExplorerNumericFilter
v-if="activeFilter.type === 'number'"
v-if="numericActiveFilter"
:filter="numericActiveFilter"
/>
</div>
@@ -95,13 +95,13 @@
<script setup lang="ts">
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/vue/24/solid'
import { ArrowPathIcon } from '@heroicons/vue/24/outline'
import type {
PropertyInfo,
StringPropertyInfo,
NumericPropertyInfo
} from '@speckle/viewer'
import type { PropertyInfo, StringPropertyInfo } from '@speckle/viewer'
import { useFilterUtilities } from '~~/lib/viewer/composables/ui'
import { useMixpanel } from '~~/lib/core/composables/mp'
import {
isNumericPropertyInfo,
isStringPropertyInfo
} from '~/lib/viewer/helpers/sceneExplorer'
const {
setPropertyFilter,
@@ -150,9 +150,8 @@ const relevantFilters = computed(() => {
})
})
const speckleTypeFilter = computed(
() =>
relevantFilters.value.find((f) => f.key === 'speckle_type') as StringPropertyInfo
const speckleTypeFilter = computed(() =>
relevantFilters.value.find((f) => f.key === 'speckle_type')
)
const activeFilter = computed(
() => propertyFilter.filter.value || speckleTypeFilter.value
@@ -169,9 +168,12 @@ watch(activeFilter, (newVal) => {
})
})
// Using these as casting activeFilter as XXX in the prop causes some syntax highliting bug to show. Apologies :)
const stringActiveFilter = computed(() => activeFilter.value as StringPropertyInfo)
const numericActiveFilter = computed(() => activeFilter.value as NumericPropertyInfo)
const stringActiveFilter = computed(() =>
isStringPropertyInfo(activeFilter.value) ? activeFilter.value : undefined
)
const numericActiveFilter = computed(() =>
isNumericPropertyInfo(activeFilter.value) ? activeFilter.value : undefined
)
const searchString = ref<string | undefined>(undefined)
const relevantFiltersSearched = computed(() => {
@@ -229,7 +231,7 @@ const toggleColors = () => {
// Handles a rather complicated ux flow: user sets a numeric filter which only makes sense with colors on. we set the force colors flag in that scenario, so we can revert it if user selects a non-numeric filter afterwards.
let forcedColors = false
const refreshColorsIfSetOrActiveFilterIsNumeric = () => {
if (activeFilter.value.type === 'number' && !colors.value) {
if (!!numericActiveFilter.value && !colors.value) {
forcedColors = true
applyPropertyFilter()
return
@@ -144,7 +144,7 @@ import type {
ExplorerNode,
SpeckleObject,
SpeckleReference
} from '~~/lib/common/helpers/sceneExplorer'
} from '~~/lib/viewer/helpers/sceneExplorer'
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
import {
getHeaderAndSubheaderForSpeckleObject,
@@ -131,7 +131,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { ChevronDownIcon } from '@heroicons/vue/24/solid'
import { ClipboardDocumentIcon } from '@heroicons/vue/24/outline'
import type { SpeckleObject } from '~~/lib/common/helpers/sceneExplorer'
import type { SpeckleObject } from '~~/lib/viewer/helpers/sceneExplorer'
import { getHeaderAndSubheaderForSpeckleObject } from '~~/lib/object-sidebar/helpers'
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
import { useHighlightedObjectsUtilities } from '~/lib/viewer/composables/ui'
@@ -1,15 +0,0 @@
import { type SpeckleObject, type SpeckleReference } from '@speckle/viewer'
// Note: minor typing hacks for less squiggly lines in the explorer.
// TODO: ask alex re viewer data tree types
export type ExplorerNode = {
guid?: string
data?: SpeckleObject
raw?: SpeckleObject
atomic?: boolean
model?: Record<string, unknown> & { id?: string }
children: ExplorerNode[]
}
export type { SpeckleObject, SpeckleReference }
@@ -1,9 +1,10 @@
import { useScopedState } from '~~/lib/common/composables/scopedState'
import * as Observability from '@speckle/shared/dist/esm/observability/index'
import type {
AbstractErrorHandler,
AbstractErrorHandlerParams,
AbstractUnhandledErrorHandler
import {
prettify,
type AbstractErrorHandler,
type AbstractErrorHandlerParams,
type AbstractUnhandledErrorHandler
} from '~/lib/core/helpers/observability'
const ENTER_STATE_AT_ERRORS_PER_MIN = 100
@@ -65,7 +66,11 @@ export const useGetErrorLoggingTransports = () => {
export const useLogToErrorLoggingTransports = () => {
const transports = useGetErrorLoggingTransports()
const invokeTransportsWithPayload = (payload: AbstractErrorHandlerParams) => {
transports.forEach((handler) => handler.onError(payload))
transports.forEach((handler) =>
handler.onError(payload, {
prettifyMessage: (msg) => prettify(payload.otherData || {}, msg)
})
)
}
return {
@@ -8,7 +8,7 @@ import createUploadLink from 'apollo-upload-client/createUploadLink.mjs'
import { WebSocketLink } from '@apollo/client/link/ws'
import { getMainDefinition } from '@apollo/client/utilities'
import { Kind } from 'graphql'
import type { OperationDefinitionNode } from 'graphql'
import type { GraphQLError, OperationDefinitionNode } from 'graphql'
import type { CookieRef, NuxtApp } from '#app'
import type { Optional } from '@speckle/shared'
import { useAuthCookie } from '~~/lib/auth/composables/auth'
@@ -22,7 +22,7 @@ import { onError } from '@apollo/client/link/error'
import { useNavigateToLogin, loginRoute } from '~~/lib/common/helpers/route'
import { useAppErrorState } from '~~/lib/core/composables/error'
import { isInvalidAuth } from '~~/lib/common/helpers/graphql'
import { isBoolean, omit } from 'lodash-es'
import { isArray, isBoolean, omit } from 'lodash-es'
import { useRequestId } from '~/lib/core/composables/server'
const appName = 'frontend-2'
@@ -337,12 +337,15 @@ function createLink(params: {
? skipLoggingErrors
: skipLoggingErrors?.(res)
if (!isSubTokenMissingError && !shouldSkip) {
const errMsg = res.networkError?.message || res.graphQLErrors?.[0]?.message
const gqlErrors: Array<GraphQLError> = isArray(res.graphQLErrors)
? res.graphQLErrors
: []
const errMsg = res.networkError?.message || gqlErrors[0]?.message
logger.error(
{
...omit(res, ['forward', 'response']),
networkErrorMessage: res.networkError?.message,
gqlErrorMessages: res.graphQLErrors?.map((e) => e.message),
gqlErrorMessages: gqlErrors.map((e) => e.message),
errorMessage: errMsg,
graphql: true
},
@@ -19,7 +19,7 @@ import type { Logger } from 'pino'
/**
* Add pino-pretty like formatting
*/
const prettify = (log: object, msg: string) =>
export const prettify = (log: object, msg: string) =>
msg.replace(/{([^{}]+)}/g, (match: string, p1: string) => {
const val = get(log, p1)
if (val === undefined) return match
@@ -33,7 +33,7 @@ const prettify = (log: object, msg: string) =>
* Wrap any logger call w/ logic that prettifies the error message like pino-pretty does
* and emits bindings if they are provided
*/
const log =
const prettifiedLoggerFactory =
(logger: (...args: unknown[]) => void, bindings?: () => Record<string, unknown>) =>
(...vals: unknown[]) => {
const finalVals = vals.slice()
@@ -72,16 +72,16 @@ export function buildFakePinoLogger(
const errLogger = (...args: unknown[]) => {
const { onError } = options || {}
if (onError) onError(...args)
log(console.error, bindings)(...args)
prettifiedLoggerFactory(console.error, bindings)(...args)
}
const logger = {
debug: log(console.debug, bindings),
info: log(console.info, bindings),
warn: log(console.warn, bindings),
debug: prettifiedLoggerFactory(console.debug, bindings),
info: prettifiedLoggerFactory(console.info, bindings),
warn: prettifiedLoggerFactory(console.warn, bindings),
error: errLogger,
fatal: errLogger,
trace: log(console.trace, bindings),
trace: prettifiedLoggerFactory(console.trace, bindings),
silent: noop
} as unknown as ReturnType<typeof Observability.getLogger>
@@ -121,13 +121,18 @@ export const formatAppError = (err: SimpleError) => {
}
}
export type AbstractErrorHandler = (params: {
args: unknown[]
firstString: Optional<string>
firstError: Optional<Error>
otherData: Record<string, unknown>
nonObjectOtherData: unknown[]
}) => void
export type AbstractErrorHandler = (
params: {
args: unknown[]
firstString: Optional<string>
firstError: Optional<Error>
otherData: Record<string, unknown>
nonObjectOtherData: unknown[]
},
helpers: {
prettifyMessage: (msg: string) => string
}
) => void
export type AbstractUnhandledErrorHandler = (params: {
event: ErrorEvent | PromiseRejectionEvent
@@ -175,13 +180,16 @@ export function enableCustomErrorHandling(params: {
{},
...otherDataObjects
) as Record<string, unknown>
onError({
args,
firstError,
firstString,
otherData: mergedOtherDataObject,
nonObjectOtherData: otherDataNonObjects
})
onError(
{
args,
firstError,
firstString,
otherData: mergedOtherDataObject,
nonObjectOtherData: otherDataNonObjects
},
{ prettifyMessage: (msg) => prettify(mergedOtherDataObject, msg) }
)
}
return log(...args)
@@ -1,4 +1,4 @@
import type { SpeckleObject } from '~~/lib/common/helpers/sceneExplorer'
import type { SpeckleObject } from '~/lib/viewer/helpers/sceneExplorer'
export type HeaderSubheader = {
header: string
@@ -49,7 +49,7 @@ import { nanoid } from 'nanoid'
import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables/toast'
import type { CommentBubbleModel } from '~~/lib/viewer/composables/commentBubbles'
import { setupUrlHashState } from '~~/lib/viewer/composables/setup/urlHashState'
import type { SpeckleObject } from '~~/lib/common/helpers/sceneExplorer'
import type { SpeckleObject } from '~/lib/viewer/helpers/sceneExplorer'
import type { Box3 } from 'three'
import { Vector3 } from 'three'
import { writableAsyncComputed } from '~~/lib/common/composables/async'
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { MeasurementType } from '@speckle/viewer'
import type { SpeckleObject } from '~~/lib/common/helpers/sceneExplorer'
import type { SpeckleObject } from '~/lib/viewer/helpers/sceneExplorer'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useInjectedViewerState } from '~~/lib/viewer/composables/setup'
import { useCameraUtilities, useSelectionUtilities } from '~~/lib/viewer/composables/ui'
@@ -4,7 +4,7 @@ import { CameraController, MeasurementsExtension } from '@speckle/viewer'
import { until } from '@vueuse/shared'
import { difference, isString, uniq } from 'lodash-es'
import { useEmbedState } from '~/lib/viewer/composables/setup/embed'
import type { SpeckleObject } from '~~/lib/common/helpers/sceneExplorer'
import type { SpeckleObject } from '~/lib/viewer/helpers/sceneExplorer'
import { isNonNullable } from '~~/lib/common/helpers/utils'
import {
useInjectedViewer,
@@ -16,7 +16,7 @@ import {
type SelectionEvent,
type TreeNode
} from '@speckle/viewer'
import type { SpeckleObject } from '~~/lib/common/helpers/sceneExplorer'
import type { SpeckleObject } from '~/lib/viewer/helpers/sceneExplorer'
// NOTE: this is a preformance optimisation - this function is hot, and has to do
// potentially large searches if many elements are hidden/isolated. We cache the
@@ -0,0 +1,29 @@
import type { MaybeNullOrUndefined } from '@speckle/shared'
import {
type NumericPropertyInfo,
type PropertyInfo,
type SpeckleObject,
type SpeckleReference,
type StringPropertyInfo
} from '@speckle/viewer'
export const isStringPropertyInfo = (
info: MaybeNullOrUndefined<PropertyInfo>
): info is StringPropertyInfo => info?.type === 'string'
export const isNumericPropertyInfo = (
info: MaybeNullOrUndefined<PropertyInfo>
): info is NumericPropertyInfo => info?.type === 'number'
// Note: minor typing hacks for less squiggly lines in the explorer.
// TODO: ask alex re viewer data tree types
export type ExplorerNode = {
guid?: string
data?: SpeckleObject
raw?: SpeckleObject
atomic?: boolean
model?: Record<string, unknown> & { id?: string }
children: ExplorerNode[]
}
export type { SpeckleObject, SpeckleReference }
+9 -14
View File
@@ -136,6 +136,10 @@ export default defineNuxtPlugin(async (nuxtApp) => {
...collectMainInfo({ isBrowser: true })
})
logger = buildFakePinoLogger({
consoleBindings: logCsrEmitProps ? collectCoreInfo : undefined
})
// SEQ Browser integration
if (logClientApiToken?.length && logClientApiEndpoint?.length) {
const seq = await import('seq-logging/browser')
@@ -195,24 +199,15 @@ export default defineNuxtPlugin(async (nuxtApp) => {
})
}
errorHandlers.push(errorLogger)
logger = buildFakePinoLogger({
consoleBindings: logCsrEmitProps ? collectCoreInfo : undefined
})
logger.debug('Set up seq ingestion...')
} else {
// No seq integration, fallback to basic console logging
logger = buildFakePinoLogger({
consoleBindings: logCsrEmitProps ? collectCoreInfo : undefined
})
}
}
// Register seq transports, if any
if (errorHandlers.length) {
registerErrorTransport({
onError: (params) => {
errorHandlers.forEach((handler) => handler(params))
onError: (...params) => {
errorHandlers.forEach((handler) => handler(...params))
},
onUnhandledError: (event) => {
unhandledErrorHandlers.forEach((handler) => handler(event))
@@ -220,19 +215,19 @@ export default defineNuxtPlugin(async (nuxtApp) => {
})
}
// Global error handler - handle all transports
// Global error handler - handle all transports besides the core pino/console.log logger
const transports = useGetErrorLoggingTransports()
let serverFatalError: Optional<AbstractErrorHandlerParams> = undefined
logger = enableCustomErrorHandling({
logger,
onError: (params) => {
onError: (params, helpers) => {
const { otherData } = params
if (import.meta.server && otherData?.isAppError) {
serverFatalError = params
}
transports.forEach((handler) => handler.onError(params))
transports.forEach((handler) => handler.onError(params, helpers))
}
})
+17 -3
View File
@@ -7,6 +7,7 @@ import type { Plugin } from 'nuxt/dist/app/nuxt'
import { isH3Error } from '~/lib/common/helpers/error'
import { useRequestId, useServerRequestId } from '~/lib/core/composables/server'
import { isBrave, isSafari } from '@speckle/shared'
import { isString } from 'lodash-es'
type PluginNuxtApp = Parameters<Plugin>[0]
@@ -69,15 +70,28 @@ function initRumClient(app: PluginNuxtApp) {
: {}
registerErrorTransport({
onError: ({ args, firstError, firstString, otherData, nonObjectOtherData }) => {
onError: (
{ args, firstError, firstString, otherData, nonObjectOtherData },
{ prettifyMessage }
) => {
if (!datadog || !('addError' in datadog)) return
const error = firstError || firstString || args[0]
let error = firstError || firstString || args[0]
const mainErrorMessageTemplate = firstString
const mainErrorMessage = mainErrorMessageTemplate
? prettifyMessage(mainErrorMessageTemplate)
: undefined
if (isString(error)) {
error = prettifyMessage(error)
}
datadog.addError(error, {
...otherData,
...resolveH3Data(firstError),
extraData: nonObjectOtherData,
mainErrorMessage: firstString,
mainErrorMessageTemplate,
mainErrorMessage,
isProperlySentError: true
})
},