From e5670d820d6abdd314b4073a7ed24cf3302b2383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Mon, 4 Mar 2024 17:25:49 +0300 Subject: [PATCH 01/13] refactor: rewrite useApps with RQ --- .../src/controllers/api/v1/apps/get-apps.js | 4 +-- .../ChooseAppAndEventSubstep/index.jsx | 25 ++++++++++++++--- .../src/components/ExecutionStep/index.jsx | 16 ++++++++--- .../web/src/components/FlowStep/index.jsx | 27 ++++++++++++++++--- packages/web/src/hooks/useApps.js | 25 ++++++++++------- 5 files changed, 76 insertions(+), 21 deletions(-) diff --git a/packages/backend/src/controllers/api/v1/apps/get-apps.js b/packages/backend/src/controllers/api/v1/apps/get-apps.js index be6e112e..4cedc706 100644 --- a/packages/backend/src/controllers/api/v1/apps/get-apps.js +++ b/packages/backend/src/controllers/api/v1/apps/get-apps.js @@ -4,11 +4,11 @@ import { renderObject } from '../../../../helpers/renderer.js'; export default async (request, response) => { let apps = await App.findAll(request.query.name); - if (request.query.onlyWithTriggers) { + if (request.query.onlyWithTriggers === 'true') { apps = apps.filter((app) => app.triggers?.length); } - if (request.query.onlyWithActions) { + if (request.query.onlyWithActions === 'true') { apps = apps.filter((app) => app.actions?.length); } diff --git a/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx b/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx index aa5da519..d71d82c8 100644 --- a/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx +++ b/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx @@ -8,6 +8,7 @@ import ListItem from '@mui/material/ListItem'; import TextField from '@mui/material/TextField'; import Autocomplete from '@mui/material/Autocomplete'; import Chip from '@mui/material/Chip'; + import useFormatMessage from 'hooks/useFormatMessage'; import useApps from 'hooks/useApps'; import { EditorContext } from 'contexts/Editor'; @@ -18,13 +19,16 @@ const optionGenerator = (app) => ({ label: app.name, value: app.key, }); + const eventOptionGenerator = (app) => ({ label: app.name, value: app.key, type: app?.type, }); + const getOption = (options, selectedOptionValue) => options.find((option) => option.value === selectedOptionValue); + function ChooseAppAndEventSubstep(props) { const { substep, @@ -39,26 +43,36 @@ function ChooseAppAndEventSubstep(props) { const editorContext = React.useContext(EditorContext); const isTrigger = step.type === 'trigger'; const isAction = step.type === 'action'; - const { apps } = useApps({ + + const { data: apps } = useApps({ onlyWithTriggers: isTrigger, onlyWithActions: isAction, }); - const app = apps?.find((currentApp) => currentApp.key === step.appKey); + + const app = apps?.data?.find((currentApp) => currentApp.key === step.appKey); + const appOptions = React.useMemo( - () => apps?.map((app) => optionGenerator(app)) || [], - [apps], + () => apps?.data?.map((app) => optionGenerator(app)) || [], + [apps?.data], ); + const actionsOrTriggers = (isTrigger ? app?.triggers : app?.actions) || []; + const actionOrTriggerOptions = React.useMemo( () => actionsOrTriggers.map((trigger) => eventOptionGenerator(trigger)), [app?.key], ); + const selectedActionOrTrigger = actionsOrTriggers.find( (actionOrTrigger) => actionOrTrigger.key === step?.key, ); + const isWebhook = isTrigger && selectedActionOrTrigger?.type === 'webhook'; + const { name } = substep; + const valid = !!step.key && !!step.appKey; + // placeholders const onEventChange = React.useCallback( (event, selectedOption) => { @@ -79,6 +93,7 @@ function ChooseAppAndEventSubstep(props) { }, [step, onChange], ); + const onAppChange = React.useCallback( (event, selectedOption) => { if (typeof selectedOption === 'object') { @@ -100,7 +115,9 @@ function ChooseAppAndEventSubstep(props) { }, [step, onChange], ); + const onToggle = expanded ? onCollapse : onExpand; + return ( {props.id} ); + return ( @@ -48,6 +51,7 @@ function ExecutionStepDate(props) { const formatMessage = useFormatMessage(); const createdAt = DateTime.fromMillis(parseInt(props.createdAt, 10)); const relativeCreatedAt = createdAt.toRelative(); + return ( currentApp.key === step.appKey); - if (!apps) return null; + + const app = apps?.data?.find((currentApp) => currentApp.key === step.appKey); + + if (!apps?.data) return null; + const validationStatusIcon = executionStep.status === 'success' ? validIcon : errorIcon; + const hasError = !!executionStep.errorDetails; + return (
diff --git a/packages/web/src/components/FlowStep/index.jsx b/packages/web/src/components/FlowStep/index.jsx index 8d360498..b4c16fc0 100644 --- a/packages/web/src/components/FlowStep/index.jsx +++ b/packages/web/src/components/FlowStep/index.jsx @@ -14,6 +14,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; + import { EditorContext } from 'contexts/Editor'; import { StepExecutionsProvider } from 'contexts/StepExecutions'; import TestSubstep from 'components/TestSubstep'; @@ -83,6 +84,7 @@ function generateValidationSchema(substeps) { } } } + return { ...allValidations, ...substepArgumentValidations, @@ -90,9 +92,11 @@ function generateValidationSchema(substeps) { }, {}, ); + const validationSchema = yup.object({ parameters: yup.object(fieldValidations), }); + return yupResolver(validationSchema); } @@ -106,16 +110,19 @@ function FlowStep(props) { const isAction = step.type === 'action'; const formatMessage = useFormatMessage(); const [currentSubstep, setCurrentSubstep] = React.useState(0); - const { apps } = useApps({ + + const { data: apps } = useApps({ onlyWithTriggers: isTrigger, onlyWithActions: isAction, }); + const [ getStepWithTestExecutions, { data: stepWithTestExecutionsData, called: stepWithTestExecutionsCalled }, ] = useLazyQuery(GET_STEP_WITH_TEST_EXECUTIONS, { fetchPolicy: 'network-only', }); + React.useEffect(() => { if (!stepWithTestExecutionsCalled && !collapsed && !isTrigger) { getStepWithTestExecutions({ @@ -131,26 +138,33 @@ function FlowStep(props) { step.id, isTrigger, ]); - const app = apps?.find((currentApp) => currentApp.key === step.appKey); + + const app = apps?.data?.find((currentApp) => currentApp.key === step.appKey); const actionsOrTriggers = (isTrigger ? app?.triggers : app?.actions) || []; const actionOrTrigger = actionsOrTriggers?.find( ({ key }) => key === step.key, ); + const substeps = actionOrTrigger?.substeps || []; + const handleChange = React.useCallback(({ step }) => { onChange(step); }, []); + const expandNextStep = React.useCallback(() => { setCurrentSubstep((currentSubstep) => (currentSubstep ?? 0) + 1); }, []); + const handleSubmit = (val) => { handleChange({ step: val }); }; + const stepValidationSchema = React.useMemo( () => generateValidationSchema(substeps), [substeps], ); - if (!apps) { + + if (!apps?.data) { return ( ); } + const onContextMenuClose = (event) => { event.stopPropagation(); setAnchorEl(null); }; + const onContextMenuClick = (event) => { event.stopPropagation(); setAnchorEl(contextButtonRef.current); }; + const onOpen = () => collapsed && props.onOpen?.(); + const onClose = () => props.onClose?.(); + const toggleSubstep = (substepIndex) => setCurrentSubstep((value) => value !== substepIndex ? substepIndex : null, ); + const validationStatusIcon = step.status === 'completed' ? validIcon : errorIcon; + return ( { + const { data } = await api.get('/v1/apps', { + params: variables, + signal, + }); + + return data; + }, }); - const apps = data?.getApps; - return { - apps, - loading, - }; + + return query; } From 6a58d1e3da9305bc8359d4e5bdbca852deb4dfef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Mon, 4 Mar 2024 18:04:26 +0300 Subject: [PATCH 02/13] fix(useAdminAppAuthClient): pass signal --- packages/web/src/hooks/useAdminAppAuthClient.ee.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/web/src/hooks/useAdminAppAuthClient.ee.js b/packages/web/src/hooks/useAdminAppAuthClient.ee.js index 29b23e75..32b31f27 100644 --- a/packages/web/src/hooks/useAdminAppAuthClient.ee.js +++ b/packages/web/src/hooks/useAdminAppAuthClient.ee.js @@ -6,7 +6,9 @@ export default function useAdminAppAuthClient(id) { const query = useQuery({ queryKey: ['adminAppAuthClient', id], queryFn: async ({ payload, signal }) => { - const { data } = await api.get(`/v1/admin/app-auth-clients/${id}`); + const { data } = await api.get(`/v1/admin/app-auth-clients/${id}`, { + signal, + }); return data; }, From 3301b038feda57520c0e48bc15f769359c349a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Tue, 5 Mar 2024 11:48:04 +0300 Subject: [PATCH 03/13] feat: add useTriggers with RQ --- .../ChooseAppAndEventSubstep/index.jsx | 11 ++++++++--- packages/web/src/components/FlowStep/index.jsx | 9 ++++++++- packages/web/src/hooks/useTriggers.js | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 packages/web/src/hooks/useTriggers.js diff --git a/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx b/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx index d71d82c8..f3054325 100644 --- a/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx +++ b/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx @@ -14,6 +14,7 @@ import useApps from 'hooks/useApps'; import { EditorContext } from 'contexts/Editor'; import FlowSubstepTitle from 'components/FlowSubstepTitle'; import { StepPropType, SubstepPropType } from 'propTypes/propTypes'; +import useTriggers from 'hooks/useTriggers'; const optionGenerator = (app) => ({ label: app.name, @@ -49,18 +50,22 @@ function ChooseAppAndEventSubstep(props) { onlyWithActions: isAction, }); - const app = apps?.data?.find((currentApp) => currentApp.key === step.appKey); + const app = apps?.data?.find( + (currentApp) => currentApp?.key === step?.appKey, + ); + + const { data: triggers } = useTriggers(app?.key); const appOptions = React.useMemo( () => apps?.data?.map((app) => optionGenerator(app)) || [], [apps?.data], ); - const actionsOrTriggers = (isTrigger ? app?.triggers : app?.actions) || []; + const actionsOrTriggers = (isTrigger ? triggers?.data : app?.actions) || []; const actionOrTriggerOptions = React.useMemo( () => actionsOrTriggers.map((trigger) => eventOptionGenerator(trigger)), - [app?.key], + [app?.key, actionsOrTriggers], ); const selectedActionOrTrigger = actionsOrTriggers.find( diff --git a/packages/web/src/components/FlowStep/index.jsx b/packages/web/src/components/FlowStep/index.jsx index b4c16fc0..cabd3f68 100644 --- a/packages/web/src/components/FlowStep/index.jsx +++ b/packages/web/src/components/FlowStep/index.jsx @@ -36,6 +36,7 @@ import { } from './style'; import isEmpty from 'helpers/isEmpty'; import { StepPropType } from 'propTypes/propTypes'; +import useTriggers from 'hooks/useTriggers'; const validIcon = ; const errorIcon = ; @@ -140,7 +141,13 @@ function FlowStep(props) { ]); const app = apps?.data?.find((currentApp) => currentApp.key === step.appKey); - const actionsOrTriggers = (isTrigger ? app?.triggers : app?.actions) || []; + + const { data: triggers } = useTriggers(app?.key); + + console.log('triggers:', triggers); + + const actionsOrTriggers = (isTrigger ? triggers?.data : app?.actions) || []; + const actionOrTrigger = actionsOrTriggers?.find( ({ key }) => key === step.key, ); diff --git a/packages/web/src/hooks/useTriggers.js b/packages/web/src/hooks/useTriggers.js new file mode 100644 index 00000000..cf029cd0 --- /dev/null +++ b/packages/web/src/hooks/useTriggers.js @@ -0,0 +1,18 @@ +import { useQuery } from '@tanstack/react-query'; + +import api from 'helpers/api'; + +export default function useTriggers(appKey) { + const query = useQuery({ + queryKey: ['triggers', appKey], + queryFn: async ({ payload, signal }) => { + const { data } = await api.get(`/v1/apps/${appKey}/triggers`, { + signal, + }); + + return data; + }, + }); + + return query; +} From c4b2ea125c996aa20a992befa668492fb546bc09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Tue, 5 Mar 2024 11:56:59 +0300 Subject: [PATCH 04/13] feat: add useActions with RQ --- .../ChooseAppAndEventSubstep/index.jsx | 5 ++++- packages/web/src/components/FlowStep/index.jsx | 5 +++-- packages/web/src/hooks/useActions.js | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 packages/web/src/hooks/useActions.js diff --git a/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx b/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx index f3054325..b8b116fe 100644 --- a/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx +++ b/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx @@ -15,6 +15,7 @@ import { EditorContext } from 'contexts/Editor'; import FlowSubstepTitle from 'components/FlowSubstepTitle'; import { StepPropType, SubstepPropType } from 'propTypes/propTypes'; import useTriggers from 'hooks/useTriggers'; +import useActions from 'hooks/useActions'; const optionGenerator = (app) => ({ label: app.name, @@ -56,12 +57,14 @@ function ChooseAppAndEventSubstep(props) { const { data: triggers } = useTriggers(app?.key); + const { data: actions } = useActions(app?.key); + const appOptions = React.useMemo( () => apps?.data?.map((app) => optionGenerator(app)) || [], [apps?.data], ); - const actionsOrTriggers = (isTrigger ? triggers?.data : app?.actions) || []; + const actionsOrTriggers = (isTrigger ? triggers?.data : actions?.data) || []; const actionOrTriggerOptions = React.useMemo( () => actionsOrTriggers.map((trigger) => eventOptionGenerator(trigger)), diff --git a/packages/web/src/components/FlowStep/index.jsx b/packages/web/src/components/FlowStep/index.jsx index cabd3f68..13704e75 100644 --- a/packages/web/src/components/FlowStep/index.jsx +++ b/packages/web/src/components/FlowStep/index.jsx @@ -37,6 +37,7 @@ import { import isEmpty from 'helpers/isEmpty'; import { StepPropType } from 'propTypes/propTypes'; import useTriggers from 'hooks/useTriggers'; +import useActions from 'hooks/useActions'; const validIcon = ; const errorIcon = ; @@ -144,9 +145,9 @@ function FlowStep(props) { const { data: triggers } = useTriggers(app?.key); - console.log('triggers:', triggers); + const { data: actions } = useActions(app?.key); - const actionsOrTriggers = (isTrigger ? triggers?.data : app?.actions) || []; + const actionsOrTriggers = (isTrigger ? triggers?.data : actions?.data) || []; const actionOrTrigger = actionsOrTriggers?.find( ({ key }) => key === step.key, diff --git a/packages/web/src/hooks/useActions.js b/packages/web/src/hooks/useActions.js new file mode 100644 index 00000000..ef48e843 --- /dev/null +++ b/packages/web/src/hooks/useActions.js @@ -0,0 +1,18 @@ +import { useQuery } from '@tanstack/react-query'; + +import api from 'helpers/api'; + +export default function useActions(appKey) { + const query = useQuery({ + queryKey: ['actions', appKey], + queryFn: async ({ payload, signal }) => { + const { data } = await api.get(`/v1/apps/${appKey}/actions`, { + signal, + }); + + return data; + }, + }); + + return query; +} From 5fe3546d2a74301036702902728c09153af79669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Tue, 5 Mar 2024 15:08:10 +0300 Subject: [PATCH 05/13] feat: add useTriggerSubsteps and useActionSubsteps with RQ --- .../web/src/components/FlowStep/index.jsx | 21 +++++++++++++++++- packages/web/src/hooks/useActionsSubsteps.js | 22 +++++++++++++++++++ packages/web/src/hooks/useTriggerSubsteps.js | 22 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 packages/web/src/hooks/useActionsSubsteps.js create mode 100644 packages/web/src/hooks/useTriggerSubsteps.js diff --git a/packages/web/src/components/FlowStep/index.jsx b/packages/web/src/components/FlowStep/index.jsx index 13704e75..1d78f73a 100644 --- a/packages/web/src/components/FlowStep/index.jsx +++ b/packages/web/src/components/FlowStep/index.jsx @@ -38,6 +38,8 @@ import isEmpty from 'helpers/isEmpty'; import { StepPropType } from 'propTypes/propTypes'; import useTriggers from 'hooks/useTriggers'; import useActions from 'hooks/useActions'; +import useTriggerSubsteps from 'hooks/useTriggerSubsteps'; +import useActionSubsteps from 'hooks/useActionsSubsteps'; const validIcon = ; const errorIcon = ; @@ -153,7 +155,24 @@ function FlowStep(props) { ({ key }) => key === step.key, ); - const substeps = actionOrTrigger?.substeps || []; + const { data: triggerSubsteps } = useTriggerSubsteps( + app?.key, + actionOrTrigger?.key, + ); + + const triggerSubstepsData = triggerSubsteps?.data || []; + + const { data: actionSubsteps } = useActionSubsteps( + app?.key, + actionOrTrigger?.key, + ); + + const actionSubstepsData = actionSubsteps?.data || []; + + const substeps = + triggerSubstepsData.length > 0 + ? triggerSubstepsData + : actionSubstepsData || []; const handleChange = React.useCallback(({ step }) => { onChange(step); diff --git a/packages/web/src/hooks/useActionsSubsteps.js b/packages/web/src/hooks/useActionsSubsteps.js new file mode 100644 index 00000000..bcc527b3 --- /dev/null +++ b/packages/web/src/hooks/useActionsSubsteps.js @@ -0,0 +1,22 @@ +import { useQuery } from '@tanstack/react-query'; + +import api from 'helpers/api'; + +export default function useActionSubsteps(appKey, actionKey) { + const query = useQuery({ + queryKey: ['actionSubsteps', appKey, actionKey], + queryFn: async ({ payload, signal }) => { + const { data } = await api.get( + `/v1/apps/${appKey}/actions/${actionKey}/substeps`, + { + signal, + }, + ); + + return data; + }, + enabled: !!appKey, + }); + + return query; +} diff --git a/packages/web/src/hooks/useTriggerSubsteps.js b/packages/web/src/hooks/useTriggerSubsteps.js new file mode 100644 index 00000000..d697ba5e --- /dev/null +++ b/packages/web/src/hooks/useTriggerSubsteps.js @@ -0,0 +1,22 @@ +import { useQuery } from '@tanstack/react-query'; + +import api from 'helpers/api'; + +export default function useTriggerSubsteps(appKey, triggerKey) { + const query = useQuery({ + queryKey: ['triggerSubsteps', appKey, triggerKey], + queryFn: async ({ payload, signal }) => { + const { data } = await api.get( + `/v1/apps/${appKey}/triggers/${triggerKey}/substeps`, + { + signal, + }, + ); + + return data; + }, + enabled: !!appKey, + }); + + return query; +} From be62c09d069a56b4a2b1ab96c6b072b2ce724abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Tue, 5 Mar 2024 17:35:22 +0300 Subject: [PATCH 06/13] refactor: rewrite useApp with RQ --- packages/web/src/hooks/useApp.js | 27 ++++++++++++------- .../web/src/hooks/useAuthenticateApp.ee.js | 15 +++++++++-- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/packages/web/src/hooks/useApp.js b/packages/web/src/hooks/useApp.js index 0fcbc51d..bc4edf2b 100644 --- a/packages/web/src/hooks/useApp.js +++ b/packages/web/src/hooks/useApp.js @@ -1,12 +1,19 @@ -import { useQuery } from '@apollo/client'; -import { GET_APP } from 'graphql/queries/get-app'; -export default function useApp(key) { - const { data, loading } = useQuery(GET_APP, { - variables: { key }, +import { useQuery } from '@tanstack/react-query'; + +import api from 'helpers/api'; + +export default function useApp(appKey) { + const query = useQuery({ + queryKey: ['app', appKey], + queryFn: async ({ payload, signal }) => { + const { data } = await api.get(`/v1/apps/${appKey}`, { + signal, + }); + + return data; + }, + enabled: !!appKey, }); - const app = data?.getApp; - return { - app, - loading, - }; + + return query; } diff --git a/packages/web/src/hooks/useAuthenticateApp.ee.js b/packages/web/src/hooks/useAuthenticateApp.ee.js index 77eb8465..e1478fa5 100644 --- a/packages/web/src/hooks/useAuthenticateApp.ee.js +++ b/packages/web/src/hooks/useAuthenticateApp.ee.js @@ -1,7 +1,9 @@ import * as React from 'react'; + import { processStep } from 'helpers/authenticationSteps'; import computeAuthStepVariables from 'helpers/computeAuthStepVariables'; import useApp from './useApp'; + function getSteps(auth, hasConnection, useShared) { if (hasConnection) { if (useShared) { @@ -9,19 +11,24 @@ function getSteps(auth, hasConnection, useShared) { } return auth?.reconnectionSteps; } + if (useShared) { return auth?.sharedAuthenticationSteps; } + return auth?.authenticationSteps; } + export default function useAuthenticateApp(payload) { const { appKey, appAuthClientId, connectionId, useShared = false } = payload; - const { app } = useApp(appKey); + const { data: app } = useApp(appKey); const [authenticationInProgress, setAuthenticationInProgress] = React.useState(false); - const steps = getSteps(app?.auth, !!connectionId, useShared); + const steps = getSteps(app?.data?.auth, !!connectionId, useShared); + const authenticate = React.useMemo(() => { if (!steps?.length) return; + return async function authenticate(payload = {}) { const { fields } = payload; setAuthenticationInProgress(true); @@ -34,9 +41,11 @@ export default function useAuthenticateApp(payload) { fields, }; let stepIndex = 0; + while (stepIndex < steps?.length) { const step = steps[stepIndex]; const variables = computeAuthStepVariables(step.arguments, response); + try { const stepResponse = await processStep(step, variables); response[step.name] = stepResponse; @@ -46,6 +55,7 @@ export default function useAuthenticateApp(payload) { throw err; } stepIndex++; + if (stepIndex === steps.length) { return response; } @@ -53,6 +63,7 @@ export default function useAuthenticateApp(payload) { } }; }, [steps, appKey, appAuthClientId, connectionId]); + return { authenticate, inProgress: authenticationInProgress, From c0cc6cc17658a7cb95ffb3f9f8d518b7dcafd45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Wed, 6 Mar 2024 14:36:37 +0300 Subject: [PATCH 07/13] refactor: rewrite get-apps queries with RQ --- .../backend/src/graphql/queries/get-apps.js | 17 -- .../backend/src/graphql/query-resolvers.js | 2 - packages/backend/src/graphql/schema.graphql | 5 - .../components/AddNewAppConnection/index.jsx | 56 +++-- packages/web/src/graphql/queries/get-apps.js | 234 ------------------ .../web/src/pages/AdminApplications/index.jsx | 24 +- 6 files changed, 48 insertions(+), 290 deletions(-) delete mode 100644 packages/backend/src/graphql/queries/get-apps.js delete mode 100644 packages/web/src/graphql/queries/get-apps.js diff --git a/packages/backend/src/graphql/queries/get-apps.js b/packages/backend/src/graphql/queries/get-apps.js deleted file mode 100644 index 2c3a78a0..00000000 --- a/packages/backend/src/graphql/queries/get-apps.js +++ /dev/null @@ -1,17 +0,0 @@ -import App from '../../models/app.js'; - -const getApps = async (_parent, params) => { - const apps = await App.findAll(params.name); - - if (params.onlyWithTriggers) { - return apps.filter((app) => app.triggers?.length); - } - - if (params.onlyWithActions) { - return apps.filter((app) => app.actions?.length); - } - - return apps; -}; - -export default getApps; diff --git a/packages/backend/src/graphql/query-resolvers.js b/packages/backend/src/graphql/query-resolvers.js index f4535809..71e75e9d 100644 --- a/packages/backend/src/graphql/query-resolvers.js +++ b/packages/backend/src/graphql/query-resolvers.js @@ -2,7 +2,6 @@ import getApp from './queries/get-app.js'; import getAppAuthClient from './queries/get-app-auth-client.ee.js'; import getAppAuthClients from './queries/get-app-auth-clients.ee.js'; import getAppConfig from './queries/get-app-config.ee.js'; -import getApps from './queries/get-apps.js'; import getBillingAndUsage from './queries/get-billing-and-usage.ee.js'; import getConfig from './queries/get-config.ee.js'; import getConnectedApps from './queries/get-connected-apps.js'; @@ -37,7 +36,6 @@ const queryResolvers = { getAppAuthClient, getAppAuthClients, getAppConfig, - getApps, getBillingAndUsage, getConfig, getConnectedApps, diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index f244b2f0..ddbdfdf6 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -1,9 +1,4 @@ type Query { - getApps( - name: String - onlyWithTriggers: Boolean - onlyWithActions: Boolean - ): [App] getApp(key: String!): App getAppConfig(key: String!): AppConfig getAppAuthClient(id: String!): AppAuthClient diff --git a/packages/web/src/components/AddNewAppConnection/index.jsx b/packages/web/src/components/AddNewAppConnection/index.jsx index def8b3cc..f798eba1 100644 --- a/packages/web/src/components/AddNewAppConnection/index.jsx +++ b/packages/web/src/components/AddNewAppConnection/index.jsx @@ -1,6 +1,5 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { useLazyQuery } from '@apollo/client'; import { Link } from 'react-router-dom'; import debounce from 'lodash/debounce'; import { useTheme } from '@mui/material/styles'; @@ -20,15 +19,18 @@ import InputLabel from '@mui/material/InputLabel'; import OutlinedInput from '@mui/material/OutlinedInput'; import FormControl from '@mui/material/FormControl'; import Box from '@mui/material/Box'; + +import api from 'helpers/api'; import * as URLS from 'config/urls'; import AppIcon from 'components/AppIcon'; -import { GET_APPS } from 'graphql/queries/get-apps'; import useFormatMessage from 'hooks/useFormatMessage'; +import { useMutation } from '@tanstack/react-query'; function createConnectionOrFlow(appKey, supportsConnections = false) { if (!supportsConnections) { return URLS.CREATE_FLOW_WITH_APP(appKey); } + return URLS.APP_ADD_CONNECTION(appKey); } function AddNewAppConnection(props) { @@ -36,29 +38,31 @@ function AddNewAppConnection(props) { const theme = useTheme(); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('sm')); const formatMessage = useFormatMessage(); - const [appName, setAppName] = React.useState(null); - const [loading, setLoading] = React.useState(false); - const [getApps, { data }] = useLazyQuery(GET_APPS, { - onCompleted: () => { - setLoading(false); + const [appName, setAppName] = React.useState(''); + const [isLoading, setIsLoading] = React.useState(false); + + const { data: apps, mutate } = useMutation({ + mutationFn: async ({ payload, signal }) => { + const { data } = await api.get('/v1/apps', { + params: { name: appName }, + }); + + return data; + }, + onSuccess: () => { + setIsLoading(false); }, }); - const fetchData = React.useMemo( - () => debounce((name) => getApps({ variables: { name } }), 300), - [getApps], - ); - React.useEffect( - function fetchAppsOnAppNameChange() { - setLoading(true); - fetchData(appName); - }, - [fetchData, appName], - ); - React.useEffect(function cancelDebounceOnUnmount() { - return () => { - fetchData.cancel(); - }; - }, []); + + const fetchData = React.useMemo(() => debounce(mutate, 300), [mutate]); + + React.useEffect(() => { + setIsLoading(true); + fetchData(appName); + + return () => fetchData.cancel(); + }, [fetchData, appName]); + return ( - {loading && ( + {isLoading && ( )} - {!loading && - data?.getApps?.map((app) => ( + {!isLoading && + apps?.data.map((app) => ( { + const { data } = await api.get('/v1/apps', { + params: { name: appName }, + signal, + }); + + return data; + }, }); - const apps = data?.getApps; + const onSearchChange = React.useCallback((event) => { setAppName(event.target.value); }, []); + return ( @@ -44,7 +56,7 @@ function AdminApplications() { )} {!appsLoading && - apps?.map((app) => ( + apps?.data?.map((app) => ( From f320a44d4575a15f591dab04bac1264a3b30d27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Wed, 6 Mar 2024 14:46:06 +0300 Subject: [PATCH 08/13] refactor: rewrite get-app queries with RQ --- .../web/src/pages/AdminApplication/index.jsx | 25 +++++++++++++--- packages/web/src/pages/Application/index.jsx | 30 ++++++++++++++++--- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/packages/web/src/pages/AdminApplication/index.jsx b/packages/web/src/pages/AdminApplication/index.jsx index b42d3d11..6cda49db 100644 --- a/packages/web/src/pages/AdminApplication/index.jsx +++ b/packages/web/src/pages/AdminApplication/index.jsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { useQuery } from '@apollo/client'; import { Link, Route, @@ -15,8 +14,8 @@ import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; import Tabs from '@mui/material/Tabs'; import Tab from '@mui/material/Tab'; + import useFormatMessage from 'hooks/useFormatMessage'; -import { GET_APP } from 'graphql/queries/get-app'; import * as URLS from 'config/urls'; import AppIcon from 'components/AppIcon'; import Container from 'components/Container'; @@ -25,6 +24,9 @@ import AdminApplicationSettings from 'components/AdminApplicationSettings'; import AdminApplicationAuthClients from 'components/AdminApplicationAuthClients'; import AdminApplicationCreateAuthClient from 'components/AdminApplicationCreateAuthClient'; import AdminApplicationUpdateAuthClient from 'components/AdminApplicationUpdateAuthClient'; +import { useQuery } from '@tanstack/react-query'; +import api from 'helpers/api'; + export default function AdminApplication() { const theme = useTheme(); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); @@ -43,10 +45,25 @@ export default function AdminApplication() { end: false, }); const { appKey } = useParams(); - const { data, loading } = useQuery(GET_APP, { variables: { key: appKey } }); - const app = data?.getApp || {}; + + const { data, loading } = useQuery({ + queryKey: ['app', appKey], + queryFn: async ({ payload, signal }) => { + const { data } = await api.get(`/v1/apps/${appKey}`, { + signal, + }); + + return data; + }, + enabled: !!appKey, + }); + + const app = data?.data || {}; + const goToAuthClientsPage = () => navigate('auth-clients'); + if (loading) return null; + return ( <> diff --git a/packages/web/src/pages/Application/index.jsx b/packages/web/src/pages/Application/index.jsx index a7ff4966..49e22a30 100644 --- a/packages/web/src/pages/Application/index.jsx +++ b/packages/web/src/pages/Application/index.jsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { useQuery } from '@apollo/client'; import { Link, Route, @@ -17,9 +16,9 @@ import Grid from '@mui/material/Grid'; import Tabs from '@mui/material/Tabs'; import Tab from '@mui/material/Tab'; import AddIcon from '@mui/icons-material/Add'; + import useFormatMessage from 'hooks/useFormatMessage'; import useAppConfig from 'hooks/useAppConfig.ee'; -import { GET_APP } from 'graphql/queries/get-app'; import * as URLS from 'config/urls'; import SplitButton from 'components/SplitButton'; import ConditionalIconButton from 'components/ConditionalIconButton'; @@ -29,6 +28,9 @@ import AddAppConnection from 'components/AddAppConnection'; import AppIcon from 'components/AppIcon'; import Container from 'components/Container'; import PageTitle from 'components/PageTitle'; +import api from 'helpers/api'; +import { useQuery } from '@tanstack/react-query'; + const ReconnectConnection = (props) => { const { application, onClose } = props; const { connectionId } = useParams(); @@ -40,6 +42,7 @@ const ReconnectConnection = (props) => { /> ); }; + export default function Application() { const theme = useTheme(); const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md')); @@ -52,11 +55,26 @@ export default function Application() { const [searchParams] = useSearchParams(); const { appKey } = useParams(); const navigate = useNavigate(); - const { data, loading } = useQuery(GET_APP, { variables: { key: appKey } }); + + const { data, loading } = useQuery({ + queryKey: ['app', appKey], + queryFn: async ({ payload, signal }) => { + const { data } = await api.get(`/v1/apps/${appKey}`, { + signal, + }); + + return data; + }, + enabled: !!appKey, + }); + + const app = data?.data || {}; + const { appConfig } = useAppConfig(appKey); const connectionId = searchParams.get('connectionId') || undefined; + const goToApplicationPage = () => navigate('connections'); - const app = data?.getApp || {}; + const connectionOptions = React.useMemo(() => { const shouldHaveCustomConnection = appConfig?.canConnect && appConfig?.canCustomConnect; @@ -68,6 +86,7 @@ export default function Application() { to: URLS.APP_ADD_CONNECTION(appKey, appConfig?.canConnect), }, ]; + if (shouldHaveCustomConnection) { options.push({ label: formatMessage('app.addCustomConnection'), @@ -76,9 +95,12 @@ export default function Application() { to: URLS.APP_ADD_CONNECTION(appKey), }); } + return options; }, [appKey, appConfig]); + if (loading) return null; + return ( <> From 28c8be97b6250e30c33758d421644ea1a78c5e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Wed, 6 Mar 2024 18:37:46 +0300 Subject: [PATCH 09/13] feat: add useLazyApps hook --- .../components/AddNewAppConnection/index.jsx | 28 +++++++++++------- packages/web/src/hooks/useLazyApps.js | 29 +++++++++++++++++++ 2 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 packages/web/src/hooks/useLazyApps.js diff --git a/packages/web/src/components/AddNewAppConnection/index.jsx b/packages/web/src/components/AddNewAppConnection/index.jsx index f798eba1..ea26e1ab 100644 --- a/packages/web/src/components/AddNewAppConnection/index.jsx +++ b/packages/web/src/components/AddNewAppConnection/index.jsx @@ -20,11 +20,10 @@ import OutlinedInput from '@mui/material/OutlinedInput'; import FormControl from '@mui/material/FormControl'; import Box from '@mui/material/Box'; -import api from 'helpers/api'; import * as URLS from 'config/urls'; import AppIcon from 'components/AppIcon'; import useFormatMessage from 'hooks/useFormatMessage'; -import { useMutation } from '@tanstack/react-query'; +import useLazyApps from 'hooks/useLazyApps'; function createConnectionOrFlow(appKey, supportsConnections = false) { if (!supportsConnections) { @@ -40,27 +39,34 @@ function AddNewAppConnection(props) { const formatMessage = useFormatMessage(); const [appName, setAppName] = React.useState(''); const [isLoading, setIsLoading] = React.useState(false); + const abortControllerRef = React.useRef(null); - const { data: apps, mutate } = useMutation({ - mutationFn: async ({ payload, signal }) => { - const { data } = await api.get('/v1/apps', { - params: { name: appName }, - }); - - return data; - }, + const { data: apps, mutate } = useLazyApps({ + appName, onSuccess: () => { setIsLoading(false); }, + abortControllerRef, }); const fetchData = React.useMemo(() => debounce(mutate, 300), [mutate]); React.useEffect(() => { setIsLoading(true); + + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + abortControllerRef.current = new AbortController(); + fetchData(appName); - return () => fetchData.cancel(); + return () => { + fetchData.cancel(); + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; }, [fetchData, appName]); return ( diff --git a/packages/web/src/hooks/useLazyApps.js b/packages/web/src/hooks/useLazyApps.js new file mode 100644 index 00000000..327692c6 --- /dev/null +++ b/packages/web/src/hooks/useLazyApps.js @@ -0,0 +1,29 @@ +import { useMutation } from '@tanstack/react-query'; + +import api from 'helpers/api'; + +export default function useLazyApps({ + appName, + onSuccess, + abortControllerRef, +}) { + const query = useMutation({ + mutationFn: async ({ payload, signal }) => { + abortControllerRef.current = new AbortController(); + + const { data } = await api.get('/v1/apps', { + params: { name: appName }, + signal: abortControllerRef.current.signal, + }); + + if (abortControllerRef.current.signal.aborted) { + return; + } + + return data; + }, + onSuccess, + }); + + return query; +} From a4c0edf493719283ba160abc3a14884039f55b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Thu, 7 Mar 2024 11:18:06 +0300 Subject: [PATCH 10/13] refactor: remove getApp query from web --- packages/web/src/graphql/queries/get-app.js | 105 -------------------- 1 file changed, 105 deletions(-) delete mode 100644 packages/web/src/graphql/queries/get-app.js diff --git a/packages/web/src/graphql/queries/get-app.js b/packages/web/src/graphql/queries/get-app.js deleted file mode 100644 index edfd362d..00000000 --- a/packages/web/src/graphql/queries/get-app.js +++ /dev/null @@ -1,105 +0,0 @@ -import { gql } from '@apollo/client'; -export const GET_APP = gql` - query GetApp($key: String!) { - getApp(key: $key) { - name - key - iconUrl - docUrl - authDocUrl - primaryColor - supportsConnections - auth { - fields { - key - label - type - required - readOnly - value - description - docUrl - clickToCopy - options { - label - value - } - } - authenticationSteps { - type - name - arguments { - name - value - type - properties { - name - value - } - } - } - sharedAuthenticationSteps { - type - name - arguments { - name - value - type - properties { - name - value - } - } - } - reconnectionSteps { - type - name - arguments { - name - value - type - properties { - name - value - } - } - } - sharedReconnectionSteps { - type - name - arguments { - name - value - type - properties { - name - value - } - } - } - } - connections { - id - } - triggers { - name - key - type - showWebhookUrl - pollInterval - description - substeps { - name - } - } - actions { - name - key - description - substeps { - name - } - } - } - } -`; From 2ee5af8bfba0c64e4391aa9d7314ed23ebe651f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Thu, 7 Mar 2024 15:54:00 +0300 Subject: [PATCH 11/13] feat: add useAppAuth with RQ --- .../src/components/AddAppConnection/index.jsx | 18 +++++++++++-- .../index.jsx | 26 ++++++++++++++----- .../ChooseConnectionSubstep/index.jsx | 1 + packages/web/src/hooks/useAppAuth.js | 19 ++++++++++++++ .../web/src/hooks/useAuthenticateApp.ee.js | 6 ++--- .../web/src/pages/AdminApplication/index.jsx | 15 ++--------- .../web/src/pages/AdminApplications/index.jsx | 19 +++----------- packages/web/src/pages/Application/index.jsx | 17 +++--------- 8 files changed, 68 insertions(+), 53 deletions(-) create mode 100644 packages/web/src/hooks/useAppAuth.js diff --git a/packages/web/src/components/AddAppConnection/index.jsx b/packages/web/src/components/AddAppConnection/index.jsx index 3b9737ea..41d72328 100644 --- a/packages/web/src/components/AddAppConnection/index.jsx +++ b/packages/web/src/components/AddAppConnection/index.jsx @@ -16,10 +16,12 @@ import useAuthenticateApp from 'hooks/useAuthenticateApp.ee'; import useFormatMessage from 'hooks/useFormatMessage'; import { generateExternalLink } from 'helpers/translationValues'; import { Form } from './style'; +import useAppAuth from 'hooks/useAppAuth'; function AddAppConnection(props) { const { application, connectionId, onClose } = props; - const { name, authDocUrl, key, auth } = application; + const { name, authDocUrl, key } = application; + const { data: auth } = useAppAuth(key); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const formatMessage = useFormatMessage(); @@ -34,31 +36,40 @@ function AddAppConnection(props) { appAuthClientId, useShared: !!appAuthClientId, }); + React.useEffect(function relayProviderData() { if (window.opener) { window.opener.postMessage({ source: 'automatisch', payload: { search: window.location.search, hash: window.location.hash }, }); + window.close(); } }, []); + React.useEffect( function initiateSharedAuthenticationForGivenAuthClient() { if (!appAuthClientId) return; + if (!authenticate) return; + const asyncAuthenticate = async () => { await authenticate(); navigate(URLS.APP_CONNECTIONS(key)); }; + asyncAuthenticate(); }, [appAuthClientId, authenticate], ); + const handleClientClick = (appAuthClientId) => navigate(URLS.APP_ADD_CONNECTION_WITH_AUTH_CLIENT_ID(key, appAuthClientId)); + const handleAuthClientsDialogClose = () => navigate(URLS.APP_CONNECTIONS(key)); + const submitHandler = React.useCallback( async (data) => { if (!authenticate) return; @@ -78,6 +89,7 @@ function AddAppConnection(props) { }, [authenticate], ); + if (useShared) return ( ); + if (appAuthClientId) return ; + return ( @@ -121,7 +135,7 @@ function AddAppConnection(props) {
- {auth?.fields?.map((field) => ( + {auth?.data?.fields?.map((field) => ( ))} diff --git a/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx b/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx index 4fe7c0e4..394db3f5 100644 --- a/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx +++ b/packages/web/src/components/AdminApplicationCreateAuthClient/index.jsx @@ -8,12 +8,14 @@ import { CREATE_APP_AUTH_CLIENT } from 'graphql/mutations/create-app-auth-client import useAppConfig from 'hooks/useAppConfig.ee'; import useFormatMessage from 'hooks/useFormatMessage'; import AdminApplicationAuthClientDialog from 'components/AdminApplicationAuthClientDialog'; +import useAppAuth from 'hooks/useAppAuth'; function AdminApplicationCreateAuthClient(props) { - const { appKey, application, onClose } = props; - const { auth } = application; + const { appKey, onClose } = props; + const { data: auth } = useAppAuth(appKey); const formatMessage = useFormatMessage(); const { appConfig, loading: loadingAppConfig } = useAppConfig(appKey); + const [ createAppConfig, { loading: loadingCreateAppConfig, error: createAppConfigError }, @@ -21,6 +23,7 @@ function AdminApplicationCreateAuthClient(props) { refetchQueries: ['GetAppConfig'], context: { autoSnackbar: false }, }); + const [ createAppAuthClient, { loading: loadingCreateAppAuthClient, error: createAppAuthClientError }, @@ -28,8 +31,10 @@ function AdminApplicationCreateAuthClient(props) { refetchQueries: ['GetAppAuthClients'], context: { autoSnackbar: false }, }); + const submitHandler = async (values) => { let appConfigId = appConfig?.id; + if (!appConfigId) { const { data: appConfigData } = await createAppConfig({ variables: { @@ -41,8 +46,10 @@ function AdminApplicationCreateAuthClient(props) { }, }, }); + appConfigId = appConfigData.createAppConfig.id; } + const { name, active, ...formattedAuthDefaults } = values; await createAppAuthClient({ variables: { @@ -54,22 +61,28 @@ function AdminApplicationCreateAuthClient(props) { }, }, }); + onClose(); }; + const getAuthFieldsDefaultValues = useCallback(() => { - if (!auth?.fields) { + if (!auth?.data?.fields) { return {}; } + const defaultValues = {}; - auth.fields.forEach((field) => { + + auth.data.fields.forEach((field) => { if (field.value || field.type !== 'string') { defaultValues[field.key] = field.value; } else if (field.type === 'string') { defaultValues[field.key] = ''; } }); + return defaultValues; - }, [auth?.fields]); + }, [auth?.data?.fields]); + const defaultValues = useMemo( () => ({ name: '', @@ -78,6 +91,7 @@ function AdminApplicationCreateAuthClient(props) { }), [getAuthFieldsDefaultValues], ); + return ( diff --git a/packages/web/src/components/ChooseConnectionSubstep/index.jsx b/packages/web/src/components/ChooseConnectionSubstep/index.jsx index e5b0b8f3..303e6e0a 100644 --- a/packages/web/src/components/ChooseConnectionSubstep/index.jsx +++ b/packages/web/src/components/ChooseConnectionSubstep/index.jsx @@ -176,6 +176,7 @@ function ChooseConnectionSubstep(props) { } }, [step.connection?.id, retestConnection]); const onToggle = expanded ? onCollapse : onExpand; + return ( { + const { data } = await api.get(`/v1/apps/${appKey}/auth`, { + signal, + }); + + return data; + }, + enabled: !!appKey, + }); + + return query; +} diff --git a/packages/web/src/hooks/useAuthenticateApp.ee.js b/packages/web/src/hooks/useAuthenticateApp.ee.js index e1478fa5..68c8af8a 100644 --- a/packages/web/src/hooks/useAuthenticateApp.ee.js +++ b/packages/web/src/hooks/useAuthenticateApp.ee.js @@ -2,7 +2,7 @@ import * as React from 'react'; import { processStep } from 'helpers/authenticationSteps'; import computeAuthStepVariables from 'helpers/computeAuthStepVariables'; -import useApp from './useApp'; +import useAppAuth from './useAppAuth'; function getSteps(auth, hasConnection, useShared) { if (hasConnection) { @@ -21,10 +21,10 @@ function getSteps(auth, hasConnection, useShared) { export default function useAuthenticateApp(payload) { const { appKey, appAuthClientId, connectionId, useShared = false } = payload; - const { data: app } = useApp(appKey); + const { data: auth } = useAppAuth(appKey); const [authenticationInProgress, setAuthenticationInProgress] = React.useState(false); - const steps = getSteps(app?.data?.auth, !!connectionId, useShared); + const steps = getSteps(auth?.data, !!connectionId, useShared); const authenticate = React.useMemo(() => { if (!steps?.length) return; diff --git a/packages/web/src/pages/AdminApplication/index.jsx b/packages/web/src/pages/AdminApplication/index.jsx index 6cda49db..44e73085 100644 --- a/packages/web/src/pages/AdminApplication/index.jsx +++ b/packages/web/src/pages/AdminApplication/index.jsx @@ -24,8 +24,7 @@ import AdminApplicationSettings from 'components/AdminApplicationSettings'; import AdminApplicationAuthClients from 'components/AdminApplicationAuthClients'; import AdminApplicationCreateAuthClient from 'components/AdminApplicationCreateAuthClient'; import AdminApplicationUpdateAuthClient from 'components/AdminApplicationUpdateAuthClient'; -import { useQuery } from '@tanstack/react-query'; -import api from 'helpers/api'; +import useApp from 'hooks/useApp'; export default function AdminApplication() { const theme = useTheme(); @@ -46,17 +45,7 @@ export default function AdminApplication() { }); const { appKey } = useParams(); - const { data, loading } = useQuery({ - queryKey: ['app', appKey], - queryFn: async ({ payload, signal }) => { - const { data } = await api.get(`/v1/apps/${appKey}`, { - signal, - }); - - return data; - }, - enabled: !!appKey, - }); + const { data, loading } = useApp(appKey); const app = data?.data || {}; diff --git a/packages/web/src/pages/AdminApplications/index.jsx b/packages/web/src/pages/AdminApplications/index.jsx index 0dfca76b..a4c65316 100644 --- a/packages/web/src/pages/AdminApplications/index.jsx +++ b/packages/web/src/pages/AdminApplications/index.jsx @@ -2,7 +2,6 @@ import * as React from 'react'; import Grid from '@mui/material/Grid'; import CircularProgress from '@mui/material/CircularProgress'; import Divider from '@mui/material/Divider'; -import { useQuery } from '@tanstack/react-query'; import PageTitle from 'components/PageTitle'; import Container from 'components/Container'; @@ -10,23 +9,13 @@ import SearchInput from 'components/SearchInput'; import AppRow from 'components/AppRow'; import * as URLS from 'config/urls'; import useFormatMessage from 'hooks/useFormatMessage'; -import api from 'helpers/api'; +import useApps from 'hooks/useApps'; function AdminApplications() { const formatMessage = useFormatMessage(); const [appName, setAppName] = React.useState(null); - const { data: apps, isLoading: appsLoading } = useQuery({ - queryKey: ['apps', appName], - queryFn: async ({ payload, signal }) => { - const { data } = await api.get('/v1/apps', { - params: { name: appName }, - signal, - }); - - return data; - }, - }); + const { data: apps, isLoading: isAppsLoading } = useApps(appName); const onSearchChange = React.useCallback((event) => { setAppName(event.target.value); @@ -48,14 +37,14 @@ function AdminApplications() { - {appsLoading && ( + {isAppsLoading && ( )} - {!appsLoading && + {!isAppsLoading && apps?.data?.map((app) => ( diff --git a/packages/web/src/pages/Application/index.jsx b/packages/web/src/pages/Application/index.jsx index 49e22a30..f7588d1a 100644 --- a/packages/web/src/pages/Application/index.jsx +++ b/packages/web/src/pages/Application/index.jsx @@ -28,12 +28,12 @@ import AddAppConnection from 'components/AddAppConnection'; import AppIcon from 'components/AppIcon'; import Container from 'components/Container'; import PageTitle from 'components/PageTitle'; -import api from 'helpers/api'; -import { useQuery } from '@tanstack/react-query'; +import useApp from 'hooks/useApp'; const ReconnectConnection = (props) => { const { application, onClose } = props; const { connectionId } = useParams(); + return ( { - const { data } = await api.get(`/v1/apps/${appKey}`, { - signal, - }); - - return data; - }, - enabled: !!appKey, - }); - + const { data, loading } = useApp(appKey); const app = data?.data || {}; const { appConfig } = useAppConfig(appKey); From 5835def5d0dceff6811e184bab991419e70fb85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Mon, 11 Mar 2024 16:16:12 +0300 Subject: [PATCH 12/13] refactor: move abort controller into useLazyApps hook --- .../src/controllers/api/v1/apps/get-apps.js | 4 +-- .../components/AddNewAppConnection/index.jsx | 10 -------- .../ChooseAppAndEventSubstep/index.jsx | 16 ++++++++---- .../src/components/ExecutionStep/index.jsx | 14 ++++++++--- .../web/src/components/FlowStep/index.jsx | 16 ++++++++---- ...ctionsSubsteps.js => useActionSubsteps.js} | 0 packages/web/src/hooks/useLazyApps.js | 25 ++++++++++--------- 7 files changed, 47 insertions(+), 38 deletions(-) rename packages/web/src/hooks/{useActionsSubsteps.js => useActionSubsteps.js} (100%) diff --git a/packages/backend/src/controllers/api/v1/apps/get-apps.js b/packages/backend/src/controllers/api/v1/apps/get-apps.js index 4cedc706..be6e112e 100644 --- a/packages/backend/src/controllers/api/v1/apps/get-apps.js +++ b/packages/backend/src/controllers/api/v1/apps/get-apps.js @@ -4,11 +4,11 @@ import { renderObject } from '../../../../helpers/renderer.js'; export default async (request, response) => { let apps = await App.findAll(request.query.name); - if (request.query.onlyWithTriggers === 'true') { + if (request.query.onlyWithTriggers) { apps = apps.filter((app) => app.triggers?.length); } - if (request.query.onlyWithActions === 'true') { + if (request.query.onlyWithActions) { apps = apps.filter((app) => app.actions?.length); } diff --git a/packages/web/src/components/AddNewAppConnection/index.jsx b/packages/web/src/components/AddNewAppConnection/index.jsx index ea26e1ab..0ba62b66 100644 --- a/packages/web/src/components/AddNewAppConnection/index.jsx +++ b/packages/web/src/components/AddNewAppConnection/index.jsx @@ -39,14 +39,12 @@ function AddNewAppConnection(props) { const formatMessage = useFormatMessage(); const [appName, setAppName] = React.useState(''); const [isLoading, setIsLoading] = React.useState(false); - const abortControllerRef = React.useRef(null); const { data: apps, mutate } = useLazyApps({ appName, onSuccess: () => { setIsLoading(false); }, - abortControllerRef, }); const fetchData = React.useMemo(() => debounce(mutate, 300), [mutate]); @@ -54,18 +52,10 @@ function AddNewAppConnection(props) { React.useEffect(() => { setIsLoading(true); - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } - abortControllerRef.current = new AbortController(); - fetchData(appName); return () => { fetchData.cancel(); - if (abortControllerRef.current) { - abortControllerRef.current.abort(); - } }; }, [fetchData, appName]); diff --git a/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx b/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx index b8b116fe..de37fcde 100644 --- a/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx +++ b/packages/web/src/components/ChooseAppAndEventSubstep/index.jsx @@ -45,11 +45,17 @@ function ChooseAppAndEventSubstep(props) { const editorContext = React.useContext(EditorContext); const isTrigger = step.type === 'trigger'; const isAction = step.type === 'action'; + const useAppsOptions = {}; - const { data: apps } = useApps({ - onlyWithTriggers: isTrigger, - onlyWithActions: isAction, - }); + if (isTrigger) { + useAppsOptions.onlyWithTriggers = true; + } + + if (isAction) { + useAppsOptions.onlyWithActions = true; + } + + const { data: apps } = useApps(useAppsOptions); const app = apps?.data?.find( (currentApp) => currentApp?.key === step?.appKey, @@ -68,7 +74,7 @@ function ChooseAppAndEventSubstep(props) { const actionOrTriggerOptions = React.useMemo( () => actionsOrTriggers.map((trigger) => eventOptionGenerator(trigger)), - [app?.key, actionsOrTriggers], + [actionsOrTriggers], ); const selectedActionOrTrigger = actionsOrTriggers.find( diff --git a/packages/web/src/components/ExecutionStep/index.jsx b/packages/web/src/components/ExecutionStep/index.jsx index f1051c3e..04cdc826 100644 --- a/packages/web/src/components/ExecutionStep/index.jsx +++ b/packages/web/src/components/ExecutionStep/index.jsx @@ -79,11 +79,17 @@ function ExecutionStep(props) { const isTrigger = step.type === 'trigger'; const isAction = step.type === 'action'; const formatMessage = useFormatMessage(); + const useAppsOptions = {}; - const { data: apps } = useApps({ - onlyWithTriggers: isTrigger, - onlyWithActions: isAction, - }); + if (isTrigger) { + useAppsOptions.onlyWithTriggers = true; + } + + if (isAction) { + useAppsOptions.onlyWithActions = true; + } + + const { data: apps } = useApps(useAppsOptions); const app = apps?.data?.find((currentApp) => currentApp.key === step.appKey); diff --git a/packages/web/src/components/FlowStep/index.jsx b/packages/web/src/components/FlowStep/index.jsx index 1d78f73a..b999ec4f 100644 --- a/packages/web/src/components/FlowStep/index.jsx +++ b/packages/web/src/components/FlowStep/index.jsx @@ -39,7 +39,7 @@ import { StepPropType } from 'propTypes/propTypes'; import useTriggers from 'hooks/useTriggers'; import useActions from 'hooks/useActions'; import useTriggerSubsteps from 'hooks/useTriggerSubsteps'; -import useActionSubsteps from 'hooks/useActionsSubsteps'; +import useActionSubsteps from 'hooks/useActionSubsteps'; const validIcon = ; const errorIcon = ; @@ -114,11 +114,17 @@ function FlowStep(props) { const isAction = step.type === 'action'; const formatMessage = useFormatMessage(); const [currentSubstep, setCurrentSubstep] = React.useState(0); + const useAppsOptions = {}; - const { data: apps } = useApps({ - onlyWithTriggers: isTrigger, - onlyWithActions: isAction, - }); + if (isTrigger) { + useAppsOptions.onlyWithTriggers = true; + } + + if (isAction) { + useAppsOptions.onlyWithActions = true; + } + + const { data: apps } = useApps(useAppsOptions); const [ getStepWithTestExecutions, diff --git a/packages/web/src/hooks/useActionsSubsteps.js b/packages/web/src/hooks/useActionSubsteps.js similarity index 100% rename from packages/web/src/hooks/useActionsSubsteps.js rename to packages/web/src/hooks/useActionSubsteps.js diff --git a/packages/web/src/hooks/useLazyApps.js b/packages/web/src/hooks/useLazyApps.js index 327692c6..291ffcc2 100644 --- a/packages/web/src/hooks/useLazyApps.js +++ b/packages/web/src/hooks/useLazyApps.js @@ -1,25 +1,26 @@ import { useMutation } from '@tanstack/react-query'; import api from 'helpers/api'; +import React from 'react'; + +export default function useLazyApps({ appName, onSuccess }) { + const abortControllerRef = React.useRef(new AbortController()); + + React.useEffect(() => { + abortControllerRef.current = new AbortController(); + + return () => { + abortControllerRef.current?.abort(); + }; + }, [appName]); -export default function useLazyApps({ - appName, - onSuccess, - abortControllerRef, -}) { const query = useMutation({ - mutationFn: async ({ payload, signal }) => { - abortControllerRef.current = new AbortController(); - + mutationFn: async ({ payload }) => { const { data } = await api.get('/v1/apps', { params: { name: appName }, signal: abortControllerRef.current.signal, }); - if (abortControllerRef.current.signal.aborted) { - return; - } - return data; }, onSuccess, From c9ff6d7bb972cfba4569dba283f562fe1cc95977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C4=B1dvan=20Akca?= Date: Mon, 11 Mar 2024 17:17:09 +0300 Subject: [PATCH 13/13] fix: pass enabled to useTriggers and useActions hooks --- packages/web/src/hooks/useActions.js | 1 + packages/web/src/hooks/useTriggers.js | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/web/src/hooks/useActions.js b/packages/web/src/hooks/useActions.js index ef48e843..41295ee9 100644 --- a/packages/web/src/hooks/useActions.js +++ b/packages/web/src/hooks/useActions.js @@ -12,6 +12,7 @@ export default function useActions(appKey) { return data; }, + enabled: !!appKey, }); return query; diff --git a/packages/web/src/hooks/useTriggers.js b/packages/web/src/hooks/useTriggers.js index cf029cd0..c0caa287 100644 --- a/packages/web/src/hooks/useTriggers.js +++ b/packages/web/src/hooks/useTriggers.js @@ -12,6 +12,7 @@ export default function useTriggers(appKey) { return data; }, + enabled: !!appKey, }); return query;