feat: add support to rename flow step
This commit is contained in:
@@ -91,15 +91,15 @@ function ChooseAppAndEventSubstep(props) {
|
|||||||
const onEventChange = React.useCallback(
|
const onEventChange = React.useCallback(
|
||||||
(event, selectedOption) => {
|
(event, selectedOption) => {
|
||||||
if (typeof selectedOption === 'object') {
|
if (typeof selectedOption === 'object') {
|
||||||
// TODO: try to simplify type casting below.
|
const eventKey = selectedOption?.value;
|
||||||
const typedSelectedOption = selectedOption;
|
const eventLabel = selectedOption?.label;
|
||||||
const option = typedSelectedOption;
|
|
||||||
const eventKey = option?.value;
|
|
||||||
if (step.key !== eventKey) {
|
if (step.key !== eventKey) {
|
||||||
onChange({
|
onChange({
|
||||||
step: {
|
step: {
|
||||||
...step,
|
...step,
|
||||||
key: eventKey,
|
key: eventKey,
|
||||||
|
keyLabel: eventLabel,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -111,10 +111,8 @@ function ChooseAppAndEventSubstep(props) {
|
|||||||
const onAppChange = React.useCallback(
|
const onAppChange = React.useCallback(
|
||||||
(event, selectedOption) => {
|
(event, selectedOption) => {
|
||||||
if (typeof selectedOption === 'object') {
|
if (typeof selectedOption === 'object') {
|
||||||
// TODO: try to simplify type casting below.
|
const appKey = selectedOption?.value;
|
||||||
const typedSelectedOption = selectedOption;
|
|
||||||
const option = typedSelectedOption;
|
|
||||||
const appKey = option?.value;
|
|
||||||
if (step.appKey !== appKey) {
|
if (step.appKey !== appKey) {
|
||||||
onChange({
|
onChange({
|
||||||
step: {
|
step: {
|
||||||
|
|||||||
@@ -7,37 +7,68 @@ import { Box, TextField } from './style';
|
|||||||
const noop = () => null;
|
const noop = () => null;
|
||||||
|
|
||||||
function EditableTypography(props) {
|
function EditableTypography(props) {
|
||||||
const { children, onConfirm = noop, sx, ...typographyProps } = props;
|
const {
|
||||||
|
children,
|
||||||
|
onConfirm = noop,
|
||||||
|
iconPosition = 'start',
|
||||||
|
iconSize = 'large',
|
||||||
|
sx,
|
||||||
|
disabledEditing = false,
|
||||||
|
prefixValue = '',
|
||||||
|
...typographyProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
const [editing, setEditing] = React.useState(false);
|
const [editing, setEditing] = React.useState(false);
|
||||||
|
|
||||||
const handleClick = React.useCallback(() => {
|
const handleClick = React.useCallback(() => {
|
||||||
|
if (disabledEditing) return;
|
||||||
|
|
||||||
setEditing((editing) => !editing);
|
setEditing((editing) => !editing);
|
||||||
}, []);
|
}, [disabledEditing]);
|
||||||
|
|
||||||
const handleTextFieldClick = React.useCallback((event) => {
|
const handleTextFieldClick = React.useCallback((event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleTextFieldKeyDown = React.useCallback(
|
const handleTextFieldKeyDown = React.useCallback(
|
||||||
async (event) => {
|
async (event) => {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
|
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
if (target.value !== children) {
|
if (target.value !== children) {
|
||||||
await onConfirm(target.value);
|
await onConfirm(target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setEditing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Escape') {
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[children],
|
[children],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleTextFieldBlur = React.useCallback(
|
const handleTextFieldBlur = React.useCallback(
|
||||||
async (event) => {
|
async (event) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
|
|
||||||
if (value !== children) {
|
if (value !== children) {
|
||||||
await onConfirm(value);
|
await onConfirm(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
},
|
},
|
||||||
[onConfirm, children],
|
[onConfirm, children],
|
||||||
);
|
);
|
||||||
let component = <Typography {...typographyProps}>{children}</Typography>;
|
|
||||||
|
let component = (
|
||||||
|
<Typography {...typographyProps}>
|
||||||
|
{prefixValue}
|
||||||
|
{children}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
if (editing) {
|
if (editing) {
|
||||||
component = (
|
component = (
|
||||||
<TextField
|
<TextField
|
||||||
@@ -51,18 +82,34 @@ function EditableTypography(props) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={sx} onClick={handleClick} editing={editing}>
|
<Box
|
||||||
<EditIcon sx={{ mr: 1 }} />
|
sx={sx}
|
||||||
|
onClick={handleClick}
|
||||||
|
editing={editing}
|
||||||
|
disabledEditing={disabledEditing}
|
||||||
|
>
|
||||||
|
{iconPosition === 'start' && editing === false && (
|
||||||
|
<EditIcon fontSize={iconSize} sx={{ mr: 1 }} />
|
||||||
|
)}
|
||||||
|
|
||||||
{component}
|
{component}
|
||||||
|
|
||||||
|
{iconPosition === 'end' && editing === false && (
|
||||||
|
<EditIcon fontSize={iconSize} sx={{ ml: 1 }} />
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableTypography.propTypes = {
|
EditableTypography.propTypes = {
|
||||||
children: PropTypes.string.isRequired,
|
children: PropTypes.string.isRequired,
|
||||||
|
disabledEditing: PropTypes.bool,
|
||||||
|
iconPosition: PropTypes.oneOf(['start', 'end']),
|
||||||
|
iconSize: PropTypes.oneOf(['small', 'large']),
|
||||||
onConfirm: PropTypes.func,
|
onConfirm: PropTypes.func,
|
||||||
|
prefixValue: PropTypes.string,
|
||||||
sx: PropTypes.object,
|
sx: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,23 @@ import { styled } from '@mui/material/styles';
|
|||||||
import MuiBox from '@mui/material/Box';
|
import MuiBox from '@mui/material/Box';
|
||||||
import MuiTextField from '@mui/material/TextField';
|
import MuiTextField from '@mui/material/TextField';
|
||||||
import { inputClasses } from '@mui/material/Input';
|
import { inputClasses } from '@mui/material/Input';
|
||||||
const boxShouldForwardProp = (prop) => !['editing'].includes(prop);
|
|
||||||
|
const boxShouldForwardProp = (prop) =>
|
||||||
|
!['editing', 'disabledEditing'].includes(prop);
|
||||||
|
|
||||||
export const Box = styled(MuiBox, {
|
export const Box = styled(MuiBox, {
|
||||||
shouldForwardProp: boxShouldForwardProp,
|
shouldForwardProp: boxShouldForwardProp,
|
||||||
})`
|
})`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 300px;
|
min-width: 300px;
|
||||||
|
max-width: 90%;
|
||||||
height: 33px;
|
height: 33px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
${({ disabledEditing }) => !disabledEditing && 'cursor: pointer;'}
|
||||||
${({ editing }) => editing && 'border-bottom: 1px dashed #000;'}
|
${({ editing }) => editing && 'border-bottom: 1px dashed #000;'}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TextField = styled(MuiTextField)({
|
export const TextField = styled(MuiTextField)({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
[`.${inputClasses.root}:before, .${inputClasses.root}:after, .${inputClasses.root}:hover`]:
|
[`.${inputClasses.root}:before, .${inputClasses.root}:after, .${inputClasses.root}:hover`]:
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ function Editor(props) {
|
|||||||
connectionId: step.connection?.id,
|
connectionId: step.connection?.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (step.name) {
|
||||||
|
payload.name = step.name;
|
||||||
|
}
|
||||||
|
|
||||||
if (step.appKey) {
|
if (step.appKey) {
|
||||||
payload.appKey = step.appKey;
|
payload.appKey = step.appKey;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,10 @@ const EditorNew = ({ flow }) => {
|
|||||||
connectionId: step.connection?.id,
|
connectionId: step.connection?.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (step.name) {
|
||||||
|
payload.name = step.name || step.keyLabel;
|
||||||
|
}
|
||||||
|
|
||||||
if (step.appKey) {
|
if (step.appKey) {
|
||||||
payload.appKey = step.appKey;
|
payload.appKey = step.appKey;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as React from 'react';
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Collapse from '@mui/material/Collapse';
|
import Collapse from '@mui/material/Collapse';
|
||||||
import List from '@mui/material/List';
|
import List from '@mui/material/List';
|
||||||
@@ -18,6 +19,7 @@ import { isEqual } from 'lodash';
|
|||||||
import { EditorContext } from 'contexts/Editor';
|
import { EditorContext } from 'contexts/Editor';
|
||||||
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
import { StepExecutionsProvider } from 'contexts/StepExecutions';
|
||||||
import TestSubstep from 'components/TestSubstep';
|
import TestSubstep from 'components/TestSubstep';
|
||||||
|
import EditableTypography from 'components/EditableTypography';
|
||||||
import FlowSubstep from 'components/FlowSubstep';
|
import FlowSubstep from 'components/FlowSubstep';
|
||||||
import ChooseAppAndEventSubstep from 'components/ChooseAppAndEventSubstep';
|
import ChooseAppAndEventSubstep from 'components/ChooseAppAndEventSubstep';
|
||||||
import ChooseConnectionSubstep from 'components/ChooseConnectionSubstep';
|
import ChooseConnectionSubstep from 'components/ChooseConnectionSubstep';
|
||||||
@@ -106,10 +108,9 @@ function generateValidationSchema(substeps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FlowStep(props) {
|
function FlowStep(props) {
|
||||||
const { collapsed, onChange, onContinue, flowId } = props;
|
const { collapsed, onChange, onContinue, flowId, step } = 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 [anchorEl, setAnchorEl] = React.useState(null);
|
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||||
const isTrigger = step.type === 'trigger';
|
const isTrigger = step.type === 'trigger';
|
||||||
const isAction = step.type === 'action';
|
const isAction = step.type === 'action';
|
||||||
@@ -117,6 +118,10 @@ function FlowStep(props) {
|
|||||||
const [currentSubstep, setCurrentSubstep] = React.useState(0);
|
const [currentSubstep, setCurrentSubstep] = React.useState(0);
|
||||||
const useAppsOptions = {};
|
const useAppsOptions = {};
|
||||||
|
|
||||||
|
const stepTypeName = isTrigger
|
||||||
|
? formatMessage('flowStep.triggerType')
|
||||||
|
: formatMessage('flowStep.actionType');
|
||||||
|
|
||||||
if (isTrigger) {
|
if (isTrigger) {
|
||||||
useAppsOptions.onlyWithTriggers = true;
|
useAppsOptions.onlyWithTriggers = true;
|
||||||
}
|
}
|
||||||
@@ -183,6 +188,13 @@ function FlowStep(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleStepNameChange = async (name) => {
|
||||||
|
await onChange({
|
||||||
|
...step,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const stepValidationSchema = React.useMemo(
|
const stepValidationSchema = React.useMemo(
|
||||||
() => generateValidationSchema(substeps),
|
() => generateValidationSchema(substeps),
|
||||||
[substeps],
|
[substeps],
|
||||||
@@ -226,7 +238,7 @@ function FlowStep(props) {
|
|||||||
data-test="flow-step"
|
data-test="flow-step"
|
||||||
>
|
>
|
||||||
<Header collapsed={collapsed}>
|
<Header collapsed={collapsed}>
|
||||||
<Stack direction="row" alignItems="center" gap={2}>
|
<Stack direction="row" alignItems="center" gap={3}>
|
||||||
<AppIconWrapper>
|
<AppIconWrapper>
|
||||||
<AppIcon
|
<AppIcon
|
||||||
url={app?.iconUrl}
|
url={app?.iconUrl}
|
||||||
@@ -239,17 +251,30 @@ function FlowStep(props) {
|
|||||||
</AppIconStatusIconWrapper>
|
</AppIconStatusIconWrapper>
|
||||||
</AppIconWrapper>
|
</AppIconWrapper>
|
||||||
|
|
||||||
<div>
|
<Stack direction="column" gap={0.5} sx={{ width: '100%' }}>
|
||||||
<Typography variant="caption">
|
<Typography
|
||||||
{isTrigger
|
component={Stack}
|
||||||
? formatMessage('flowStep.triggerType')
|
direction="row"
|
||||||
: formatMessage('flowStep.actionType')}
|
variant="stepApp"
|
||||||
|
alignItems="center"
|
||||||
|
gap={0.5}
|
||||||
|
>
|
||||||
|
<Chip label={stepTypeName} variant="stepType" size="small" />
|
||||||
|
|
||||||
|
{app?.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="body2">
|
<EditableTypography
|
||||||
{step.position}. {app?.name}
|
iconPosition="end"
|
||||||
</Typography>
|
iconSize="small"
|
||||||
</div>
|
variant="body2"
|
||||||
|
onConfirm={handleStepNameChange}
|
||||||
|
prefixValue={`${step.position}. `}
|
||||||
|
disabledEditing={collapsed}
|
||||||
|
>
|
||||||
|
{step.name}
|
||||||
|
</EditableTypography>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
<Box display="flex" flex={1} justifyContent="end">
|
<Box display="flex" flex={1} justifyContent="end">
|
||||||
{/* as there are no other actions besides "delete step", we hide the context menu. */}
|
{/* as there are no other actions besides "delete step", we hide the context menu. */}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import MuiListItemButton from '@mui/material/ListItemButton';
|
import MuiListItemButton from '@mui/material/ListItemButton';
|
||||||
import MuiTypography from '@mui/material/Typography';
|
import MuiTypography from '@mui/material/Typography';
|
||||||
|
|
||||||
export const ListItemButton = styled(MuiListItemButton)`
|
export const ListItemButton = styled(MuiListItemButton)`
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Typography = styled(MuiTypography)`
|
export const Typography = styled(MuiTypography)`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -6,19 +6,20 @@ export default function useUpdateStep() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const query = useMutation({
|
const query = useMutation({
|
||||||
mutationFn: async ({ id, appKey, key, connectionId, parameters }) => {
|
mutationFn: async ({ id, appKey, key, connectionId, name, parameters }) => {
|
||||||
const { data } = await api.patch(`/v1/steps/${id}`, {
|
const { data } = await api.patch(`/v1/steps/${id}`, {
|
||||||
appKey,
|
appKey,
|
||||||
key,
|
key,
|
||||||
connectionId,
|
connectionId,
|
||||||
|
name,
|
||||||
parameters,
|
parameters,
|
||||||
});
|
});
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: ['flows'],
|
queryKey: ['flows'],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -158,6 +158,10 @@ export const defaultTheme = createTheme({
|
|||||||
fontSize: referenceTheme.typography.pxToRem(16),
|
fontSize: referenceTheme.typography.pxToRem(16),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
stepApp: {
|
||||||
|
fontSize: referenceTheme.typography.pxToRem(12),
|
||||||
|
color: '#5C5C5C',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
MuiAppBar: {
|
MuiAppBar: {
|
||||||
@@ -211,6 +215,23 @@ export const defaultTheme = createTheme({
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
MuiChip: {
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
props: { variant: 'stepType' },
|
||||||
|
style: ({ theme }) => ({
|
||||||
|
color: '#001F52',
|
||||||
|
fontSize: theme.typography.pxToRem(12),
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: alpha(theme.palette.primary.main, 0.3),
|
||||||
|
bgcolor: alpha(
|
||||||
|
theme.palette.primary.main,
|
||||||
|
theme.palette.action.selectedOpacity,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
MuiContainer: {
|
MuiContainer: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
maxWidth: 'xl',
|
maxWidth: 'xl',
|
||||||
@@ -294,6 +315,7 @@ export const defaultTheme = createTheme({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const mationTheme = createTheme(
|
export const mationTheme = createTheme(
|
||||||
deepmerge(defaultTheme, {
|
deepmerge(defaultTheme, {
|
||||||
palette: {
|
palette: {
|
||||||
@@ -315,4 +337,5 @@ export const mationTheme = createTheme(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export default defaultTheme;
|
export default defaultTheme;
|
||||||
|
|||||||
Reference in New Issue
Block a user