From 04d0ee074fc423ca5437d0ddb3dcd012024b4408 Mon Sep 17 00:00:00 2001 From: Kristaps Fabians Geikins Date: Thu, 5 Sep 2024 11:33:38 +0300 Subject: [PATCH] fix(fe2): default error skip logic & error policy (#2875) --- .../project/page/settings/general/General.vue | 18 ++------ .../settings/workspaces/Billing.vue | 16 ++----- .../settings/workspaces/Members.vue | 16 ++----- .../components/workspace/ProjectList.vue | 15 ++---- .../frontend-2/lib/core/configs/apollo.ts | 46 ++++++++++++++++--- .../projects/composables/projectManagement.ts | 6 --- .../frontend-2/pages/projects/[id]/index.vue | 10 +--- 7 files changed, 53 insertions(+), 74 deletions(-) diff --git a/packages/frontend-2/components/project/page/settings/general/General.vue b/packages/frontend-2/components/project/page/settings/general/General.vue index c1c34a5b4..51f611fbf 100644 --- a/packages/frontend-2/components/project/page/settings/general/General.vue +++ b/packages/frontend-2/components/project/page/settings/general/General.vue @@ -50,7 +50,6 @@ import type { ProjectUpdateInput } from '~~/lib/common/generated/gql/graphql' import { useUpdateProject } from '~~/lib/projects/composables/projectManagement' import { graphql } from '~~/lib/common/generated/gql' import { useTeamInternals } from '~/lib/projects/composables/team' -import { skipLoggingErrorsIfOneFieldError } from '~/lib/common/helpers/graphql' const projectPageSettingsGeneralQuery = graphql(` query ProjectPageSettingsGeneral($projectId: String!) { @@ -72,20 +71,9 @@ const updateProject = useUpdateProject() const projectId = computed(() => route.params.id as string) -const { result: pageResult } = useQuery( - projectPageSettingsGeneralQuery, - () => ({ - projectId: projectId.value - }), - () => ({ - // Custom error policy so that a failing invitedTeam resolver (due to access rights) - // doesn't kill the entire query - errorPolicy: 'all', - context: { - skipLoggingErrors: skipLoggingErrorsIfOneFieldError('invitedTeam') - } - }) -) +const { result: pageResult } = useQuery(projectPageSettingsGeneralQuery, () => ({ + projectId: projectId.value +})) const project = computed(() => pageResult.value?.project) diff --git a/packages/frontend-2/components/settings/workspaces/Billing.vue b/packages/frontend-2/components/settings/workspaces/Billing.vue index 67bb2338e..16b57ce53 100644 --- a/packages/frontend-2/components/settings/workspaces/Billing.vue +++ b/packages/frontend-2/components/settings/workspaces/Billing.vue @@ -65,7 +65,6 @@ import { graphql } from '~/lib/common/generated/gql' import { useQuery } from '@vue/apollo-composable' import { settingsWorkspaceBillingQuery } from '~/lib/settings/graphql/queries' -import { skipLoggingErrorsIfOneFieldError } from '~/lib/common/helpers/graphql' graphql(` fragment SettingsWorkspacesBilling_Workspace on Workspace { @@ -87,18 +86,9 @@ const props = defineProps<{ workspaceId: string }>() -const { result } = useQuery( - settingsWorkspaceBillingQuery, - () => ({ - workspaceId: props.workspaceId - }), - () => ({ - errorPolicy: 'all', - context: { - skipLoggingErrors: skipLoggingErrorsIfOneFieldError('billing') - } - }) -) +const { result } = useQuery(settingsWorkspaceBillingQuery, () => ({ + workspaceId: props.workspaceId +})) const billing = computed(() => result.value?.workspace.billing) const versionCount = computed(() => billing.value?.versionsCount) diff --git a/packages/frontend-2/components/settings/workspaces/Members.vue b/packages/frontend-2/components/settings/workspaces/Members.vue index d97797160..631dfc1a7 100644 --- a/packages/frontend-2/components/settings/workspaces/Members.vue +++ b/packages/frontend-2/components/settings/workspaces/Members.vue @@ -33,7 +33,6 @@ import { Roles } from '@speckle/shared' import { useQuery } from '@vue/apollo-composable' import { graphql } from '~/lib/common/generated/gql' -import { skipLoggingErrorsIfOneFieldError } from '~/lib/common/helpers/graphql' import { settingsWorkspacesMembersQuery } from '~/lib/settings/graphql/queries' import type { LayoutPageTabItem } from '~~/lib/layout/helpers/components' @@ -48,18 +47,9 @@ const props = defineProps<{ workspaceId: string }>() -const { result } = useQuery( - settingsWorkspacesMembersQuery, - () => ({ - workspaceId: props.workspaceId - }), - () => ({ - // Custom error policy so that a failing invitedTeam resolver (due to access rights) - // doesn't kill the entire query - errorPolicy: 'all', - context: skipLoggingErrorsIfOneFieldError(['domains', 'invitedTeam']) - }) -) +const { result } = useQuery(settingsWorkspacesMembersQuery, () => ({ + workspaceId: props.workspaceId +})) const isAdmin = computed(() => result.value?.workspace?.role === Roles.Workspace.Admin) const tabItems = computed(() => [ diff --git a/packages/frontend-2/components/workspace/ProjectList.vue b/packages/frontend-2/components/workspace/ProjectList.vue index 7e588cf3f..76055837c 100644 --- a/packages/frontend-2/components/workspace/ProjectList.vue +++ b/packages/frontend-2/components/workspace/ProjectList.vue @@ -70,7 +70,6 @@ import type { WorkspaceProjectList_ProjectCollectionFragment, WorkspaceProjectsQueryQueryVariables } from '~~/lib/common/generated/gql/graphql' -import { skipLoggingErrorsIfOneFieldError } from '~/lib/common/helpers/graphql' import { workspaceRoute } from '~/lib/common/helpers/route' import { Roles } from '@speckle/shared' @@ -103,6 +102,9 @@ const { }) const token = computed(() => route.query.token as Optional) + +const pageFetchPolicy = usePageQueryStandardFetchPolicy() + const { result: initialQueryResult } = useQuery( workspacePageQuery, () => ({ @@ -113,16 +115,7 @@ const { result: initialQueryResult } = useQuery( token: token.value || null }), () => ({ - // Custom error policy so that a failing invitedTeam resolver (due to access rights) - // doesn't kill the entire query - errorPolicy: 'all', - context: { - skipLoggingErrors: skipLoggingErrorsIfOneFieldError([ - 'billing', - 'domains', - 'invitedTeam' - ]) - } + fetchPolicy: pageFetchPolicy.value }) ) diff --git a/packages/frontend-2/lib/core/configs/apollo.ts b/packages/frontend-2/lib/core/configs/apollo.ts index c76682d85..0676826fc 100644 --- a/packages/frontend-2/lib/core/configs/apollo.ts +++ b/packages/frontend-2/lib/core/configs/apollo.ts @@ -18,10 +18,10 @@ import { incomingOverwritesExistingMergeFunction, mergeAsObjectsFunction } from '~~/lib/core/helpers/apolloSetup' -import { onError } from '@apollo/client/link/error' +import { onError, type ErrorResponse } from '@apollo/client/link/error' import { useAppErrorState } from '~~/lib/core/composables/error' import { isInvalidAuth } from '~~/lib/common/helpers/graphql' -import { isArray, isBoolean, omit } from 'lodash-es' +import { intersection, isArray, isBoolean, omit } from 'lodash-es' import { useRequestId } from '~/lib/core/composables/server' const appName = 'frontend-2' @@ -332,6 +332,21 @@ function createWsClient(params: { ) } +const coreShouldSkipLoggingErrors = (err: ErrorResponse): boolean => { + // These fields have special auth requirements and will often throw errors that we don't want to log + const specialAuthFields = ['invitedTeam', 'billing', 'domains'] + const specialAuthFieldErrorCodes = ['FORBIDDEN', 'UNAUTHORIZED_ACCESS_ERROR'] + + return !!( + err.graphQLErrors && + err.graphQLErrors.every( + (e) => + intersection(e.path || [], specialAuthFields).length > 0 && + specialAuthFieldErrorCodes.includes(e.extensions?.code as string) + ) + ) +} + function createLink(params: { httpEndpoint: string wsClient?: SubscriptionClient @@ -349,10 +364,14 @@ function createLink(params: { 'need a token to subscribe' ) - const skipLoggingErrors = res.operation.getContext().skipLoggingErrors - const shouldSkip = isBoolean(skipLoggingErrors) - ? skipLoggingErrors - : skipLoggingErrors?.(res) + let shouldSkip = coreShouldSkipLoggingErrors(res) + const skipLoggingErrorsResolver = res.operation.getContext().skipLoggingErrors + if (skipLoggingErrorsResolver) { + shouldSkip = isBoolean(skipLoggingErrorsResolver) + ? skipLoggingErrorsResolver + : skipLoggingErrorsResolver?.(res) + } + if (!isSubTokenMissingError && !shouldSkip) { const gqlErrors: Array = isArray(res.graphQLErrors) ? res.graphQLErrors @@ -484,7 +503,20 @@ const defaultConfigResolver: ApolloConfigResolver = () => { cache: markRaw(createCache()), link, name: appName, - version: speckleServerVersion + version: speckleServerVersion, + defaultOptions: { + // We want to retain all data even if there are errors, cause there's often fields with special auth requirements that we don't want + // to be able to kill the entire query. Besides - in most cases partial data is better than no data at all. + query: { + errorPolicy: 'all' + }, + mutate: { + errorPolicy: 'all' + }, + watchQuery: { + errorPolicy: 'all' + } + } } } diff --git a/packages/frontend-2/lib/projects/composables/projectManagement.ts b/packages/frontend-2/lib/projects/composables/projectManagement.ts index fa8680650..87e3709b1 100644 --- a/packages/frontend-2/lib/projects/composables/projectManagement.ts +++ b/packages/frontend-2/lib/projects/composables/projectManagement.ts @@ -121,12 +121,6 @@ export function useCreateProject() { .mutate({ mutation: createProjectMutation, variables: { input }, - errorPolicy: 'all', - context: { - skipLoggingErrors: (err) => - err.graphQLErrors?.length === 1 && - err.graphQLErrors.some((e) => e.path?.includes('invitedTeam')) - }, update: (cache, { data }) => { const newProject = data?.projectMutations.create diff --git a/packages/frontend-2/pages/projects/[id]/index.vue b/packages/frontend-2/pages/projects/[id]/index.vue index a0161e7ae..6feb4ba4f 100644 --- a/packages/frontend-2/pages/projects/[id]/index.vue +++ b/packages/frontend-2/pages/projects/[id]/index.vue @@ -102,15 +102,7 @@ const { result: projectPageResult } = useQuery( ...(token.value?.length ? { token: token.value } : {}) }), () => ({ - fetchPolicy: pageFetchPolicy.value, - // Custom error policy so that a failing invitedTeam resolver (due to access rights) - // doesn't kill the entire query - errorPolicy: 'all' - // context: { - // skipLoggingErrors: (err) => - // err.graphQLErrors?.length === 1 && - // err.graphQLErrors.some((e) => !!e.path?.includes('invitedTeam')) - // } + fetchPolicy: pageFetchPolicy.value }) )