Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(web): [FE] display errors msg got from be #1343

Open
wants to merge 10 commits into
base: refactor/error_handling_be
Choose a base branch
from
Open
71 changes: 35 additions & 36 deletions web/src/beta/features/Notification/hooks.ts
Original file line number Diff line number Diff line change
@@ -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 =
Expand All @@ -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);

Expand Down Expand Up @@ -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
]);
Expand Down
22 changes: 12 additions & 10 deletions web/src/sentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"}`
)
);
}
});
};
2 changes: 2 additions & 0 deletions web/src/services/gql/provider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -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
]),
Expand Down
28 changes: 18 additions & 10 deletions web/src/services/gql/provider/links/errorLink.ts
Original file line number Diff line number Diff line change
@@ -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[] = [];

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);
}
});
};
14 changes: 14 additions & 0 deletions web/src/services/gql/provider/links/langLink.ts
Original file line number Diff line number Diff line change
@@ -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
}
};
});
};
1 change: 0 additions & 1 deletion web/src/services/i18n/translations/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: ''
Expand Down
1 change: 0 additions & 1 deletion web/src/services/i18n/translations/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: プロジェクト情報
Expand Down
16 changes: 10 additions & 6 deletions web/src/services/state/gqlErrorHandling.ts
Original file line number Diff line number Diff line change
@@ -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<GQLError | undefined>(undefined);

export const useError = () => useAtom(error);
export type GQLError = {
type?: string;
message?: string;
code?: string;
description?: string;
};
const errors = atom<GQLError[]>([]);
export const useErrors = () => useAtom(errors);

export default () => {
const setError = useSetAtom(error);
return { setError };
const setErrors = useSetAtom(errors);
return { setErrors };
};
2 changes: 1 addition & 1 deletion web/src/services/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading