From 313b0a504889a1b7b1814654eb7600e2a75607f4 Mon Sep 17 00:00:00 2001 From: soneda-yuya Date: Thu, 9 Jan 2025 18:55:34 +0900 Subject: [PATCH 1/3] refactor(web): [FE]Improvement: display error msg with translations on FE --- web/src/beta/features/Notification/hooks.ts | 71 +++++++++---------- web/src/sentry.ts | 22 +++--- web/src/services/gql/provider/index.tsx | 2 + .../services/gql/provider/links/errorLink.ts | 30 +++++--- .../services/gql/provider/links/langLink.ts | 14 ++++ web/src/services/state/gqlErrorHandling.ts | 16 +++-- web/src/services/state/index.ts | 2 +- 7 files changed, 93 insertions(+), 64 deletions(-) create mode 100644 web/src/services/gql/provider/links/langLink.ts diff --git a/web/src/beta/features/Notification/hooks.ts b/web/src/beta/features/Notification/hooks.ts index fab6de4de6..426c6498a5 100644 --- a/web/src/beta/features/Notification/hooks.ts +++ b/web/src/beta/features/Notification/hooks.ts @@ -1,9 +1,6 @@ import { useT, useLang } from "@reearth/services/i18n"; -import { - useError, - useNotification, - Notification -} from "@reearth/services/state"; +import { useNotification, Notification } from "@reearth/services/state"; +import { useErrors } from "@reearth/services/state/gqlErrorHandling"; import { useState, useEffect, useCallback, useMemo } from "react"; export type PolicyItems = @@ -26,7 +23,7 @@ const policyItems: PolicyItems[] = [ export default () => { const t = useT(); const currentLanguage = useLang(); - const [error, setError] = useError(); + const [errors, setErrors] = useErrors(); const [notification, setNotification] = useNotification(); const [visible, changeVisibility] = useState(false); @@ -54,42 +51,44 @@ export default () => { }, []); useEffect(() => { - if (!error) return; - if (error.message?.includes("policy violation") && error.message) { - const limitedItem = policyItems.find((i) => error.message?.includes(i)); - const policyItem = - limitedItem && policyLimitNotifications - ? policyLimitNotifications[limitedItem] - : undefined; - const message = policyItem - ? typeof policyItem === "string" - ? policyItem - : policyItem[currentLanguage] - : t( - "You have reached a policy limit. Please contact an administrator of your Re:Earth system." - ); + if (errors.length === 0) return; + errors.forEach((error) => { + if (error.message?.includes("policy violation") && error.message) { + const limitedItem = policyItems.find((i) => error.message?.includes(i)); + const policyItem = + limitedItem && policyLimitNotifications + ? policyLimitNotifications[limitedItem] + : undefined; + const message = policyItem + ? typeof policyItem === "string" + ? policyItem + : policyItem[currentLanguage] + : t( + "You have reached a policy limit. Please contact an administrator of your Re:Earth system." + ); - setNotification({ - type: "info", - heading: noticeHeading, - text: message, - duration: "persistent" - }); - } else { - setNotification({ - type: "error", - heading: errorHeading, - text: t("Something went wrong. Please try again later.") - }); - } - setError(undefined); + setNotification({ + type: "info", + heading: noticeHeading, + text: message, + duration: "persistent" + }); + } else { + setNotification({ + type: "error", + heading: errorHeading, + text: error.description || error.message || "" + }); + } + }); + setErrors([]); }, [ - error, + errors, currentLanguage, policyLimitNotifications, errorHeading, noticeHeading, - setError, + setErrors, setNotification, t ]); diff --git a/web/src/sentry.ts b/web/src/sentry.ts index e303896863..95b4d9ed5d 100644 --- a/web/src/sentry.ts +++ b/web/src/sentry.ts @@ -12,14 +12,16 @@ export const initialize = () => { } }; -export const reportError = (error: ReportError) => { - if (error instanceof Error) { - Sentry.captureException(error); - } else { - Sentry.captureException( - new Error( - `${error.type || "Unknown"}: ${error.message || "No message provided"}` - ) - ); - } +export const reportError = (errors: ReportError[]) => { + errors.forEach((error) => { + if (error instanceof Error) { + Sentry.captureException(error); + } else { + Sentry.captureException( + new Error( + `${error.type || "Unknown"}: ${error.message || "No message provided"}` + ) + ); + } + }); }; diff --git a/web/src/services/gql/provider/index.tsx b/web/src/services/gql/provider/index.tsx index 4e865afda7..9f9b1a4e5d 100644 --- a/web/src/services/gql/provider/index.tsx +++ b/web/src/services/gql/provider/index.tsx @@ -14,6 +14,7 @@ import { useCallback, type ReactNode } from "react"; import fragmentMatcher from "../__gen__/fragmentMatcher.json"; import { authLink, sentryLink, errorLink, uploadLink, taskLink } from "./links"; +import langLink from "./links/langLink"; import { paginationMerge } from "./pagination"; const Provider: React.FC<{ children?: ReactNode }> = ({ children }) => { @@ -90,6 +91,7 @@ const Provider: React.FC<{ children?: ReactNode }> = ({ children }) => { errorLink(), sentryLink(endpoint), authLink(), + langLink(), // https://github.com/apollographql/apollo-client/issues/6011#issuecomment-619468320 uploadLink(endpoint) as unknown as ApolloLink ]), diff --git a/web/src/services/gql/provider/links/errorLink.ts b/web/src/services/gql/provider/links/errorLink.ts index 6ce70b57e2..6c0114fdb8 100644 --- a/web/src/services/gql/provider/links/errorLink.ts +++ b/web/src/services/gql/provider/links/errorLink.ts @@ -1,25 +1,33 @@ import { onError } from "@apollo/client/link/error"; import { reportError } from "@reearth/sentry"; import { useSetError } from "@reearth/services/state"; +import { GQLError } from "@reearth/services/state/gqlErrorHandling"; export default () => { - const { setError } = useSetError(); + const { setErrors } = useSetError(); return onError(({ graphQLErrors, networkError }) => { if (!networkError && !graphQLErrors) return; - let error: { type?: string; message?: string } | undefined; - + let errors: GQLError[] = []; + console.log(graphQLErrors); if (networkError?.message) { - error = { message: networkError?.message }; + errors = [ + { message: networkError?.message, description: networkError.message } + ]; } else { - error = { - type: graphQLErrors?.[0].path?.[0].toString(), - message: graphQLErrors?.[0].message - }; + errors = + graphQLErrors?.map((gqlError) => { + return { + type: gqlError.path?.[0].toString(), + message: gqlError.message, + code: gqlError.extensions?.code as string, + description: gqlError.extensions?.description as string + }; + }) ?? []; } - if (error) { - setError(error); - reportError(error); + if (errors.length > 0) { + setErrors(errors); + reportError(errors); } }); }; diff --git a/web/src/services/gql/provider/links/langLink.ts b/web/src/services/gql/provider/links/langLink.ts new file mode 100644 index 0000000000..18d5063531 --- /dev/null +++ b/web/src/services/gql/provider/links/langLink.ts @@ -0,0 +1,14 @@ +import { setContext } from "@apollo/client/link/context"; +import i18n from "@reearth/services/i18n/i18n"; + +export default () => { + return setContext(async (_, { headers }) => { + const local = i18n.language.split("-")[0]; + return { + headers: { + ...headers, + lang: local + } + }; + }); +}; diff --git a/web/src/services/state/gqlErrorHandling.ts b/web/src/services/state/gqlErrorHandling.ts index 27b4a6d996..b4d8e4f744 100644 --- a/web/src/services/state/gqlErrorHandling.ts +++ b/web/src/services/state/gqlErrorHandling.ts @@ -1,12 +1,16 @@ import { atom, useAtom, useSetAtom } from "jotai"; // useError is needed for Apollo provider error only. Handle other errors with useNotification directly. -type GQLError = { type?: string; message?: string }; -const error = atom(undefined); - -export const useError = () => useAtom(error); +export type GQLError = { + type?: string; + message?: string; + code?: string; + description?: string; +}; +const errors = atom([]); +export const useErrors = () => useAtom(errors); export default () => { - const setError = useSetAtom(error); - return { setError }; + const setErrors = useSetAtom(errors); + return { setErrors }; }; diff --git a/web/src/services/state/index.ts b/web/src/services/state/index.ts index 39725f1af9..d45e863bda 100644 --- a/web/src/services/state/index.ts +++ b/web/src/services/state/index.ts @@ -5,7 +5,7 @@ import { TeamMember } from "../gql"; export * from "./devPlugins"; -export { default as useSetError, useError } from "./gqlErrorHandling"; +export { default as useSetError } from "./gqlErrorHandling"; export type WidgetAreaState = { zone: "inner" | "outer"; From 20929910c6e1a2055702ec115a765c8b00044e1e Mon Sep 17 00:00:00 2001 From: soneda-yuya Date: Fri, 10 Jan 2025 18:09:58 +0900 Subject: [PATCH 2/3] fix: i18n check --- web/src/services/i18n/translations/en.yml | 1 - web/src/services/i18n/translations/ja.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/web/src/services/i18n/translations/en.yml b/web/src/services/i18n/translations/en.yml index b8429699e1..983f9f3c19 100644 --- a/web/src/services/i18n/translations/en.yml +++ b/web/src/services/i18n/translations/en.yml @@ -233,7 +233,6 @@ Error: '' Warning: '' Notice: '' You have reached a policy limit. Please contact an administrator of your Re:Earth system.: '' -Something went wrong. Please try again later.: '' Public: '' Most project settings are hidden when the project is archived. Please unarchive the project to view and edit these settings.: '' Project Info: '' diff --git a/web/src/services/i18n/translations/ja.yml b/web/src/services/i18n/translations/ja.yml index 6bfe4f1365..3782288b77 100644 --- a/web/src/services/i18n/translations/ja.yml +++ b/web/src/services/i18n/translations/ja.yml @@ -236,7 +236,6 @@ Error: エラー Warning: 注意 Notice: 通知 You have reached a policy limit. Please contact an administrator of your Re:Earth system.: 現在のポリシーの上限に達しました。システム管理者にお問い合わせください。 -Something went wrong. Please try again later.: 何らかの問題が発生しました。しばらく経ってからお試しください。 Public: 公開 Most project settings are hidden when the project is archived. Please unarchive the project to view and edit these settings.: プロジェクトをアーカイブ化すると、削除とアーカイブ化解除以外の編集は行えません。再度編集可能な状態にするには、プロジェクトのアーカイブ化を解除してください。 Project Info: プロジェクト情報 From f299e326f129bb0e4bf4cc9f108564d289e6b1ac Mon Sep 17 00:00:00 2001 From: soneda-yuya Date: Fri, 10 Jan 2025 18:22:18 +0900 Subject: [PATCH 3/3] delete console.log --- web/src/services/gql/provider/links/errorLink.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/services/gql/provider/links/errorLink.ts b/web/src/services/gql/provider/links/errorLink.ts index 6c0114fdb8..8a2b935f79 100644 --- a/web/src/services/gql/provider/links/errorLink.ts +++ b/web/src/services/gql/provider/links/errorLink.ts @@ -9,7 +9,7 @@ export default () => { return onError(({ graphQLErrors, networkError }) => { if (!networkError && !graphQLErrors) return; let errors: GQLError[] = []; - console.log(graphQLErrors); + if (networkError?.message) { errors = [ { message: networkError?.message, description: networkError.message }