diff --git a/packages/backend/src/apps/twitter/index.ts b/packages/backend/src/apps/twitter/index.ts index 31272c72..8771bd36 100644 --- a/packages/backend/src/apps/twitter/index.ts +++ b/packages/backend/src/apps/twitter/index.ts @@ -1,7 +1,6 @@ import TwitterApi from 'twitter-api-v2'; import App from '../../models/app'; import Field from '../../types/field'; -import appData from './info'; export default class Twitter { client: any @@ -17,7 +16,7 @@ export default class Twitter { }); this.connectionData = connectionData; - this.appData = appData; + this.appData = App.findOneByKey('twitter'); } async createAuthLink() { diff --git a/packages/backend/src/graphql/mutations/create-auth-link.ts b/packages/backend/src/graphql/mutations/create-auth-link.ts index af0ce3bf..4b129950 100644 --- a/packages/backend/src/graphql/mutations/create-auth-link.ts +++ b/packages/backend/src/graphql/mutations/create-auth-link.ts @@ -14,7 +14,7 @@ const createAuthLinkResolver = async (params: Params, req: RequestWithCurrentUse const appClass = (await import(`../../apps/${connection.key}`)).default; - const appInstance = new appClass(connection.data) + const appInstance = new appClass({ consumerKey: connection.data.consumerKey, consumerSecret: connection.data.consumerSecret }); const authLink = await appInstance.createAuthLink(); await connection.$query().patch({ diff --git a/packages/backend/src/graphql/mutations/update-connection.ts b/packages/backend/src/graphql/mutations/update-connection.ts index 8e10e195..00a6f44d 100644 --- a/packages/backend/src/graphql/mutations/update-connection.ts +++ b/packages/backend/src/graphql/mutations/update-connection.ts @@ -21,14 +21,17 @@ const updateConnectionResolver = async (params: Params, req: RequestWithCurrentU } }) - const appClass = (await import(`../../apps/${connection.key}`)).default; + // Not every updateConnection mutation can verify credentials as some need to reconnect + try { + const appClass = (await import(`../../apps/${connection.key}`)).default; - const appInstance = new appClass(connection.data) - const verifiedCredentials = await appInstance.verifyCredentials(); + const appInstance = new appClass(connection.data) + const verifiedCredentials = await appInstance.verifyCredentials(); - connection = await connection.$query().patchAndFetch({ - data: verifiedCredentials - }) + connection = await connection.$query().patchAndFetch({ + data: verifiedCredentials + }) + } catch {} return connection; } diff --git a/packages/backend/src/helpers/graphql-instance.ts b/packages/backend/src/helpers/graphql-instance.ts index 614ec183..e0753ff9 100644 --- a/packages/backend/src/helpers/graphql-instance.ts +++ b/packages/backend/src/helpers/graphql-instance.ts @@ -4,6 +4,12 @@ import graphQLSchema from '../graphql/graphql-schema' const graphQLInstance = graphqlHTTP({ schema: graphQLSchema, graphiql: true, + customFormatErrorFn: (error) => ({ + message: error.message, + locations: error.locations, + stack: error.stack ? error.stack.split('\n') : [], + path: error.path, + }) }) export default graphQLInstance; diff --git a/packages/web/src/components/AddAppConnection/index.tsx b/packages/web/src/components/AddAppConnection/index.tsx index f586e6e8..a4d029f4 100644 --- a/packages/web/src/components/AddAppConnection/index.tsx +++ b/packages/web/src/components/AddAppConnection/index.tsx @@ -15,6 +15,7 @@ import { Form } from './style'; type AddAppConnectionProps = { onClose: () => void; application: App; + connectionId?: string; }; type Response = { @@ -22,8 +23,8 @@ type Response = { } export default function AddAppConnection(props: AddAppConnectionProps){ - const { application, onClose } = props; - const { key, fields, authenticationSteps } = application; + const { application, connectionId, onClose } = props; + const { key, fields, authenticationSteps, reconnectionSteps } = application; useEffect(() => { if (window.opener) { @@ -33,14 +34,21 @@ export default function AddAppConnection(props: AddAppConnectionProps){ }, []); const submitHandler: SubmitHandler = useCallback(async (data) => { + const hasConnection = Boolean(connectionId); + const response: Response = { key, + connection: { + id: connectionId + }, fields: data, }; + const steps = hasConnection ? reconnectionSteps : authenticationSteps; + let stepIndex = 0; - while (stepIndex < authenticationSteps.length) { - const step = authenticationSteps[stepIndex]; + while (stepIndex < steps.length) { + const step = steps[stepIndex]; const variables = computeAuthStepVariables(step, response); const stepResponse = await processStep(step, variables); @@ -51,7 +59,7 @@ export default function AddAppConnection(props: AddAppConnectionProps){ } onClose?.(); - }, [authenticationSteps, key, onClose]); + }, [connectionId, key, reconnectionSteps, authenticationSteps, onClose]); return ( diff --git a/packages/web/src/components/AppConnectionContextMenu/index.tsx b/packages/web/src/components/AppConnectionContextMenu/index.tsx index c51ec851..fcc64668 100644 --- a/packages/web/src/components/AppConnectionContextMenu/index.tsx +++ b/packages/web/src/components/AppConnectionContextMenu/index.tsx @@ -13,13 +13,14 @@ type Action = { type ContextMenuProps = { appKey: string; + connectionId: string; onClose: () => void; onMenuItemClick: (event: React.MouseEvent, action: Action) => void; anchorEl: PopoverProps['anchorEl']; }; export default function ContextMenu(props: ContextMenuProps) { - const { appKey, onClose, onMenuItemClick, anchorEl } = props; + const { appKey, connectionId, onClose, onMenuItemClick, anchorEl } = props; const formatMessage = useFormatMessage(); const createActionHandler = React.useCallback((action: Action) => { @@ -37,7 +38,11 @@ export default function ContextMenu(props: ContextMenuProps) { hideBackdrop={false} anchorEl={anchorEl} > - + {formatMessage('connection.viewFlows')} @@ -45,7 +50,11 @@ export default function ContextMenu(props: ContextMenuProps) { {formatMessage('connection.testConnection')} - + {formatMessage('connection.reconnect')} diff --git a/packages/web/src/components/AppConnectionRow/index.tsx b/packages/web/src/components/AppConnectionRow/index.tsx index 7734c157..357946d5 100644 --- a/packages/web/src/components/AppConnectionRow/index.tsx +++ b/packages/web/src/components/AppConnectionRow/index.tsx @@ -19,11 +19,11 @@ type AppConnectionRowProps = { const countTranslation = (value: React.ReactNode) => (<>{value}
); function AppConnectionRow(props: AppConnectionRowProps) { - const [testConnection, { data: testData, called: testCalled, loading: testLoading }] = useLazyQuery(TEST_CONNECTION); + const [testConnection, { called: testCalled, loading: testLoading }] = useLazyQuery(TEST_CONNECTION); const [deleteConnection] = useMutation(DELETE_CONNECTION); const formatMessage = useFormatMessage(); - const { id, key, data } = props.connection; + const { id, key, data, verified } = props.connection; const contextButtonRef = React.useRef(null); const [anchorEl, setAnchorEl] = React.useState(null); @@ -39,7 +39,7 @@ function AppConnectionRow(props: AppConnectionRowProps) { variables: { id }, update: (cache, mutationResult) => { const connectionCacheId = cache.identify({ - __typename: 'connection', + __typename: 'Connection', id, }); @@ -65,7 +65,7 @@ function AppConnectionRow(props: AppConnectionRowProps) { - {testCalled && !testLoading && (testData ? 'yes' : 'no')} + {testCalled && !testLoading && (verified ? 'yes' : 'no')} @@ -83,6 +83,7 @@ function AppConnectionRow(props: AppConnectionRowProps) { {anchorEl && - } - primary={formatMessage('drawer.dashboard')} - to={URLS.DASHBOARD} - /> - } primary={formatMessage('drawer.flows')} diff --git a/packages/web/src/config/urls.ts b/packages/web/src/config/urls.ts index 98aadc44..b9def6a6 100644 --- a/packages/web/src/config/urls.ts +++ b/packages/web/src/config/urls.ts @@ -8,6 +8,8 @@ export const APP_CONNECTIONS = (appKey: string) => `/app/${appKey}/connections`; export const APP_CONNECTIONS_PATTERN = '/app/:key/connections'; export const APP_ADD_CONNECTION = (appKey: string) => `/app/${appKey}/connections/add`; export const APP_ADD_CONNECTION_PATTERN = '/app/:key/connections/add'; +export const APP_RECONNECT_CONNECTION = (appKey: string, connectionId: string) => `/app/${appKey}/connections/${connectionId}/reconnect`; +export const APP_RECONNECT_CONNECTION_PATTERN = '/app/:key/connections/:connectionId/reconnect'; export const APP_FLOWS = (appKey: string) => `/app/${appKey}/flows`; export const APP_FLOWS_PATTERN = '/app/:key/flows'; diff --git a/packages/web/src/graphql/mutations/create-connection.ts b/packages/web/src/graphql/mutations/create-connection.ts index 1380a38a..fe856e10 100644 --- a/packages/web/src/graphql/mutations/create-connection.ts +++ b/packages/web/src/graphql/mutations/create-connection.ts @@ -1,7 +1,7 @@ import { gql } from '@apollo/client'; export const CREATE_CONNECTION = gql` - mutation CreateConnection($key: String!, $data: twitterCredentialInput!) { + mutation CreateConnection($key: String!, $data: TwitterCredentialInput!) { createConnection(key: $key, data: $data) { id key diff --git a/packages/web/src/graphql/mutations/update-connection.ts b/packages/web/src/graphql/mutations/update-connection.ts index c637d81a..82e8e1f4 100644 --- a/packages/web/src/graphql/mutations/update-connection.ts +++ b/packages/web/src/graphql/mutations/update-connection.ts @@ -1,7 +1,7 @@ import { gql } from '@apollo/client'; export const UPDATE_CONNECTION = gql` - mutation UpdateConnection($id: String!, $data: twitterCredentialInput!) { + mutation UpdateConnection($id: String!, $data: TwitterCredentialInput!) { updateConnection(id: $id, data: $data) { id key diff --git a/packages/web/src/graphql/queries/get-app.ts b/packages/web/src/graphql/queries/get-app.ts index 67ed318a..85a307ce 100644 --- a/packages/web/src/graphql/queries/get-app.ts +++ b/packages/web/src/graphql/queries/get-app.ts @@ -32,6 +32,19 @@ export const GET_APP = gql` } } } + reconnectionSteps { + step + type + name + fields { + name + value + fields { + name + value + } + } + } connections { id } diff --git a/packages/web/src/graphql/queries/test-connection.ts b/packages/web/src/graphql/queries/test-connection.ts index 5a0275de..83771f87 100644 --- a/packages/web/src/graphql/queries/test-connection.ts +++ b/packages/web/src/graphql/queries/test-connection.ts @@ -2,6 +2,9 @@ import { gql } from '@apollo/client'; export const TEST_CONNECTION = gql` query TestConnection($id: String!) { - testConnection(id: $id) + testConnection(id: $id) { + id + verified + } } `; \ No newline at end of file diff --git a/packages/web/src/pages/Application/index.tsx b/packages/web/src/pages/Application/index.tsx index 310a3fd2..12bc4ca0 100644 --- a/packages/web/src/pages/Application/index.tsx +++ b/packages/web/src/pages/Application/index.tsx @@ -1,5 +1,5 @@ import { useQuery } from '@apollo/client'; -import { Link, Route, Redirect, Switch, useParams, useRouteMatch, useHistory } from 'react-router-dom'; +import { Link, Route, Redirect, Switch, RouteComponentProps, useParams, useRouteMatch, useHistory } from 'react-router-dom'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; import Button from '@mui/material/Button'; @@ -20,6 +20,7 @@ import PageTitle from 'components/PageTitle'; type ApplicationParams = { key: string; + connectionId?: string; }; export default function Application() { @@ -95,9 +96,9 @@ export default function Application() { - - - + ) => ( + + )} /> ); }; diff --git a/packages/web/src/routes.tsx b/packages/web/src/routes.tsx index f822da4e..f62f496a 100644 --- a/packages/web/src/routes.tsx +++ b/packages/web/src/routes.tsx @@ -1,5 +1,4 @@ import { Route, Switch, Redirect } from "react-router"; -import Dashboard from 'pages/Dashboard'; import Applications from 'pages/Applications'; import Application from 'pages/Application'; import Flows from 'pages/Flows'; @@ -8,10 +7,6 @@ import * as URLS from 'config/urls'; export default ( - - - - @@ -29,7 +24,7 @@ export default ( - + diff --git a/packages/web/src/types/app.ts b/packages/web/src/types/app.ts index f00c6d89..ebb8b849 100644 --- a/packages/web/src/types/app.ts +++ b/packages/web/src/types/app.ts @@ -19,6 +19,7 @@ type App = { primaryColor: string; fields: AppFields[]; authenticationSteps: any[]; + reconnectionSteps: any[]; }; export type { App, AppFields };