diff --git a/packages/backend/src/graphql/queries/get-flows.js b/packages/backend/src/graphql/queries/get-flows.js deleted file mode 100644 index 6f3899e8..00000000 --- a/packages/backend/src/graphql/queries/get-flows.js +++ /dev/null @@ -1,40 +0,0 @@ -import Flow from '../../models/flow.js'; -import paginate from '../../helpers/pagination.js'; - -const getFlows = async (_parent, params, context) => { - const conditions = context.currentUser.can('read', 'Flow'); - const userFlows = context.currentUser.$relatedQuery('flows'); - const allFlows = Flow.query(); - const baseQuery = conditions.isCreator ? userFlows : allFlows; - - const flowsQuery = baseQuery - .clone() - .joinRelated({ - steps: true, - }) - .withGraphFetched({ - steps: { - connection: true, - }, - }) - .where((builder) => { - if (params.connectionId) { - builder.where('steps.connection_id', params.connectionId); - } - - if (params.name) { - builder.where('flows.name', 'ilike', `%${params.name}%`); - } - - if (params.appKey) { - builder.where('steps.app_key', params.appKey); - } - }) - .groupBy('flows.id') - .orderBy('active', 'desc') - .orderBy('updated_at', 'desc'); - - return paginate(flowsQuery, params.limit, params.offset); -}; - -export default getFlows; diff --git a/packages/backend/src/graphql/query-resolvers.js b/packages/backend/src/graphql/query-resolvers.js index 74ccedee..54fdc798 100644 --- a/packages/backend/src/graphql/query-resolvers.js +++ b/packages/backend/src/graphql/query-resolvers.js @@ -7,7 +7,6 @@ import getConnectedApps from './queries/get-connected-apps.js'; import getDynamicData from './queries/get-dynamic-data.js'; import getDynamicFields from './queries/get-dynamic-fields.js'; import getFlow from './queries/get-flow.js'; -import getFlows from './queries/get-flows.js'; import getNotifications from './queries/get-notifications.js'; import getStepWithTestExecutions from './queries/get-step-with-test-executions.js'; import getUsers from './queries/get-users.js'; @@ -23,7 +22,6 @@ const queryResolvers = { getDynamicData, getDynamicFields, getFlow, - getFlows, getNotifications, getStepWithTestExecutions, getUsers, diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 3e1ab036..1e61d56b 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -5,13 +5,6 @@ type Query { getConnectedApps(name: String): [App] testConnection(id: String!): Connection getFlow(id: String!): Flow - getFlows( - limit: Int! - offset: Int! - appKey: String - connectionId: String - name: String - ): FlowConnection getStepWithTestExecutions(stepId: String!): [Step] getDynamicData( stepId: String! @@ -257,15 +250,6 @@ type Field { options: [SubstepArgumentOption] } -type FlowConnection { - edges: [FlowEdge] - pageInfo: PageInfo -} - -type FlowEdge { - node: Flow -} - enum FlowStatus { paused published diff --git a/packages/web/src/components/AppFlows/index.jsx b/packages/web/src/components/AppFlows/index.jsx index bf5f4d8e..23ea2f14 100644 --- a/packages/web/src/components/AppFlows/index.jsx +++ b/packages/web/src/components/AppFlows/index.jsx @@ -1,35 +1,39 @@ import PropTypes from 'prop-types'; -import { useQuery } from '@apollo/client'; import { Link, useSearchParams } from 'react-router-dom'; -import { GET_FLOWS } from 'graphql/queries/get-flows'; import Pagination from '@mui/material/Pagination'; import PaginationItem from '@mui/material/PaginationItem'; + import * as URLS from 'config/urls'; import AppFlowRow from 'components/FlowRow'; import NoResultFound from 'components/NoResultFound'; import useFormatMessage from 'hooks/useFormatMessage'; -const FLOW_PER_PAGE = 10; -const getLimitAndOffset = (page) => ({ - limit: FLOW_PER_PAGE, - offset: (page - 1) * FLOW_PER_PAGE, -}); +import useConnectionFlows from 'hooks/useConnectionFlows'; +import useAppFlows from 'hooks/useAppFlows'; + function AppFlows(props) { const { appKey } = props; const formatMessage = useFormatMessage(); const [searchParams, setSearchParams] = useSearchParams(); const connectionId = searchParams.get('connectionId') || undefined; const page = parseInt(searchParams.get('page') || '', 10) || 1; - const { data } = useQuery(GET_FLOWS, { - variables: { - appKey, - connectionId, - ...getLimitAndOffset(page), - }, - }); - const getFlows = data?.getFlows || {}; - const { pageInfo, edges } = getFlows; - const flows = edges?.map(({ node }) => node); + const isConnectionFlowEnabled = !!connectionId; + const isAppFlowEnabled = !!appKey && !connectionId; + + const connectionFlows = useConnectionFlows( + { connectionId, page }, + { enabled: isConnectionFlowEnabled }, + ); + + const appFlows = useAppFlows({ appKey, page }, { enabled: isAppFlowEnabled }); + + const flows = isConnectionFlowEnabled + ? connectionFlows?.data?.data || [] + : appFlows?.data?.data || []; + const pageInfo = isConnectionFlowEnabled + ? connectionFlows?.data?.meta || [] + : appFlows?.data?.meta || []; const hasFlows = flows?.length; + if (!hasFlows) { return ( ); } + return ( <> {flows?.map((appFlow) => ( diff --git a/packages/web/src/graphql/queries/get-flows.js b/packages/web/src/graphql/queries/get-flows.js deleted file mode 100644 index e4213d31..00000000 --- a/packages/web/src/graphql/queries/get-flows.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client'; -export const GET_FLOWS = gql` - query GetFlows( - $limit: Int! - $offset: Int! - $appKey: String - $connectionId: String - $name: String - ) { - getFlows( - limit: $limit - offset: $offset - appKey: $appKey - connectionId: $connectionId - name: $name - ) { - pageInfo { - currentPage - totalPages - } - edges { - node { - id - name - createdAt - updatedAt - active - status - steps { - iconUrl - } - } - } - } - } -`; diff --git a/packages/web/src/hooks/useAppFlows.js b/packages/web/src/hooks/useAppFlows.js new file mode 100644 index 00000000..8ff98766 --- /dev/null +++ b/packages/web/src/hooks/useAppFlows.js @@ -0,0 +1,22 @@ +import { useQuery } from '@tanstack/react-query'; + +import api from 'helpers/api'; + +export default function useAppFlows({ appKey, page }, { enabled }) { + const query = useQuery({ + queryKey: ['appFlows', appKey, page], + queryFn: async ({ signal }) => { + const { data } = await api.get(`/v1/apps/${appKey}/flows`, { + params: { + page, + }, + signal, + }); + + return data; + }, + enabled, + }); + + return query; +} diff --git a/packages/web/src/hooks/useConnectionFlows.js b/packages/web/src/hooks/useConnectionFlows.js new file mode 100644 index 00000000..d8799567 --- /dev/null +++ b/packages/web/src/hooks/useConnectionFlows.js @@ -0,0 +1,25 @@ +import { useQuery } from '@tanstack/react-query'; + +import api from 'helpers/api'; + +export default function useConnectionFlows( + { connectionId, page }, + { enabled }, +) { + const query = useQuery({ + queryKey: ['connectionFlows', connectionId, page], + queryFn: async ({ signal }) => { + const { data } = await api.get(`/v1/connections/${connectionId}/flows`, { + params: { + page, + }, + signal, + }); + + return data; + }, + enabled, + }); + + return query; +} diff --git a/packages/web/src/hooks/useLazyFlows.js b/packages/web/src/hooks/useLazyFlows.js new file mode 100644 index 00000000..8de9ac82 --- /dev/null +++ b/packages/web/src/hooks/useLazyFlows.js @@ -0,0 +1,30 @@ +import * as React from 'react'; + +import api from 'helpers/api'; +import { useMutation } from '@tanstack/react-query'; + +export default function useLazyFlows({ flowName, page }, { onSuccess }) { + const abortControllerRef = React.useRef(new AbortController()); + + React.useEffect(() => { + abortControllerRef.current = new AbortController(); + + return () => { + abortControllerRef.current?.abort(); + }; + }, [flowName]); + + const query = useMutation({ + mutationFn: async () => { + const { data } = await api.get('/v1/flows', { + params: { name: flowName, page }, + signal: abortControllerRef.current.signal, + }); + + return data; + }, + onSuccess, + }); + + return query; +} diff --git a/packages/web/src/pages/Flows/index.jsx b/packages/web/src/pages/Flows/index.jsx index 462a89eb..c77f72a4 100644 --- a/packages/web/src/pages/Flows/index.jsx +++ b/packages/web/src/pages/Flows/index.jsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { Link, useSearchParams } from 'react-router-dom'; -import { useLazyQuery } from '@apollo/client'; import debounce from 'lodash/debounce'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; @@ -9,6 +8,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import Divider from '@mui/material/Divider'; import Pagination from '@mui/material/Pagination'; import PaginationItem from '@mui/material/PaginationItem'; + import Can from 'components/Can'; import FlowRow from 'components/FlowRow'; import NoResultFound from 'components/NoResultFound'; @@ -17,45 +17,37 @@ import Container from 'components/Container'; import PageTitle from 'components/PageTitle'; import SearchInput from 'components/SearchInput'; import useFormatMessage from 'hooks/useFormatMessage'; -import { GET_FLOWS } from 'graphql/queries/get-flows'; import * as URLS from 'config/urls'; -const FLOW_PER_PAGE = 10; -const getLimitAndOffset = (page) => ({ - limit: FLOW_PER_PAGE, - offset: (page - 1) * FLOW_PER_PAGE, -}); +import useLazyFlows from 'hooks/useLazyFlows'; + export default function Flows() { const formatMessage = useFormatMessage(); const [searchParams, setSearchParams] = useSearchParams(); const page = parseInt(searchParams.get('page') || '', 10) || 1; const [flowName, setFlowName] = React.useState(''); - const [loading, setLoading] = React.useState(false); - const [getFlows, { data }] = useLazyQuery(GET_FLOWS, { - onCompleted: () => { - setLoading(false); + const [isLoading, setIsLoading] = React.useState(false); + + const { data, mutate } = useLazyFlows( + { flowName, page }, + { + onSuccess: () => { + setIsLoading(false); + }, }, - }); - const fetchData = React.useMemo( - () => - debounce( - (name) => - getFlows({ - variables: { - ...getLimitAndOffset(page), - name, - }, - }), - 300, - ), - [page, getFlows], - ); - React.useEffect( - function fetchFlowsOnSearch() { - setLoading(true); - fetchData(flowName); - }, - [fetchData, flowName], ); + + const fetchData = React.useMemo(() => debounce(mutate, 300), [mutate]); + + React.useEffect(() => { + setIsLoading(true); + + fetchData({ flowName, page }); + + return () => { + fetchData.cancel(); + }; + }, [fetchData, flowName, page]); + React.useEffect( function resetPageOnSearch() { // reset search params which only consists of `page` @@ -63,17 +55,15 @@ export default function Flows() { }, [flowName], ); - React.useEffect(function cancelDebounceOnUnmount() { - return () => { - fetchData.cancel(); - }; - }, []); - const { pageInfo, edges } = data?.getFlows || {}; - const flows = edges?.map(({ node }) => node); + + const flows = data?.data || []; + const pageInfo = data?.meta; const hasFlows = flows?.length; + const onSearchChange = React.useCallback((event) => { setFlowName(event.target.value); }, []); + return ( @@ -116,18 +106,18 @@ export default function Flows() { - {loading && ( + {isLoading && ( )} - {!loading && + {!isLoading && flows?.map((flow) => )} - {!loading && !hasFlows && ( + {!isLoading && !hasFlows && ( )} - {!loading && pageInfo && pageInfo.totalPages > 1 && ( + {!isLoading && pageInfo && pageInfo.totalPages > 1 && (