From c88e9e67856bd890085b5b347749e5876981b1f1 Mon Sep 17 00:00:00 2001 From: Ivan Li Date: Sun, 9 May 2021 16:42:19 +0800 Subject: [PATCH] feat: notify apollo error on screen. --- .vscode/settings.json | 1 + src/commits/commit-list.tsx | 27 +++++-- src/commons/graphql/client.ts | 29 ------- src/commons/graphql/client.tsx | 83 +++++++++++++++++++++ src/commons/route/router.tsx | 31 ++++++++ src/index.tsx | 30 ++------ src/pipeline-tasks/pipeline-task-detail.tsx | 9 +++ src/routes.tsx | 36 +++++++-- 8 files changed, 180 insertions(+), 66 deletions(-) delete mode 100644 src/commons/graphql/client.ts create mode 100644 src/commons/graphql/client.tsx create mode 100644 src/commons/route/router.tsx create mode 100644 src/pipeline-tasks/pipeline-task-detail.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 54c4ae6..6711284 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "Formik", "clsx", "fontsource", + "notistack", "vditor" ] } \ No newline at end of file diff --git a/src/commits/commit-list.tsx b/src/commits/commit-list.tsx index f69d862..23aac32 100644 --- a/src/commits/commit-list.tsx +++ b/src/commits/commit-list.tsx @@ -1,4 +1,5 @@ import { useQuery } from '@apollo/client'; +import { Link, useResponse } from '@curi/react-dom'; import { faPlayCircle, faVial } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { CircularProgress, Collapse, IconButton, LinearProgress, List, ListItem, ListItemIcon, ListItemSecondaryAction, ListItemText, makeStyles, useTheme } from '@material-ui/core'; @@ -37,11 +38,13 @@ export const CommitList: FC = ({pipeline}) => { return ; } - return - {data?.commits.map(commit => ( - - ))} - ; + return ( + + {data?.commits.map((commit) => ( + + ))} + + ); })()} ) @@ -50,6 +53,8 @@ export const CommitList: FC = ({pipeline}) => { const Item: FC<{commit: Commit}> = ({commit}) => { const [isOpen, setOpen] = useState(() => false); const classes = useStyles(); + + const {response} = useResponse(); return ( setOpen(!isOpen)}> @@ -74,9 +79,15 @@ const Item: FC<{commit: Commit}> = ({commit}) => { - { - commit.tasks.map(task => ()) - } + {commit.tasks.map((task) => ( + + + + ))} diff --git a/src/commons/graphql/client.ts b/src/commons/graphql/client.ts deleted file mode 100644 index 7135d8e..0000000 --- a/src/commons/graphql/client.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ApolloClient, ApolloLink, HttpLink, InMemoryCache } from "@apollo/client"; -import { withScalars } from 'apollo-link-scalars'; -import { buildClientSchema, IntrospectionQuery } from 'graphql'; -import { DateTimeResolver } from 'graphql-scalars'; -import introspectionResult from "../../generated/graphql.schema.json"; - -const schema = buildClientSchema( - (introspectionResult as unknown) as IntrospectionQuery -); - -const httpLink = new HttpLink({ - uri: "/api/graphql", -}); - -const typesMap = { - DateTime: DateTimeResolver, -}; -const link = ApolloLink.from([ - (withScalars({ schema, typesMap }) as unknown) as ApolloLink, - httpLink, -]); - -export function createApolloClient() { - return new ApolloClient({ - ssrMode: typeof window === "undefined", - link, - cache: new InMemoryCache(), - }); -} \ No newline at end of file diff --git a/src/commons/graphql/client.tsx b/src/commons/graphql/client.tsx new file mode 100644 index 0000000..6bf9b3a --- /dev/null +++ b/src/commons/graphql/client.tsx @@ -0,0 +1,83 @@ +import { + ApolloClient, + ApolloLink, + HttpLink, + InMemoryCache, + split, + ApolloProvider, +} from "@apollo/client"; +import { withScalars } from "apollo-link-scalars"; +import { buildClientSchema, IntrospectionQuery } from "graphql"; +import { DateTimeResolver } from "graphql-scalars"; +import { FC } from "react"; +import introspectionResult from "../../generated/graphql.schema.json"; +import { onError } from "@apollo/client/link/error"; +import { WebSocketLink } from "@apollo/client/link/ws"; +import { getMainDefinition } from "@apollo/client/utilities"; +import { useSnackbar } from "notistack"; + +const schema = buildClientSchema( + (introspectionResult as unknown) as IntrospectionQuery +); + +const typesMap = { + DateTime: DateTimeResolver, +}; + +export const FennecApolloClientProvider: FC = ({ children }) => { + const { enqueueSnackbar } = useSnackbar(); + const errorLink = onError(({ graphQLErrors, networkError }) => { + if (graphQLErrors) { + graphQLErrors.forEach((error) => { + enqueueSnackbar(error.message, { + variant: "error", + }); + }); + graphQLErrors.forEach(({ message, locations, path }) => { + console.error( + `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` + ); + }); + } + if (networkError) { + console.log(`[Network error]: ${networkError}`); + enqueueSnackbar(networkError.message, { + variant: "error", + }); + } + }); + const wsLink = new WebSocketLink({ + uri: `${window.location.protocol.replace("http", "ws")}//${ + window.location.hostname + }:${window.location.port}/api/graphql`, + options: { + reconnect: true, + }, + }); + const httpLink = new HttpLink({ + uri: "/api/graphql", + }); + const splitLink = split( + ({ query }) => { + const definition = getMainDefinition(query); + return ( + definition.kind === "OperationDefinition" && + definition.operation === "subscription" + ); + }, + wsLink, + httpLink + ); + const link = ApolloLink.from([ + errorLink, + (withScalars({ schema, typesMap }) as unknown) as ApolloLink, + splitLink, + ]); + const client = new ApolloClient({ + ssrMode: typeof window === "undefined", + link, + cache: new InMemoryCache(), + }); + + return {children}; +}; diff --git a/src/commons/route/router.tsx b/src/commons/route/router.tsx new file mode 100644 index 0000000..17af170 --- /dev/null +++ b/src/commons/route/router.tsx @@ -0,0 +1,31 @@ +import { useApolloClient } from "@apollo/client"; +import { createRouterComponent } from "@curi/react-dom"; +import { createRouter, announce } from "@curi/router"; +import { browser } from "@hickory/browser"; +import { FC, ReactNode, useEffect, useMemo, useState } from "react"; +import routes from "../../routes"; +import { LinearProgress } from "@material-ui/core"; + +const Component: FC = ({ children }) => { + const client = useApolloClient(); + const [body, setBody] = useState(null); + + useEffect(() => { + const router = createRouter(browser, routes, { + sideEffects: [ + announce(({ response }) => { + return `Navigated to ${response.location.pathname}`; + }), + ], + external: { client }, + }); + const Router = createRouterComponent(router); + router.once(() => { + setBody({children}); + }); + }, [setBody, client, children]); + + return body ?? ; +}; + +export default Component; diff --git a/src/index.tsx b/src/index.tsx index f79bc38..29e530b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,48 +4,30 @@ import "./index.css"; import "fontsource-roboto"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; -import { createApolloClient } from "./commons/graphql/client"; -import { ApolloProvider } from "@apollo/client"; +import { FennecApolloClientProvider } from "./commons/graphql/client"; import { MuiPickersUtilsProvider } from "@material-ui/pickers"; import DateFnsUtils from "@date-io/date-fns"; import zhLocale from "date-fns/locale/zh-CN"; -import { createRouterComponent } from "@curi/react-dom"; -import { createRouter, announce } from "@curi/router"; -import { browser } from "@hickory/browser"; -import routes from "./routes"; -import { ConfirmProvider } from 'material-ui-confirm'; -import { SnackbarProvider } from 'notistack'; +import { ConfirmProvider } from "material-ui-confirm"; +import { SnackbarProvider } from "notistack"; +import Router from './commons/route/router'; -const client = createApolloClient(); - -const router = createRouter(browser, routes, { - sideEffects: [ - announce(({ response }) => { - return `Navigated to ${response.location.pathname}`; - }), - ], - external: { client } -}); -const Router = createRouterComponent(router); - -router.once(() => { ReactDOM.render( - + - + , document.getElementById("root") ); -}); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) diff --git a/src/pipeline-tasks/pipeline-task-detail.tsx b/src/pipeline-tasks/pipeline-task-detail.tsx new file mode 100644 index 0000000..94d7556 --- /dev/null +++ b/src/pipeline-tasks/pipeline-task-detail.tsx @@ -0,0 +1,9 @@ +import { FC } from 'react' + +interface Props { + taskId: string; +} + +export const PipelineTaskDetail: FC = ({taskId}) => { + return
{taskId} detail
+} \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx index c0e0728..0de3eeb 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -3,11 +3,10 @@ import { prepareRoutes } from "@curi/router"; import { omit } from 'ramda'; import React from 'react'; import { CreateProjectInput, Pipeline, Project } from './generated/graphql'; -import { PipelineDetail } from './pipelines'; import { ProjectDetail, ProjectEditor, PROJECT } from "./projects"; import { COMMIT_LIST_QUERY } from './commons/graphql/queries'; import { CommitList } from './commits/commit-list'; -import { COMMITS } from './commits/queries'; +import { PipelineTaskDetail } from './pipeline-tasks/pipeline-task-detail'; export default prepareRoutes([ { @@ -93,9 +92,36 @@ export default prepareRoutes([ return { body: () => ( - + + + ), + }; + }, + respond({ resolved, error }) { + return resolved ||
Failed
; + }, + }, + { + name: "pipeline-task-detail", + path: "pipelines/:pipelineId/tasks/:taskId", + async resolve( + matched, + { client }: { client: ApolloClient } + ) { + const { data } = await client.query<{ + pipeline: Pipeline; + project: Project; + }>({ + query: COMMIT_LIST_QUERY, + variables: { + projectId: matched?.params.projectId, + pipelineId: matched?.params.pipelineId, + }, + }); + return { + body: () => ( + + ), };