feat: make step in editor configurable
This commit is contained in:

committed by
Ömer Faruk Aydın

parent
4985fb422e
commit
95a63affe7
@@ -1,52 +1,54 @@
|
||||
import { GraphQLString, GraphQLNonNull, GraphQLInt, GraphQLEnumType } from 'graphql';
|
||||
import { GraphQLNonNull } from 'graphql';
|
||||
import Step from '../../models/step';
|
||||
import Flow from '../../models/flow';
|
||||
import stepType from '../types/step';
|
||||
import stepType, { stepInputType } from '../types/step';
|
||||
import RequestWithCurrentUser from '../../types/express/request-with-current-user';
|
||||
|
||||
type Params = {
|
||||
flowId: number,
|
||||
key: string,
|
||||
appKey: string,
|
||||
type: string
|
||||
connectionId: number
|
||||
}
|
||||
input: {
|
||||
key: string;
|
||||
appKey: string;
|
||||
flow: {
|
||||
id: number;
|
||||
};
|
||||
connection: {
|
||||
id: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const createStepResolver = async (params: Params, req: RequestWithCurrentUser) => {
|
||||
const flow = await Flow.query().findOne({
|
||||
id: params.flowId,
|
||||
user_id: req.currentUser.id
|
||||
}).throwIfNotFound();
|
||||
const createStepResolver = async (
|
||||
params: Params,
|
||||
req: RequestWithCurrentUser
|
||||
) => {
|
||||
const { input } = params;
|
||||
|
||||
const flow = await Flow.query()
|
||||
.findOne({
|
||||
id: input.flow.id,
|
||||
user_id: req.currentUser.id,
|
||||
})
|
||||
.throwIfNotFound();
|
||||
|
||||
const step = await Step.query().insertAndFetch({
|
||||
flowId: flow.id,
|
||||
key: params.key,
|
||||
appKey: params.appKey,
|
||||
type: params.type,
|
||||
connectionId: params.connectionId,
|
||||
key: input.key,
|
||||
appKey: input.appKey,
|
||||
type: 'action',
|
||||
connectionId: input.connection?.id,
|
||||
position: 1,
|
||||
});
|
||||
|
||||
return step;
|
||||
}
|
||||
};
|
||||
|
||||
const createStep = {
|
||||
type: stepType,
|
||||
args: {
|
||||
flowId: { type: GraphQLNonNull(GraphQLInt) },
|
||||
key: { type: GraphQLNonNull(GraphQLString) },
|
||||
appKey: { type: GraphQLNonNull(GraphQLString) },
|
||||
type: {
|
||||
type: new GraphQLEnumType({
|
||||
name: 'StepInputEnumType',
|
||||
values: {
|
||||
trigger: { value: 'trigger' },
|
||||
action: { value: 'action' },
|
||||
}
|
||||
})
|
||||
},
|
||||
connectionId: { type: GraphQLNonNull(GraphQLInt) }
|
||||
input: { type: new GraphQLNonNull(stepInputType) },
|
||||
},
|
||||
resolve: (_: any, params: Params, req: RequestWithCurrentUser) => createStepResolver(params, req)
|
||||
resolve: (_: any, params: Params, req: RequestWithCurrentUser) =>
|
||||
createStepResolver(params, req),
|
||||
};
|
||||
|
||||
export default createStep;
|
||||
|
@@ -1,34 +1,41 @@
|
||||
import { GraphQLInt, GraphQLString, GraphQLNonNull } from 'graphql';
|
||||
import Flow from '../../models/flow';
|
||||
import Step from '../../models/step';
|
||||
import stepType from '../types/step';
|
||||
import stepType, { stepInputType } from '../types/step';
|
||||
import availableAppsEnumType from '../types/available-apps-enum-type';
|
||||
import RequestWithCurrentUser from '../../types/express/request-with-current-user';
|
||||
|
||||
type Params = {
|
||||
id: number,
|
||||
flowId: number,
|
||||
key: string,
|
||||
appKey: string,
|
||||
connectionId: number
|
||||
input: {
|
||||
id: number,
|
||||
key: string,
|
||||
appKey: string,
|
||||
flow: {
|
||||
id: number,
|
||||
},
|
||||
connection: {
|
||||
id: number
|
||||
},
|
||||
}
|
||||
}
|
||||
const updateStepResolver = async (params: Params, req: RequestWithCurrentUser) => {
|
||||
const { input } = params;
|
||||
|
||||
const flow = await Flow.query().findOne({
|
||||
user_id: req.currentUser.id,
|
||||
id: params.flowId
|
||||
id: input.flow.id
|
||||
}).throwIfNotFound();
|
||||
|
||||
let step = await Step.query().findOne({
|
||||
flow_id: flow.id,
|
||||
id: params.id
|
||||
id: input.id
|
||||
}).throwIfNotFound();
|
||||
|
||||
step = await step.$query().patchAndFetch({
|
||||
...step,
|
||||
key: params.key,
|
||||
appKey: params.appKey,
|
||||
connectionId: params.connectionId
|
||||
})
|
||||
step = await Step.query().patchAndFetchById(input.id, {
|
||||
key: input.key,
|
||||
appKey: input.appKey,
|
||||
connectionId: input.connection.id,
|
||||
});
|
||||
|
||||
return step;
|
||||
}
|
||||
@@ -36,10 +43,7 @@ const updateStepResolver = async (params: Params, req: RequestWithCurrentUser) =
|
||||
const updateStep = {
|
||||
type: stepType,
|
||||
args: {
|
||||
id: { type: GraphQLNonNull(GraphQLInt) },
|
||||
flowId: { type: GraphQLNonNull(GraphQLInt) },
|
||||
key: { type: GraphQLString },
|
||||
appKey: { type: availableAppsEnumType },
|
||||
input: { type: new GraphQLNonNull(stepInputType) }
|
||||
},
|
||||
resolve: (_: any, params: Params, req: RequestWithCurrentUser) => updateStepResolver(params, req)
|
||||
};
|
||||
|
@@ -3,6 +3,7 @@ import {
|
||||
GraphQLString,
|
||||
GraphQLEnumType,
|
||||
GraphQLInt,
|
||||
GraphQLInputObjectType,
|
||||
} from 'graphql';
|
||||
import ConnectionType from './connection';
|
||||
|
||||
@@ -10,6 +11,7 @@ const stepType = new GraphQLObjectType({
|
||||
name: 'Step',
|
||||
fields: {
|
||||
id: { type: GraphQLInt },
|
||||
previousStepId: { type: GraphQLInt },
|
||||
key: { type: GraphQLString },
|
||||
appKey: { type: GraphQLString },
|
||||
type: {
|
||||
@@ -26,4 +28,30 @@ const stepType = new GraphQLObjectType({
|
||||
},
|
||||
});
|
||||
|
||||
export const stepInputType = new GraphQLInputObjectType({
|
||||
name: 'StepInput',
|
||||
fields: {
|
||||
id: { type: GraphQLInt },
|
||||
previousStepId: { type: GraphQLInt },
|
||||
key: { type: GraphQLString },
|
||||
appKey: { type: GraphQLString },
|
||||
connection: {
|
||||
type: new GraphQLInputObjectType({
|
||||
name: 'StepConnectionInput',
|
||||
fields: {
|
||||
id: { type: GraphQLInt },
|
||||
}
|
||||
})
|
||||
},
|
||||
flow: {
|
||||
type: new GraphQLInputObjectType({
|
||||
name: 'StepFlowInput',
|
||||
fields: {
|
||||
id: { type: GraphQLInt },
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default stepType;
|
||||
|
@@ -26,7 +26,7 @@ class Step extends Base {
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
flowId: { type: 'integer' },
|
||||
key: { type: 'string', minLength: 1, maxLength: 255 },
|
||||
key: { type: ['string', null] },
|
||||
appKey: { type: 'string', minLength: 1, maxLength: 255 },
|
||||
type: { type: 'string', enum: ['action', 'trigger'] },
|
||||
connectionId: { type: 'integer' },
|
||||
|
@@ -28,7 +28,7 @@
|
||||
"react-hook-form": "^7.17.2",
|
||||
"react-intl": "^5.20.12",
|
||||
"react-router-dom": "^6.0.2",
|
||||
"react-scripts": "4.0.3",
|
||||
"react-scripts": "5.0.0",
|
||||
"typescript": "^4.1.2",
|
||||
"web-vitals": "^1.0.1"
|
||||
},
|
||||
|
@@ -3,8 +3,8 @@ import Avatar from '@mui/material/Avatar';
|
||||
import type { AvatarProps } from '@mui/material/Avatar';
|
||||
|
||||
type AppIconProps = {
|
||||
name: string;
|
||||
url: string;
|
||||
name?: string;
|
||||
url?: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
|
@@ -1,17 +1,85 @@
|
||||
import * as React from 'react';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { ApolloCache, FetchResult } from '@apollo/client';
|
||||
import Box from '@mui/material/Box';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
import { GET_FLOW } from 'graphql/queries/get-flow';
|
||||
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
||||
import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
||||
import FlowStep from 'components/FlowStep';
|
||||
import type { Flow } from 'types/flow';
|
||||
import type { Step } from 'types/step';
|
||||
|
||||
type EditorProps = {
|
||||
flow: Flow;
|
||||
}
|
||||
|
||||
function updateHandlerFactory(flowId: number, previousStepId: number) {
|
||||
return function createStepUpdateHandler(
|
||||
cache: any,
|
||||
mutationResult: any,
|
||||
) {
|
||||
const { data } = mutationResult;
|
||||
const { createStep: createdStep } = data;
|
||||
const { getFlow: flow } = cache.readQuery({
|
||||
query: GET_FLOW,
|
||||
variables: { id: flowId },
|
||||
});
|
||||
const steps = flow.steps.reduce((steps: any[], currentStep: any) => {
|
||||
if (currentStep.id === previousStepId) {
|
||||
return [...steps, currentStep, createdStep];
|
||||
}
|
||||
|
||||
return [...steps, currentStep];
|
||||
}, []);
|
||||
|
||||
cache.writeQuery({
|
||||
query: GET_FLOW,
|
||||
variables: { id: flowId },
|
||||
data: { getFlow: { ...flow, steps } },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default function Editor(props: EditorProps): React.ReactElement {
|
||||
const [currentStep, setCurrentStep] = React.useState<number | null>(null);
|
||||
const [updateStep] = useMutation(UPDATE_STEP);
|
||||
const [createStep] = useMutation(CREATE_STEP);
|
||||
const [currentStep, setCurrentStep] = React.useState<number | null>(0);
|
||||
const { flow } = props;
|
||||
|
||||
const onStepChange = React.useCallback((step: any) => {
|
||||
const mutationInput = {
|
||||
id: step.id,
|
||||
key: step.key,
|
||||
appKey: step.appKey,
|
||||
previousStepId: step.previousStepId,
|
||||
connection: {
|
||||
id: step.connection?.id
|
||||
},
|
||||
flow: {
|
||||
id: flow.id,
|
||||
},
|
||||
};
|
||||
|
||||
updateStep({ variables: { input: mutationInput } });
|
||||
}, [updateStep, flow.id]);
|
||||
|
||||
const addStep = React.useCallback((previousStepId) => {
|
||||
const mutationInput = {
|
||||
previousStepId: previousStepId,
|
||||
flow: {
|
||||
id: flow.id,
|
||||
},
|
||||
};
|
||||
|
||||
createStep({
|
||||
variables: { input: mutationInput },
|
||||
update: updateHandlerFactory(flow.id, previousStepId),
|
||||
});
|
||||
}, [createStep, flow.id]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
display="flex"
|
||||
@@ -23,15 +91,22 @@ export default function Editor(props: EditorProps): React.ReactElement {
|
||||
gap={2}
|
||||
>
|
||||
{flow?.steps?.map((step, index) => (
|
||||
<FlowStep
|
||||
key={step.id}
|
||||
step={step}
|
||||
index={index + 1}
|
||||
collapsed={currentStep !== index}
|
||||
onOpen={() => setCurrentStep(index)}
|
||||
onClose={() => setCurrentStep(null)}
|
||||
/>
|
||||
<React.Fragment key={`${step}-${index}`}>
|
||||
<FlowStep
|
||||
key={step.id}
|
||||
step={step}
|
||||
index={index + 1}
|
||||
collapsed={currentStep !== index}
|
||||
onOpen={() => setCurrentStep(index)}
|
||||
onClose={() => setCurrentStep(null)}
|
||||
onChange={onStepChange}
|
||||
/>
|
||||
|
||||
<IconButton onClick={() => addStep(step.id)} color="primary">
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
};
|
||||
};
|
||||
|
@@ -38,9 +38,9 @@ export default function EditorLayout(): React.ReactElement {
|
||||
<Box pr={1}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch checked={false} />
|
||||
<Switch checked={flow?.active} />
|
||||
}
|
||||
label={formatMessage('flow.inactive')}
|
||||
label={flow?.active ? formatMessage('flow.active') : formatMessage('flow.inactive')}
|
||||
labelPlacement="start"
|
||||
/>
|
||||
</Box>
|
||||
|
@@ -17,7 +17,7 @@ export default function FlowRow(props: FlowRowProps): React.ReactElement {
|
||||
const { flow } = props;
|
||||
|
||||
return (
|
||||
<Link to={URLS.FLOW(flow.id)}>
|
||||
<Link to={URLS.FLOW(flow.id.toString())}>
|
||||
<Card sx={{ mb: 1 }}>
|
||||
<CardActionArea>
|
||||
<CardContent>
|
||||
|
@@ -3,13 +3,20 @@ import { useQuery } from '@apollo/client';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Button from '@mui/material/Button';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
|
||||
import AppIcon from 'components/AppIcon';
|
||||
import { GET_APP } from 'graphql/queries/get-app';
|
||||
import { GET_APPS } from 'graphql/queries/get-apps';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import type { App } from 'types/app';
|
||||
import type { Step } from 'types/step';
|
||||
import { StepType } from 'types/step';
|
||||
import { Header, Wrapper } from './style';
|
||||
import { Content, Header, Wrapper } from './style';
|
||||
|
||||
type FlowStepProps = {
|
||||
collapsed?: boolean;
|
||||
@@ -17,41 +24,99 @@ type FlowStepProps = {
|
||||
index?: number;
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
onChange?: (step: Step) => void;
|
||||
}
|
||||
|
||||
export default function FlowStep(props: FlowStepProps): React.ReactElement | null {
|
||||
const { collapsed, index, step } = props;
|
||||
const formatMessage = useFormatMessage();
|
||||
const { data } = useQuery(GET_APP, { variables: { key: step?.appKey }})
|
||||
const app = data?.getApp;
|
||||
const optionGenerator = (app: App): { label: string; value: string; } => ({
|
||||
label: app.name,
|
||||
value: app.key,
|
||||
});
|
||||
|
||||
if (!app) return null;
|
||||
const getOption = (options: Record<string, unknown>[], appKey: string) => options.find(app => app.value === appKey);
|
||||
|
||||
export default function FlowStep(props: FlowStepProps): React.ReactElement | null {
|
||||
const { collapsed, index, onChange } = props;
|
||||
const [step, setStep] = React.useState<Step>(props.step);
|
||||
const formatMessage = useFormatMessage();
|
||||
const [currentSubstep, setCurrentSubstep] = React.useState<number | null>(0);
|
||||
const { data } = useQuery(GET_APPS)
|
||||
const apps: App[] = data?.getApps;
|
||||
|
||||
// emit the step change to the parent component
|
||||
React.useEffect(() => {
|
||||
onChange?.(step);
|
||||
}, [step, onChange]);
|
||||
|
||||
const appAndEventOptions = React.useMemo(() => apps?.map(optionGenerator), [apps]);
|
||||
|
||||
if (!apps) return null;
|
||||
|
||||
const app = apps.find((currentApp: App) => currentApp.key === step.appKey);
|
||||
|
||||
const onOpen = () => collapsed && props.onOpen?.();
|
||||
const onClose = () => props.onClose?.();
|
||||
|
||||
const toggleSubstep = (substepIndex: number) => setCurrentSubstep((value) => value !== substepIndex ? substepIndex : null);
|
||||
|
||||
const onAppAndEventChange = (event: React.SyntheticEvent, selectedOption: unknown) => {
|
||||
if (typeof selectedOption === 'object') {
|
||||
const typedSelectedOption = selectedOption as { value: string; };
|
||||
const option: { value: string } = typedSelectedOption;
|
||||
const appKey = option.value as string;
|
||||
const updatedStep = { ...step, appKey };
|
||||
setStep(updatedStep);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper elevation={collapsed ? 1 : 4} onClick={onOpen}>
|
||||
<Header borderBottom={!collapsed}>
|
||||
<Header collapsed={collapsed}>
|
||||
<Stack direction="row" alignItems="center" gap={2}>
|
||||
<AppIcon url={app.iconUrl} name={app.name} />
|
||||
<AppIcon url={app?.iconUrl} name={app?.name} />
|
||||
|
||||
<div>
|
||||
<Typography variant="caption">
|
||||
{step.type === StepType.Trigger ? formatMessage('flowStep.triggerType') : formatMessage('flowStep.actionType')}
|
||||
{
|
||||
step.type === StepType.Trigger ?
|
||||
formatMessage('flowStep.triggerType') :
|
||||
formatMessage('flowStep.actionType')
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2">
|
||||
{index}. {app.name}
|
||||
{index}. {app?.name}
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
</Header>
|
||||
|
||||
{!collapsed && (
|
||||
<Button onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
{true && (
|
||||
<Collapse in={!collapsed}>
|
||||
<Content>
|
||||
<List>
|
||||
<ListItemButton onClick={() => toggleSubstep(0)}>
|
||||
Choose app & event
|
||||
</ListItemButton>
|
||||
<Collapse in={currentSubstep === 0} timeout="auto" unmountOnExit>
|
||||
<ListItem sx={{ pt: 2 }}>
|
||||
<Autocomplete
|
||||
disablePortal
|
||||
id="combo-box-demo"
|
||||
options={appAndEventOptions}
|
||||
sx={{ width: 300 }}
|
||||
renderInput={(params) => <TextField {...params} label="Choose app & event" />}
|
||||
value={getOption(appAndEventOptions, step.appKey)}
|
||||
onChange={onAppAndEventChange}
|
||||
/>
|
||||
</ListItem>
|
||||
</Collapse>
|
||||
</List>
|
||||
</Content>
|
||||
|
||||
<Button onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
</Collapse>
|
||||
)}
|
||||
</Wrapper>
|
||||
)
|
||||
|
@@ -3,14 +3,21 @@ import Card from '@mui/material/Card';
|
||||
|
||||
export const Wrapper = styled(Card)`
|
||||
width: 100%;
|
||||
overflow: unset;
|
||||
`;
|
||||
|
||||
type HeaderProps = {
|
||||
borderBottom?: boolean;
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
export const Header = styled('div', { shouldForwardProp: prop => prop !== 'borderBottom' })<HeaderProps>`
|
||||
border-bottom: 1px solid ${({ theme, borderBottom }) => borderBottom ? alpha(theme.palette.divider, .8) : 'transparent'};
|
||||
padding: ${({ theme }) => theme.spacing(2, 2)};
|
||||
cursor: ${({ borderBottom }) => borderBottom ? 'unset' : 'pointer'};
|
||||
`;
|
||||
export const Header = styled('div', { shouldForwardProp: prop => prop !== 'collapsed' })<HeaderProps>`
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
cursor: ${({ collapsed }) => collapsed ? 'pointer' : 'unset'};
|
||||
`;
|
||||
|
||||
export const Content = styled('div')`
|
||||
border: 1px solid ${({ theme }) => alpha(theme.palette.divider, .8)};
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
padding: ${({ theme }) => theme.spacing(2, 0)};
|
||||
`;
|
||||
|
15
packages/web/src/graphql/mutations/create-step.ts
Normal file
15
packages/web/src/graphql/mutations/create-step.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_STEP = gql`
|
||||
mutation CreateStep($input: StepInput!) {
|
||||
createStep(input: $input) {
|
||||
id
|
||||
type
|
||||
key
|
||||
appKey
|
||||
connection {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
15
packages/web/src/graphql/mutations/update-step.ts
Normal file
15
packages/web/src/graphql/mutations/update-step.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_STEP = gql`
|
||||
mutation UpdateStep($input: StepInput!) {
|
||||
updateStep(input: $input) {
|
||||
id
|
||||
type
|
||||
key
|
||||
appKey
|
||||
connection {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
@@ -55,6 +55,17 @@ export const GET_APP = gql`
|
||||
name
|
||||
key
|
||||
description
|
||||
subSteps {
|
||||
name
|
||||
}
|
||||
}
|
||||
actions {
|
||||
name
|
||||
key
|
||||
description
|
||||
subSteps {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ export const GET_FLOW = gql`
|
||||
getFlow(id: $id) {
|
||||
id
|
||||
name
|
||||
active
|
||||
steps {
|
||||
id
|
||||
type
|
||||
|
@@ -30,8 +30,8 @@
|
||||
"connection.deletedMessage": "The connection has been deleted.",
|
||||
"connection.addedAt": "added {datetime}",
|
||||
"createFlow.creating": "Creating a flow...",
|
||||
"flow.active": "Enabled",
|
||||
"flow.inactive": "Disabled",
|
||||
"flow.active": "ON",
|
||||
"flow.inactive": "OFF",
|
||||
"flowStep.triggerType": "Trigger",
|
||||
"flowStep.actionType": "Action",
|
||||
"flows.create": "Create flow",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { useParams, } from 'react-router-dom';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Box from '@mui/material/Box';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
|
@@ -20,6 +20,8 @@ type App = {
|
||||
fields: AppFields[];
|
||||
authenticationSteps: any[];
|
||||
reconnectionSteps: any[];
|
||||
triggers: any[];
|
||||
actions: any[];
|
||||
};
|
||||
|
||||
export type { App, AppFields };
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import type { Step } from './step';
|
||||
|
||||
export type Flow = {
|
||||
id: string;
|
||||
id: number;
|
||||
name: string;
|
||||
steps: Step[];
|
||||
active: boolean;
|
||||
};
|
||||
|
@@ -9,5 +9,8 @@ export type Step = {
|
||||
name: string;
|
||||
appKey: string;
|
||||
type: StepType;
|
||||
connectionId: number;
|
||||
previousStepId: number | null;
|
||||
connection: {
|
||||
id: number;
|
||||
};
|
||||
};
|
||||
|
Reference in New Issue
Block a user