feat: add support to rename flow step

This commit is contained in:
Ali BARIN
2025-01-07 13:46:07 +00:00
parent 583c09ce40
commit e29e71bdf1
9 changed files with 140 additions and 30 deletions

View File

@@ -91,15 +91,15 @@ function ChooseAppAndEventSubstep(props) {
const onEventChange = React.useCallback(
(event, selectedOption) => {
if (typeof selectedOption === 'object') {
// TODO: try to simplify type casting below.
const typedSelectedOption = selectedOption;
const option = typedSelectedOption;
const eventKey = option?.value;
const eventKey = selectedOption?.value;
const eventLabel = selectedOption?.label;
if (step.key !== eventKey) {
onChange({
step: {
...step,
key: eventKey,
keyLabel: eventLabel,
},
});
}
@@ -111,10 +111,8 @@ function ChooseAppAndEventSubstep(props) {
const onAppChange = React.useCallback(
(event, selectedOption) => {
if (typeof selectedOption === 'object') {
// TODO: try to simplify type casting below.
const typedSelectedOption = selectedOption;
const option = typedSelectedOption;
const appKey = option?.value;
const appKey = selectedOption?.value;
if (step.appKey !== appKey) {
onChange({
step: {

View File

@@ -7,37 +7,68 @@ import { Box, TextField } from './style';
const noop = () => null;
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 handleClick = React.useCallback(() => {
if (disabledEditing) return;
setEditing((editing) => !editing);
}, []);
}, [disabledEditing]);
const handleTextFieldClick = React.useCallback((event) => {
event.stopPropagation();
}, []);
const handleTextFieldKeyDown = React.useCallback(
async (event) => {
const target = event.target;
if (event.key === 'Enter') {
if (target.value !== children) {
await onConfirm(target.value);
}
setEditing(false);
}
if (event.key === 'Escape') {
setEditing(false);
}
},
[children],
);
const handleTextFieldBlur = React.useCallback(
async (event) => {
const value = event.target.value;
if (value !== children) {
await onConfirm(value);
}
setEditing(false);
},
[onConfirm, children],
);
let component = <Typography {...typographyProps}>{children}</Typography>;
let component = (
<Typography {...typographyProps}>
{prefixValue}
{children}
</Typography>
);
if (editing) {
component = (
<TextField
@@ -51,18 +82,34 @@ function EditableTypography(props) {
/>
);
}
return (
<Box sx={sx} onClick={handleClick} editing={editing}>
<EditIcon sx={{ mr: 1 }} />
<Box
sx={sx}
onClick={handleClick}
editing={editing}
disabledEditing={disabledEditing}
>
{iconPosition === 'start' && editing === false && (
<EditIcon fontSize={iconSize} sx={{ mr: 1 }} />
)}
{component}
{iconPosition === 'end' && editing === false && (
<EditIcon fontSize={iconSize} sx={{ ml: 1 }} />
)}
</Box>
);
}
EditableTypography.propTypes = {
children: PropTypes.string.isRequired,
disabledEditing: PropTypes.bool,
iconPosition: PropTypes.oneOf(['start', 'end']),
iconSize: PropTypes.oneOf(['small', 'large']),
onConfirm: PropTypes.func,
prefixValue: PropTypes.string,
sx: PropTypes.object,
};

View File

@@ -2,17 +2,23 @@ import { styled } from '@mui/material/styles';
import MuiBox from '@mui/material/Box';
import MuiTextField from '@mui/material/TextField';
import { inputClasses } from '@mui/material/Input';
const boxShouldForwardProp = (prop) => !['editing'].includes(prop);
const boxShouldForwardProp = (prop) =>
!['editing', 'disabledEditing'].includes(prop);
export const Box = styled(MuiBox, {
shouldForwardProp: boxShouldForwardProp,
})`
display: flex;
flex: 1;
width: 300px;
min-width: 300px;
max-width: 90%;
height: 33px;
align-items: center;
${({ disabledEditing }) => !disabledEditing && 'cursor: pointer;'}
${({ editing }) => editing && 'border-bottom: 1px dashed #000;'}
`;
export const TextField = styled(MuiTextField)({
width: '100%',
[`.${inputClasses.root}:before, .${inputClasses.root}:after, .${inputClasses.root}:hover`]:

View File

@@ -27,6 +27,10 @@ function Editor(props) {
connectionId: step.connection?.id,
};
if (step.name) {
payload.name = step.name;
}
if (step.appKey) {
payload.appKey = step.appKey;
}

View File

@@ -90,6 +90,10 @@ const EditorNew = ({ flow }) => {
connectionId: step.connection?.id,
};
if (step.name) {
payload.name = step.name || step.keyLabel;
}
if (step.appKey) {
payload.appKey = step.appKey;
}

View File

@@ -3,6 +3,7 @@ import * as React from 'react';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';
import Button from '@mui/material/Button';
import Collapse from '@mui/material/Collapse';
import List from '@mui/material/List';
@@ -18,6 +19,7 @@ import { isEqual } from 'lodash';
import { EditorContext } from 'contexts/Editor';
import { StepExecutionsProvider } from 'contexts/StepExecutions';
import TestSubstep from 'components/TestSubstep';
import EditableTypography from 'components/EditableTypography';
import FlowSubstep from 'components/FlowSubstep';
import ChooseAppAndEventSubstep from 'components/ChooseAppAndEventSubstep';
import ChooseConnectionSubstep from 'components/ChooseConnectionSubstep';
@@ -106,10 +108,9 @@ function generateValidationSchema(substeps) {
}
function FlowStep(props) {
const { collapsed, onChange, onContinue, flowId } = props;
const { collapsed, onChange, onContinue, flowId, step } = props;
const editorContext = React.useContext(EditorContext);
const contextButtonRef = React.useRef(null);
const step = props.step;
const [anchorEl, setAnchorEl] = React.useState(null);
const isTrigger = step.type === 'trigger';
const isAction = step.type === 'action';
@@ -117,6 +118,10 @@ function FlowStep(props) {
const [currentSubstep, setCurrentSubstep] = React.useState(0);
const useAppsOptions = {};
const stepTypeName = isTrigger
? formatMessage('flowStep.triggerType')
: formatMessage('flowStep.actionType');
if (isTrigger) {
useAppsOptions.onlyWithTriggers = true;
}
@@ -183,6 +188,13 @@ function FlowStep(props) {
}
};
const handleStepNameChange = async (name) => {
await onChange({
...step,
name,
});
};
const stepValidationSchema = React.useMemo(
() => generateValidationSchema(substeps),
[substeps],
@@ -226,7 +238,7 @@ function FlowStep(props) {
data-test="flow-step"
>
<Header collapsed={collapsed}>
<Stack direction="row" alignItems="center" gap={2}>
<Stack direction="row" alignItems="center" gap={3}>
<AppIconWrapper>
<AppIcon
url={app?.iconUrl}
@@ -239,17 +251,30 @@ function FlowStep(props) {
</AppIconStatusIconWrapper>
</AppIconWrapper>
<div>
<Typography variant="caption">
{isTrigger
? formatMessage('flowStep.triggerType')
: formatMessage('flowStep.actionType')}
<Stack direction="column" gap={0.5} sx={{ width: '100%' }}>
<Typography
component={Stack}
direction="row"
variant="stepApp"
alignItems="center"
gap={0.5}
>
<Chip label={stepTypeName} variant="stepType" size="small" />
{app?.name}
</Typography>
<Typography variant="body2">
{step.position}. {app?.name}
</Typography>
</div>
<EditableTypography
iconPosition="end"
iconSize="small"
variant="body2"
onConfirm={handleStepNameChange}
prefixValue={`${step.position}. `}
disabledEditing={collapsed}
>
{step.name}
</EditableTypography>
</Stack>
<Box display="flex" flex={1} justifyContent="end">
{/* as there are no other actions besides "delete step", we hide the context menu. */}

View File

@@ -1,9 +1,11 @@
import { styled } from '@mui/material/styles';
import MuiListItemButton from '@mui/material/ListItemButton';
import MuiTypography from '@mui/material/Typography';
export const ListItemButton = styled(MuiListItemButton)`
justify-content: space-between;
`;
export const Typography = styled(MuiTypography)`
display: flex;
align-items: center;

View File

@@ -6,19 +6,20 @@ export default function useUpdateStep() {
const queryClient = useQueryClient();
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}`, {
appKey,
key,
connectionId,
name,
parameters,
});
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: ['flows'],
});
},

View File

@@ -158,6 +158,10 @@ export const defaultTheme = createTheme({
fontSize: referenceTheme.typography.pxToRem(16),
},
},
stepApp: {
fontSize: referenceTheme.typography.pxToRem(12),
color: '#5C5C5C',
},
},
components: {
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: {
defaultProps: {
maxWidth: 'xl',
@@ -294,6 +315,7 @@ export const defaultTheme = createTheme({
},
},
});
export const mationTheme = createTheme(
deepmerge(defaultTheme, {
palette: {
@@ -315,4 +337,5 @@ export const mationTheme = createTheme(
},
}),
);
export default defaultTheme;