refactor: rewrite useFlow and useStepConnection with RQ
This commit is contained in:
@@ -1,19 +0,0 @@
|
|||||||
import Flow from '../../models/flow.js';
|
|
||||||
|
|
||||||
const getFlow = 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 flow = await baseQuery
|
|
||||||
.clone()
|
|
||||||
.withGraphJoined('[steps.[connection]]')
|
|
||||||
.orderBy('steps.position', 'asc')
|
|
||||||
.findOne({ 'flows.id': params.id })
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
return flow;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getFlow;
|
|
@@ -1,240 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import app from '../../app';
|
|
||||||
import appConfig from '../../config/app';
|
|
||||||
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
|
|
||||||
import { createRole } from '../../../test/factories/role';
|
|
||||||
import { createPermission } from '../../../test/factories/permission';
|
|
||||||
import { createUser } from '../../../test/factories/user';
|
|
||||||
import { createFlow } from '../../../test/factories/flow';
|
|
||||||
import { createStep } from '../../../test/factories/step';
|
|
||||||
import { createConnection } from '../../../test/factories/connection';
|
|
||||||
|
|
||||||
describe('graphQL getFlow query', () => {
|
|
||||||
const query = (flowId) => {
|
|
||||||
return `
|
|
||||||
query {
|
|
||||||
getFlow(id: "${flowId}") {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
active
|
|
||||||
status
|
|
||||||
steps {
|
|
||||||
id
|
|
||||||
type
|
|
||||||
key
|
|
||||||
appKey
|
|
||||||
iconUrl
|
|
||||||
webhookUrl
|
|
||||||
status
|
|
||||||
position
|
|
||||||
connection {
|
|
||||||
id
|
|
||||||
verified
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
parameters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('and without permissions', () => {
|
|
||||||
it('should throw not authorized error', async () => {
|
|
||||||
const userWithoutPermissions = await createUser();
|
|
||||||
const token = createAuthTokenByUserId(userWithoutPermissions.id);
|
|
||||||
const flow = await createFlow();
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query: query(flow.id) })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
expect(response.body.errors).toBeDefined();
|
|
||||||
expect(response.body.errors[0].message).toEqual('Not authorized!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with correct permission', () => {
|
|
||||||
let currentUser, currentUserRole, currentUserFlow;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUserRole = await createRole();
|
|
||||||
currentUser = await createUser({ roleId: currentUserRole.id });
|
|
||||||
currentUserFlow = await createFlow({ userId: currentUser.id });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and with isCreator condition', () => {
|
|
||||||
it('should return executions data of the current user', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerStep = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
webhookPath: `/webhooks/flows/${currentUserFlow.id}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionConnection = await createConnection({
|
|
||||||
userId: currentUser.id,
|
|
||||||
formattedData: {
|
|
||||||
screenName: 'Test',
|
|
||||||
authenticationKey: 'test key',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionStep = await createStep({
|
|
||||||
flowId: currentUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
connectionId: actionConnection.id,
|
|
||||||
key: 'translateText',
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query: query(currentUserFlow.id) })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getFlow: {
|
|
||||||
active: currentUserFlow.active,
|
|
||||||
id: currentUserFlow.id,
|
|
||||||
name: currentUserFlow.name,
|
|
||||||
status: 'draft',
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
appKey: triggerStep.appKey,
|
|
||||||
connection: null,
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: triggerStep.id,
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: triggerStep.status,
|
|
||||||
type: 'trigger',
|
|
||||||
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${currentUserFlow.id}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
appKey: actionStep.appKey,
|
|
||||||
connection: {
|
|
||||||
createdAt: actionConnection.createdAt.getTime().toString(),
|
|
||||||
id: actionConnection.id,
|
|
||||||
verified: actionConnection.verified,
|
|
||||||
},
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: actionStep.id,
|
|
||||||
key: 'translateText',
|
|
||||||
parameters: {},
|
|
||||||
position: 2,
|
|
||||||
status: actionStep.status,
|
|
||||||
type: 'action',
|
|
||||||
webhookUrl: 'http://localhost:3000/null',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('and without isCreator condition', () => {
|
|
||||||
it('should return executions data of all users', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUser = await createUser();
|
|
||||||
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
|
|
||||||
|
|
||||||
const triggerStep = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'trigger',
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
webhookPath: `/webhooks/flows/${anotherUserFlow.id}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionConnection = await createConnection({
|
|
||||||
userId: anotherUser.id,
|
|
||||||
formattedData: {
|
|
||||||
screenName: 'Test',
|
|
||||||
authenticationKey: 'test key',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionStep = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
type: 'action',
|
|
||||||
connectionId: actionConnection.id,
|
|
||||||
key: 'translateText',
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/graphql')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.send({ query: query(anotherUserFlow.id) })
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponsePayload = {
|
|
||||||
data: {
|
|
||||||
getFlow: {
|
|
||||||
active: anotherUserFlow.active,
|
|
||||||
id: anotherUserFlow.id,
|
|
||||||
name: anotherUserFlow.name,
|
|
||||||
status: 'draft',
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
appKey: triggerStep.appKey,
|
|
||||||
connection: null,
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: triggerStep.id,
|
|
||||||
key: 'catchRawWebhook',
|
|
||||||
parameters: {},
|
|
||||||
position: 1,
|
|
||||||
status: triggerStep.status,
|
|
||||||
type: 'trigger',
|
|
||||||
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${anotherUserFlow.id}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
appKey: actionStep.appKey,
|
|
||||||
connection: {
|
|
||||||
createdAt: actionConnection.createdAt.getTime().toString(),
|
|
||||||
id: actionConnection.id,
|
|
||||||
verified: actionConnection.verified,
|
|
||||||
},
|
|
||||||
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
|
|
||||||
id: actionStep.id,
|
|
||||||
key: 'translateText',
|
|
||||||
parameters: {},
|
|
||||||
position: 2,
|
|
||||||
status: actionStep.status,
|
|
||||||
type: 'action',
|
|
||||||
webhookUrl: 'http://localhost:3000/null',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedResponsePayload);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -4,7 +4,6 @@ import getAppAuthClients from './queries/get-app-auth-clients.ee.js';
|
|||||||
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
|
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
|
||||||
import getConnectedApps from './queries/get-connected-apps.js';
|
import getConnectedApps from './queries/get-connected-apps.js';
|
||||||
import getDynamicData from './queries/get-dynamic-data.js';
|
import getDynamicData from './queries/get-dynamic-data.js';
|
||||||
import getFlow from './queries/get-flow.js';
|
|
||||||
import getStepWithTestExecutions from './queries/get-step-with-test-executions.js';
|
import getStepWithTestExecutions from './queries/get-step-with-test-executions.js';
|
||||||
import testConnection from './queries/test-connection.js';
|
import testConnection from './queries/test-connection.js';
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ const queryResolvers = {
|
|||||||
getBillingAndUsage,
|
getBillingAndUsage,
|
||||||
getConnectedApps,
|
getConnectedApps,
|
||||||
getDynamicData,
|
getDynamicData,
|
||||||
getFlow,
|
|
||||||
getStepWithTestExecutions,
|
getStepWithTestExecutions,
|
||||||
testConnection,
|
testConnection,
|
||||||
};
|
};
|
||||||
|
@@ -4,7 +4,6 @@ type Query {
|
|||||||
getAppAuthClients(appKey: String!, active: Boolean): [AppAuthClient]
|
getAppAuthClients(appKey: String!, active: Boolean): [AppAuthClient]
|
||||||
getConnectedApps(name: String): [App]
|
getConnectedApps(name: String): [App]
|
||||||
testConnection(id: String!): Connection
|
testConnection(id: String!): Connection
|
||||||
getFlow(id: String!): Flow
|
|
||||||
getStepWithTestExecutions(stepId: String!): [Step]
|
getStepWithTestExecutions(stepId: String!): [Step]
|
||||||
getDynamicData(
|
getDynamicData(
|
||||||
stepId: String!
|
stepId: String!
|
||||||
|
@@ -21,6 +21,8 @@ import {
|
|||||||
StepPropType,
|
StepPropType,
|
||||||
SubstepPropType,
|
SubstepPropType,
|
||||||
} from 'propTypes/propTypes';
|
} from 'propTypes/propTypes';
|
||||||
|
import useStepConnection from 'hooks/useStepConnection';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
const ADD_CONNECTION_VALUE = 'ADD_CONNECTION';
|
const ADD_CONNECTION_VALUE = 'ADD_CONNECTION';
|
||||||
const ADD_SHARED_CONNECTION_VALUE = 'ADD_SHARED_CONNECTION';
|
const ADD_SHARED_CONNECTION_VALUE = 'ADD_SHARED_CONNECTION';
|
||||||
@@ -44,13 +46,14 @@ function ChooseConnectionSubstep(props) {
|
|||||||
onChange,
|
onChange,
|
||||||
application,
|
application,
|
||||||
} = props;
|
} = props;
|
||||||
const { connection, appKey } = step;
|
const { appKey } = step;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const editorContext = React.useContext(EditorContext);
|
const editorContext = React.useContext(EditorContext);
|
||||||
const [showAddConnectionDialog, setShowAddConnectionDialog] =
|
const [showAddConnectionDialog, setShowAddConnectionDialog] =
|
||||||
React.useState(false);
|
React.useState(false);
|
||||||
const [showAddSharedConnectionDialog, setShowAddSharedConnectionDialog] =
|
const [showAddSharedConnectionDialog, setShowAddSharedConnectionDialog] =
|
||||||
React.useState(false);
|
React.useState(false);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { authenticate } = useAuthenticateApp({
|
const { authenticate } = useAuthenticateApp({
|
||||||
appKey: application.key,
|
appKey: application.key,
|
||||||
@@ -63,21 +66,24 @@ function ChooseConnectionSubstep(props) {
|
|||||||
|
|
||||||
const { data: appConfig } = useAppConfig(application.key);
|
const { data: appConfig } = useAppConfig(application.key);
|
||||||
|
|
||||||
|
const { data: stepConnectionData } = useStepConnection(step.id);
|
||||||
|
const stepConnection = stepConnectionData?.data;
|
||||||
|
|
||||||
// TODO: show detailed error when connection test/verification fails
|
// TODO: show detailed error when connection test/verification fails
|
||||||
const [
|
const [
|
||||||
testConnection,
|
testConnection,
|
||||||
{ loading: testResultLoading, refetch: retestConnection },
|
{ loading: testResultLoading, refetch: retestConnection },
|
||||||
] = useLazyQuery(TEST_CONNECTION, {
|
] = useLazyQuery(TEST_CONNECTION, {
|
||||||
variables: {
|
variables: {
|
||||||
id: connection?.id,
|
id: stepConnection?.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (connection?.id) {
|
if (stepConnection?.id) {
|
||||||
testConnection({
|
testConnection({
|
||||||
variables: {
|
variables: {
|
||||||
id: connection.id,
|
id: stepConnection.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -154,8 +160,9 @@ function ChooseConnectionSubstep(props) {
|
|||||||
},
|
},
|
||||||
[onChange, refetch, step],
|
[onChange, refetch, step],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = React.useCallback(
|
const handleChange = React.useCallback(
|
||||||
(event, selectedOption) => {
|
async (event, selectedOption) => {
|
||||||
if (typeof selectedOption === 'object') {
|
if (typeof selectedOption === 'object') {
|
||||||
// TODO: try to simplify type casting below.
|
// TODO: try to simplify type casting below.
|
||||||
const typedSelectedOption = selectedOption;
|
const typedSelectedOption = selectedOption;
|
||||||
@@ -172,7 +179,7 @@ function ChooseConnectionSubstep(props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionId !== step.connection?.id) {
|
if (connectionId !== stepConnection?.id) {
|
||||||
onChange({
|
onChange({
|
||||||
step: {
|
step: {
|
||||||
...step,
|
...step,
|
||||||
@@ -181,19 +188,23 @@ function ChooseConnectionSubstep(props) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ['stepConnection', step.id],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[step, onChange],
|
[step, onChange, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (step.connection?.id) {
|
if (stepConnection?.id) {
|
||||||
retestConnection({
|
retestConnection({
|
||||||
id: step.connection.id,
|
id: stepConnection?.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [step.connection?.id, retestConnection]);
|
}, [stepConnection?.id, retestConnection]);
|
||||||
|
|
||||||
const onToggle = expanded ? onCollapse : onExpand;
|
const onToggle = expanded ? onCollapse : onExpand;
|
||||||
|
|
||||||
@@ -203,7 +214,7 @@ function ChooseConnectionSubstep(props) {
|
|||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
title={name}
|
title={name}
|
||||||
valid={testResultLoading ? null : connection?.verified}
|
valid={testResultLoading ? null : stepConnection?.verified}
|
||||||
/>
|
/>
|
||||||
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
||||||
<ListItem
|
<ListItem
|
||||||
@@ -229,7 +240,7 @@ function ChooseConnectionSubstep(props) {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
value={getOption(connectionOptions, connection?.id)}
|
value={getOption(connectionOptions, stepConnection?.id)}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
data-test="choose-connection-autocomplete"
|
data-test="choose-connection-autocomplete"
|
||||||
@@ -242,7 +253,7 @@ function ChooseConnectionSubstep(props) {
|
|||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
disabled={
|
disabled={
|
||||||
testResultLoading ||
|
testResultLoading ||
|
||||||
!connection?.verified ||
|
!stepConnection?.verified ||
|
||||||
editorContext.readOnly
|
editorContext.readOnly
|
||||||
}
|
}
|
||||||
data-test="flow-substep-continue-button"
|
data-test="flow-substep-continue-button"
|
||||||
|
@@ -3,47 +3,24 @@ import { useMutation } from '@apollo/client';
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from '@mui/icons-material/Add';
|
||||||
import { GET_FLOW } from 'graphql/queries/get-flow';
|
|
||||||
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
||||||
import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
||||||
import FlowStep from 'components/FlowStep';
|
import FlowStep from 'components/FlowStep';
|
||||||
import { FlowPropType } from 'propTypes/propTypes';
|
import { FlowPropType } from 'propTypes/propTypes';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
function updateHandlerFactory(flowId, previousStepId) {
|
|
||||||
return function createStepUpdateHandler(cache, mutationResult) {
|
|
||||||
const { data } = mutationResult;
|
|
||||||
const { createStep: createdStep } = data;
|
|
||||||
const { getFlow: flow } = cache.readQuery({
|
|
||||||
query: GET_FLOW,
|
|
||||||
variables: { id: flowId },
|
|
||||||
});
|
|
||||||
const steps = flow.steps.reduce((steps, currentStep) => {
|
|
||||||
if (currentStep.id === previousStepId) {
|
|
||||||
return [...steps, currentStep, createdStep];
|
|
||||||
}
|
|
||||||
return [...steps, currentStep];
|
|
||||||
}, []);
|
|
||||||
cache.writeQuery({
|
|
||||||
query: GET_FLOW,
|
|
||||||
variables: { id: flowId },
|
|
||||||
data: { getFlow: { ...flow, steps } },
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function Editor(props) {
|
function Editor(props) {
|
||||||
const [updateStep] = useMutation(UPDATE_STEP);
|
const [updateStep] = useMutation(UPDATE_STEP);
|
||||||
const [createStep, { loading: creationInProgress }] = useMutation(
|
const [createStep, { loading: creationInProgress }] =
|
||||||
CREATE_STEP,
|
useMutation(CREATE_STEP);
|
||||||
{
|
|
||||||
refetchQueries: ['GetFlow'],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const { flow } = props;
|
const { flow } = props;
|
||||||
const [triggerStep] = flow.steps;
|
const [triggerStep] = flow.steps;
|
||||||
const [currentStepId, setCurrentStepId] = React.useState(triggerStep.id);
|
const [currentStepId, setCurrentStepId] = React.useState(triggerStep.id);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const onStepChange = React.useCallback(
|
const onStepChange = React.useCallback(
|
||||||
(step) => {
|
async (step) => {
|
||||||
const mutationInput = {
|
const mutationInput = {
|
||||||
id: step.id,
|
id: step.id,
|
||||||
key: step.key,
|
key: step.key,
|
||||||
@@ -55,13 +32,20 @@ function Editor(props) {
|
|||||||
id: flow.id,
|
id: flow.id,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (step.appKey) {
|
if (step.appKey) {
|
||||||
mutationInput.appKey = step.appKey;
|
mutationInput.appKey = step.appKey;
|
||||||
}
|
}
|
||||||
updateStep({ variables: { input: mutationInput } });
|
|
||||||
|
await updateStep({ variables: { input: mutationInput } });
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ['stepConnection', step.id],
|
||||||
|
});
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['flow', flow.id] });
|
||||||
},
|
},
|
||||||
[updateStep, flow.id],
|
[updateStep, flow.id, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
const addStep = React.useCallback(
|
const addStep = React.useCallback(
|
||||||
async (previousStepId) => {
|
async (previousStepId) => {
|
||||||
const mutationInput = {
|
const mutationInput = {
|
||||||
@@ -72,20 +56,24 @@ function Editor(props) {
|
|||||||
id: flow.id,
|
id: flow.id,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const createdStep = await createStep({
|
const createdStep = await createStep({
|
||||||
variables: { input: mutationInput },
|
variables: { input: mutationInput },
|
||||||
update: updateHandlerFactory(flow.id, previousStepId),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const createdStepId = createdStep.data.createStep.id;
|
const createdStepId = createdStep.data.createStep.id;
|
||||||
setCurrentStepId(createdStepId);
|
setCurrentStepId(createdStepId);
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['flow', flow.id] });
|
||||||
},
|
},
|
||||||
[createStep, flow.id],
|
[createStep, flow.id, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
const openNextStep = React.useCallback((nextStep) => {
|
const openNextStep = React.useCallback((nextStep) => {
|
||||||
return () => {
|
return () => {
|
||||||
setCurrentStepId(nextStep?.id);
|
setCurrentStepId(nextStep?.id);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
@@ -106,6 +94,7 @@ function Editor(props) {
|
|||||||
onOpen={() => setCurrentStepId(step.id)}
|
onOpen={() => setCurrentStepId(step.id)}
|
||||||
onClose={() => setCurrentStepId(null)}
|
onClose={() => setCurrentStepId(null)}
|
||||||
onChange={onStepChange}
|
onChange={onStepChange}
|
||||||
|
flowId={flow.id}
|
||||||
onContinue={openNextStep(steps[index + 1])}
|
onContinue={openNextStep(steps[index + 1])}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import { useMutation, useQuery } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
@@ -8,6 +8,7 @@ import Tooltip from '@mui/material/Tooltip';
|
|||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||||
import Snackbar from '@mui/material/Snackbar';
|
import Snackbar from '@mui/material/Snackbar';
|
||||||
|
|
||||||
import { EditorProvider } from 'contexts/Editor';
|
import { EditorProvider } from 'contexts/Editor';
|
||||||
import EditableTypography from 'components/EditableTypography';
|
import EditableTypography from 'components/EditableTypography';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
@@ -15,17 +16,20 @@ import Editor from 'components/Editor';
|
|||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import { UPDATE_FLOW_STATUS } from 'graphql/mutations/update-flow-status';
|
import { UPDATE_FLOW_STATUS } from 'graphql/mutations/update-flow-status';
|
||||||
import { UPDATE_FLOW } from 'graphql/mutations/update-flow';
|
import { UPDATE_FLOW } from 'graphql/mutations/update-flow';
|
||||||
import { GET_FLOW } from 'graphql/queries/get-flow';
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import { TopBar } from './style';
|
import { TopBar } from './style';
|
||||||
|
import useFlow from 'hooks/useFlow';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
export default function EditorLayout() {
|
export default function EditorLayout() {
|
||||||
const { flowId } = useParams();
|
const { flowId } = useParams();
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const [updateFlow] = useMutation(UPDATE_FLOW);
|
const [updateFlow] = useMutation(UPDATE_FLOW);
|
||||||
const [updateFlowStatus] = useMutation(UPDATE_FLOW_STATUS);
|
const [updateFlowStatus] = useMutation(UPDATE_FLOW_STATUS);
|
||||||
const { data, loading } = useQuery(GET_FLOW, { variables: { id: flowId } });
|
const { data, isLoading: isFlowLoading } = useFlow(flowId);
|
||||||
const flow = data?.getFlow;
|
const flow = data?.data;
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const onFlowNameUpdate = React.useCallback(
|
const onFlowNameUpdate = React.useCallback(
|
||||||
async (name) => {
|
async (name) => {
|
||||||
await updateFlow({
|
await updateFlow({
|
||||||
@@ -38,14 +42,17 @@ export default function EditorLayout() {
|
|||||||
optimisticResponse: {
|
optimisticResponse: {
|
||||||
updateFlow: {
|
updateFlow: {
|
||||||
__typename: 'Flow',
|
__typename: 'Flow',
|
||||||
id: flow?.id,
|
id: flowId,
|
||||||
name,
|
name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['flow', flowId] });
|
||||||
},
|
},
|
||||||
[flow?.id],
|
[flowId, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onFlowStatusUpdate = React.useCallback(
|
const onFlowStatusUpdate = React.useCallback(
|
||||||
async (active) => {
|
async (active) => {
|
||||||
await updateFlowStatus({
|
await updateFlowStatus({
|
||||||
@@ -58,14 +65,17 @@ export default function EditorLayout() {
|
|||||||
optimisticResponse: {
|
optimisticResponse: {
|
||||||
updateFlowStatus: {
|
updateFlowStatus: {
|
||||||
__typename: 'Flow',
|
__typename: 'Flow',
|
||||||
id: flow?.id,
|
id: flowId,
|
||||||
active,
|
active,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['flow', flowId] });
|
||||||
},
|
},
|
||||||
[flow?.id],
|
[flowId, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopBar
|
<TopBar
|
||||||
@@ -94,7 +104,7 @@ export default function EditorLayout() {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{!loading && (
|
{!isFlowLoading && (
|
||||||
<EditableTypography
|
<EditableTypography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
onConfirm={onFlowNameUpdate}
|
onConfirm={onFlowNameUpdate}
|
||||||
@@ -124,7 +134,7 @@ export default function EditorLayout() {
|
|||||||
<Stack direction="column" height="100%">
|
<Stack direction="column" height="100%">
|
||||||
<Container maxWidth="md">
|
<Container maxWidth="md">
|
||||||
<EditorProvider value={{ readOnly: !!flow?.active }}>
|
<EditorProvider value={{ readOnly: !!flow?.active }}>
|
||||||
{!flow && !loading && 'not found'}
|
{!flow && !isFlowLoading && 'not found'}
|
||||||
|
|
||||||
{flow && <Editor flow={flow} />}
|
{flow && <Editor flow={flow} />}
|
||||||
</EditorProvider>
|
</EditorProvider>
|
||||||
|
@@ -5,6 +5,7 @@ import MenuItem from '@mui/material/MenuItem';
|
|||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import Can from 'components/Can';
|
import Can from 'components/Can';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import { DELETE_FLOW } from 'graphql/mutations/delete-flow';
|
import { DELETE_FLOW } from 'graphql/mutations/delete-flow';
|
||||||
@@ -12,25 +13,28 @@ import { DUPLICATE_FLOW } from 'graphql/mutations/duplicate-flow';
|
|||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
function ContextMenu(props) {
|
function ContextMenu(props) {
|
||||||
const { flowId, onClose, anchorEl } = props;
|
const { flowId, onClose, anchorEl, onDuplicateFlow, onDeleteFlow } = props;
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
const enqueueSnackbar = useEnqueueSnackbar();
|
||||||
const [deleteFlow] = useMutation(DELETE_FLOW);
|
const [deleteFlow] = useMutation(DELETE_FLOW);
|
||||||
const [duplicateFlow] = useMutation(DUPLICATE_FLOW, {
|
const [duplicateFlow] = useMutation(DUPLICATE_FLOW);
|
||||||
refetchQueries: ['GetFlows'],
|
|
||||||
});
|
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
const onFlowDuplicate = React.useCallback(async () => {
|
const onFlowDuplicate = React.useCallback(async () => {
|
||||||
await duplicateFlow({
|
await duplicateFlow({
|
||||||
variables: { input: { id: flowId } },
|
variables: { input: { id: flowId } },
|
||||||
});
|
});
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('flow.successfullyDuplicated'), {
|
enqueueSnackbar(formatMessage('flow.successfullyDuplicated'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
SnackbarProps: {
|
||||||
'data-test': 'snackbar-duplicate-flow-success',
|
'data-test': 'snackbar-duplicate-flow-success',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onDuplicateFlow?.();
|
||||||
onClose();
|
onClose();
|
||||||
}, [flowId, onClose, duplicateFlow]);
|
}, [flowId, onClose, duplicateFlow, onDuplicateFlow]);
|
||||||
|
|
||||||
const onFlowDelete = React.useCallback(async () => {
|
const onFlowDelete = React.useCallback(async () => {
|
||||||
await deleteFlow({
|
await deleteFlow({
|
||||||
variables: { input: { id: flowId } },
|
variables: { input: { id: flowId } },
|
||||||
@@ -44,11 +48,15 @@ function ContextMenu(props) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('flow.successfullyDeleted'), {
|
enqueueSnackbar(formatMessage('flow.successfullyDeleted'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onDeleteFlow?.();
|
||||||
onClose();
|
onClose();
|
||||||
}, [flowId, onClose, deleteFlow]);
|
}, [flowId, onClose, deleteFlow, onDeleteFlow]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
open={true}
|
open={true}
|
||||||
@@ -90,6 +98,8 @@ ContextMenu.propTypes = {
|
|||||||
PropTypes.func,
|
PropTypes.func,
|
||||||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
||||||
]).isRequired,
|
]).isRequired,
|
||||||
|
onDeleteFlow: PropTypes.func,
|
||||||
|
onDuplicateFlow: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ContextMenu;
|
export default ContextMenu;
|
||||||
|
@@ -6,6 +6,8 @@ import CardActionArea from '@mui/material/CardActionArea';
|
|||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import FlowAppIcons from 'components/FlowAppIcons';
|
import FlowAppIcons from 'components/FlowAppIcons';
|
||||||
import FlowContextMenu from 'components/FlowContextMenu';
|
import FlowContextMenu from 'components/FlowContextMenu';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
@@ -35,7 +37,7 @@ function FlowRow(props) {
|
|||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const contextButtonRef = React.useRef(null);
|
const contextButtonRef = React.useRef(null);
|
||||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||||
const { flow } = props;
|
const { flow, onDuplicateFlow, onDeleteFlow } = props;
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setAnchorEl(null);
|
setAnchorEl(null);
|
||||||
};
|
};
|
||||||
@@ -112,6 +114,8 @@ function FlowRow(props) {
|
|||||||
flowId={flow.id}
|
flowId={flow.id}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
|
onDeleteFlow={onDeleteFlow}
|
||||||
|
onDuplicateFlow={onDuplicateFlow}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -120,6 +124,8 @@ function FlowRow(props) {
|
|||||||
|
|
||||||
FlowRow.propTypes = {
|
FlowRow.propTypes = {
|
||||||
flow: FlowPropType.isRequired,
|
flow: FlowPropType.isRequired,
|
||||||
|
onDeleteFlow: PropTypes.func,
|
||||||
|
onDuplicateFlow: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FlowRow;
|
export default FlowRow;
|
||||||
|
@@ -105,7 +105,7 @@ function generateValidationSchema(substeps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FlowStep(props) {
|
function FlowStep(props) {
|
||||||
const { collapsed, onChange, onContinue } = props;
|
const { collapsed, onChange, onContinue, flowId } = props;
|
||||||
const editorContext = React.useContext(EditorContext);
|
const editorContext = React.useContext(EditorContext);
|
||||||
const contextButtonRef = React.useRef(null);
|
const contextButtonRef = React.useRef(null);
|
||||||
const step = props.step;
|
const step = props.step;
|
||||||
@@ -328,6 +328,7 @@ function FlowStep(props) {
|
|||||||
: false
|
: false
|
||||||
}
|
}
|
||||||
step={step}
|
step={step}
|
||||||
|
flowId={flowId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -363,6 +364,7 @@ function FlowStep(props) {
|
|||||||
deletable={!isTrigger}
|
deletable={!isTrigger}
|
||||||
onClose={onContextMenuClose}
|
onClose={onContextMenuClose}
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
|
flowId={flowId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
|
@@ -1,24 +1,31 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
|
|
||||||
import Menu from '@mui/material/Menu';
|
import Menu from '@mui/material/Menu';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import { DELETE_STEP } from 'graphql/mutations/delete-step';
|
import { DELETE_STEP } from 'graphql/mutations/delete-step';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
function FlowStepContextMenu(props) {
|
function FlowStepContextMenu(props) {
|
||||||
const { stepId, onClose, anchorEl, deletable } = props;
|
const { stepId, onClose, anchorEl, deletable, flowId } = props;
|
||||||
const [deleteStep] = useMutation(DELETE_STEP, {
|
|
||||||
refetchQueries: ['GetFlow', 'GetStepWithTestExecutions'],
|
|
||||||
});
|
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const [deleteStep] = useMutation(DELETE_STEP, {
|
||||||
|
refetchQueries: ['GetStepWithTestExecutions'],
|
||||||
|
});
|
||||||
|
|
||||||
const deleteActionHandler = React.useCallback(
|
const deleteActionHandler = React.useCallback(
|
||||||
async (event) => {
|
async (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
await deleteStep({ variables: { input: { id: stepId } } });
|
await deleteStep({ variables: { input: { id: stepId } } });
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['flow', flowId] });
|
||||||
},
|
},
|
||||||
[stepId],
|
[stepId, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
open={true}
|
open={true}
|
||||||
|
@@ -6,12 +6,15 @@ import ListItem from '@mui/material/ListItem';
|
|||||||
import Alert from '@mui/material/Alert';
|
import Alert from '@mui/material/Alert';
|
||||||
import AlertTitle from '@mui/material/AlertTitle';
|
import AlertTitle from '@mui/material/AlertTitle';
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
|
||||||
import { EditorContext } from 'contexts/Editor';
|
import { EditorContext } from 'contexts/Editor';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow';
|
import { EXECUTE_FLOW } from 'graphql/mutations/execute-flow';
|
||||||
import JSONViewer from 'components/JSONViewer';
|
import JSONViewer from 'components/JSONViewer';
|
||||||
import WebhookUrlInfo from 'components/WebhookUrlInfo';
|
import WebhookUrlInfo from 'components/WebhookUrlInfo';
|
||||||
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
import FlowSubstepTitle from 'components/FlowSubstepTitle';
|
||||||
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
function serializeErrors(graphQLErrors) {
|
function serializeErrors(graphQLErrors) {
|
||||||
return graphQLErrors?.map((error) => {
|
return graphQLErrors?.map((error) => {
|
||||||
try {
|
try {
|
||||||
@@ -28,6 +31,7 @@ function serializeErrors(graphQLErrors) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function TestSubstep(props) {
|
function TestSubstep(props) {
|
||||||
const {
|
const {
|
||||||
substep,
|
substep,
|
||||||
@@ -38,6 +42,7 @@ function TestSubstep(props) {
|
|||||||
onContinue,
|
onContinue,
|
||||||
step,
|
step,
|
||||||
showWebhookUrl = false,
|
showWebhookUrl = false,
|
||||||
|
flowId,
|
||||||
} = props;
|
} = props;
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const editorContext = React.useContext(EditorContext);
|
const editorContext = React.useContext(EditorContext);
|
||||||
@@ -52,6 +57,8 @@ function TestSubstep(props) {
|
|||||||
const isCompleted = !error && called && !loading;
|
const isCompleted = !error && called && !loading;
|
||||||
const hasNoOutput = !response && isCompleted;
|
const hasNoOutput = !response && isCompleted;
|
||||||
const { name } = substep;
|
const { name } = substep;
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
function resetTestDataOnSubstepToggle() {
|
function resetTestDataOnSubstepToggle() {
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
@@ -60,20 +67,28 @@ function TestSubstep(props) {
|
|||||||
},
|
},
|
||||||
[expanded, reset],
|
[expanded, reset],
|
||||||
);
|
);
|
||||||
const handleSubmit = React.useCallback(() => {
|
|
||||||
|
const handleSubmit = React.useCallback(async () => {
|
||||||
if (isCompleted) {
|
if (isCompleted) {
|
||||||
onContinue?.();
|
onContinue?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
executeFlow({
|
|
||||||
|
await executeFlow({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
stepId: step.id,
|
stepId: step.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [onSubmit, onContinue, isCompleted, step.id]);
|
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ['flow', flowId],
|
||||||
|
});
|
||||||
|
}, [onSubmit, onContinue, isCompleted, step.id, queryClient, flowId]);
|
||||||
|
|
||||||
const onToggle = expanded ? onCollapse : onExpand;
|
const onToggle = expanded ? onCollapse : onExpand;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<FlowSubstepTitle expanded={expanded} onClick={onToggle} title={name} />
|
<FlowSubstepTitle expanded={expanded} onClick={onToggle} title={name} />
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
export const GET_FLOW = gql`
|
|
||||||
query GetFlow($id: String!) {
|
|
||||||
getFlow(id: $id) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
active
|
|
||||||
status
|
|
||||||
steps {
|
|
||||||
id
|
|
||||||
type
|
|
||||||
key
|
|
||||||
appKey
|
|
||||||
iconUrl
|
|
||||||
webhookUrl
|
|
||||||
status
|
|
||||||
position
|
|
||||||
connection {
|
|
||||||
id
|
|
||||||
verified
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
parameters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
19
packages/web/src/hooks/useFlow.js
Normal file
19
packages/web/src/hooks/useFlow.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
|
export default function useFlow(flowId) {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ['flow', flowId],
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const { data } = await api.get(`/v1/flows/${flowId}`, {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: !!flowId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
19
packages/web/src/hooks/useStepConnection.js
Normal file
19
packages/web/src/hooks/useStepConnection.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import api from 'helpers/api';
|
||||||
|
|
||||||
|
export default function useStepConnection(stepId) {
|
||||||
|
const query = useQuery({
|
||||||
|
queryKey: ['stepConnection', stepId],
|
||||||
|
queryFn: async ({ signal }) => {
|
||||||
|
const { data } = await api.get(`/v1/steps/${stepId}/connection`, {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
enabled: !!stepId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
@@ -27,7 +27,7 @@ export default function Flows() {
|
|||||||
const [flowName, setFlowName] = React.useState('');
|
const [flowName, setFlowName] = React.useState('');
|
||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
|
||||||
const { data, mutate } = useLazyFlows(
|
const { data, mutate: fetchFlows } = useLazyFlows(
|
||||||
{ flowName, page },
|
{ flowName, page },
|
||||||
{
|
{
|
||||||
onSettled: () => {
|
onSettled: () => {
|
||||||
@@ -36,7 +36,10 @@ export default function Flows() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchData = React.useMemo(() => debounce(mutate, 300), [mutate]);
|
const fetchData = React.useMemo(
|
||||||
|
() => debounce(fetchFlows, 300),
|
||||||
|
[fetchFlows],
|
||||||
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@@ -110,7 +113,14 @@ export default function Flows() {
|
|||||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||||
)}
|
)}
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
flows?.map((flow) => <FlowRow key={flow.id} flow={flow} />)}
|
flows?.map((flow) => (
|
||||||
|
<FlowRow
|
||||||
|
key={flow.id}
|
||||||
|
flow={flow}
|
||||||
|
onDuplicateFlow={fetchFlows}
|
||||||
|
onDeleteFlow={fetchFlows}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
{!isLoading && !hasFlows && (
|
{!isLoading && !hasFlows && (
|
||||||
<NoResultFound
|
<NoResultFound
|
||||||
text={formatMessage('flows.noFlows')}
|
text={formatMessage('flows.noFlows')}
|
||||||
|
Reference in New Issue
Block a user