mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
feat: project actions UI form (#6115)
## About the changes Add, delete, and update actions is already working. The UI still needs some love, but it's functional  --------- Co-authored-by: Nuno Góis <github@nunogois.com>
This commit is contained in:
parent
260ef70309
commit
924ea39ea2
@ -26,7 +26,7 @@ export const ProjectActionsActionsCell = ({
|
||||
action,
|
||||
onCreateAction,
|
||||
}: IProjectActionsActionsCellProps) => {
|
||||
const { actions } = action;
|
||||
const { id: actionSetId, actions } = action;
|
||||
|
||||
if (actions.length === 0) {
|
||||
if (!onCreateAction) return <TextCell>0 actions</TextCell>;
|
||||
@ -38,21 +38,25 @@ export const ProjectActionsActionsCell = ({
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
<StyledActionItems>
|
||||
{actions.map(({ id, action, executionParams }) => (
|
||||
<div key={id}>
|
||||
<strong>{action}</strong>
|
||||
<StyledParameterList>
|
||||
{Object.entries(executionParams).map(
|
||||
([param, value]) => (
|
||||
<li key={param}>
|
||||
<strong>{param}</strong>:{' '}
|
||||
{value}
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</StyledParameterList>
|
||||
</div>
|
||||
))}
|
||||
{actions.map(
|
||||
({ action, executionParams, sortOrder }) => (
|
||||
<div
|
||||
key={`${actionSetId}/${sortOrder}_${action}`}
|
||||
>
|
||||
<strong>{action}</strong>
|
||||
<StyledParameterList>
|
||||
{Object.entries(executionParams).map(
|
||||
([param, value]) => (
|
||||
<li key={param}>
|
||||
<strong>{param}</strong>:{' '}
|
||||
{value}
|
||||
</li>
|
||||
),
|
||||
)}
|
||||
</StyledParameterList>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</StyledActionItems>
|
||||
}
|
||||
>
|
||||
|
@ -0,0 +1,134 @@
|
||||
import { IconButton, Tooltip } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { IAction } from 'interfaces/action';
|
||||
import { Fragment } from 'react';
|
||||
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
|
||||
import { Delete } from '@mui/icons-material';
|
||||
import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import mapValues from 'lodash.mapvalues';
|
||||
import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureSearch';
|
||||
import {
|
||||
BoxSeparator,
|
||||
Col,
|
||||
InnerBoxHeader,
|
||||
Row,
|
||||
StyledInnerBox,
|
||||
} from './InnerContainerBox';
|
||||
|
||||
export type UIAction = Omit<IAction, 'id' | 'createdAt' | 'createdByUserId'> & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export const ActionItem = ({
|
||||
action,
|
||||
index,
|
||||
stateChanged,
|
||||
onDelete,
|
||||
}: {
|
||||
action: UIAction;
|
||||
index: number;
|
||||
stateChanged: (action: UIAction) => void;
|
||||
onDelete: () => void;
|
||||
}) => {
|
||||
const { id, action: actionName } = action;
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
const environments = useProjectEnvironments(projectId);
|
||||
const { features } = useFeatureSearch(
|
||||
mapValues(
|
||||
{
|
||||
project: `IS:${projectId}`,
|
||||
},
|
||||
(value) => (value ? `${value}` : undefined),
|
||||
),
|
||||
{},
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<ConditionallyRender
|
||||
condition={index > 0}
|
||||
show={<BoxSeparator>THEN</BoxSeparator>}
|
||||
/>
|
||||
<StyledInnerBox>
|
||||
<Row>
|
||||
<span>Action {index + 1}</span>
|
||||
<InnerBoxHeader>
|
||||
<Tooltip title='Delete action' arrow>
|
||||
<IconButton onClick={onDelete}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</InnerBoxHeader>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<GeneralSelect
|
||||
label='Action'
|
||||
name='action'
|
||||
options={[
|
||||
{
|
||||
label: 'Enable flag',
|
||||
key: 'TOGGLE_FEATURE_ON',
|
||||
},
|
||||
{
|
||||
label: 'Disable flag',
|
||||
key: 'TOGGLE_FEATURE_OFF',
|
||||
},
|
||||
]}
|
||||
value={actionName}
|
||||
onChange={(selected) =>
|
||||
stateChanged({
|
||||
...action,
|
||||
action: selected,
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<GeneralSelect
|
||||
label='Environment'
|
||||
name='environment'
|
||||
options={environments.environments.map((env) => ({
|
||||
label: env.name,
|
||||
key: env.name,
|
||||
}))}
|
||||
value={action.executionParams.environment as string}
|
||||
onChange={(selected) =>
|
||||
stateChanged({
|
||||
...action,
|
||||
executionParams: {
|
||||
...action.executionParams,
|
||||
environment: selected,
|
||||
},
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<GeneralSelect
|
||||
label='Flag name'
|
||||
name='flag'
|
||||
options={features.map((feature) => ({
|
||||
label: feature.name,
|
||||
key: feature.name,
|
||||
}))}
|
||||
value={action.executionParams.featureName as string}
|
||||
onChange={(selected) =>
|
||||
stateChanged({
|
||||
...action,
|
||||
executionParams: {
|
||||
...action.executionParams,
|
||||
featureName: selected,
|
||||
},
|
||||
})
|
||||
}
|
||||
fullWidth
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</StyledInnerBox>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
@ -0,0 +1,80 @@
|
||||
import { Badge, IconButton, Tooltip, styled } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { IActionFilter } from './useProjectActionsForm';
|
||||
import { Fragment } from 'react';
|
||||
import { Delete } from '@mui/icons-material';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import {
|
||||
BoxSeparator,
|
||||
InnerBoxHeader,
|
||||
Row,
|
||||
StyledInnerBox,
|
||||
} from './InnerContainerBox';
|
||||
|
||||
const StyledInput = styled(Input)(() => ({
|
||||
width: '100%',
|
||||
}));
|
||||
|
||||
const StyledBadge = styled(Badge)(({ theme }) => ({
|
||||
color: 'primary',
|
||||
margin: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const FilterItem = ({
|
||||
filter,
|
||||
index,
|
||||
stateChanged,
|
||||
onDelete,
|
||||
}: {
|
||||
filter: IActionFilter;
|
||||
index: number;
|
||||
stateChanged: (updatedFilter: IActionFilter) => void;
|
||||
onDelete: () => void;
|
||||
}) => {
|
||||
const { id, parameter, value } = filter;
|
||||
return (
|
||||
<Fragment>
|
||||
<ConditionallyRender
|
||||
condition={index > 0}
|
||||
show={<BoxSeparator>AND</BoxSeparator>}
|
||||
/>
|
||||
<StyledInnerBox>
|
||||
<Row>
|
||||
<span>Filter {index + 1}</span>
|
||||
<InnerBoxHeader>
|
||||
<Tooltip title='Delete filter' arrow>
|
||||
<IconButton type='button' onClick={onDelete}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</InnerBoxHeader>
|
||||
</Row>
|
||||
<Row>
|
||||
<StyledInput
|
||||
label='Parameter'
|
||||
value={parameter}
|
||||
onChange={(e) =>
|
||||
stateChanged({
|
||||
id,
|
||||
parameter: e.target.value,
|
||||
value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<StyledBadge>=</StyledBadge>
|
||||
<StyledInput
|
||||
label='Value'
|
||||
value={value}
|
||||
onChange={(e) =>
|
||||
stateChanged({
|
||||
id,
|
||||
parameter,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Row>
|
||||
</StyledInnerBox>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
@ -0,0 +1,57 @@
|
||||
import { Box, styled } from '@mui/material';
|
||||
|
||||
export const StyledInnerBox = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
padding: theme.spacing(2),
|
||||
borderRadius: `${theme.shape.borderRadiusMedium}px`,
|
||||
}));
|
||||
|
||||
export const InnerBoxHeader = styled('div')(({ theme }) => ({
|
||||
marginLeft: 'auto',
|
||||
whiteSpace: 'nowrap',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
}));
|
||||
|
||||
// row for inner containers
|
||||
export const Row = styled('div')({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const Col = styled('div')({
|
||||
flex: 1,
|
||||
margin: '0 4px',
|
||||
});
|
||||
|
||||
export const BoxSeparator: React.FC = ({ children }) => {
|
||||
const StyledBoxContent = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(0.75, 1),
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
backgroundColor: theme.palette.seen.primary,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
position: 'absolute',
|
||||
zIndex: theme.zIndex.fab,
|
||||
top: '50%',
|
||||
left: theme.spacing(2),
|
||||
transform: 'translateY(-50%)',
|
||||
lineHeight: 1,
|
||||
}));
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: 1.5,
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<StyledBoxContent>{children}</StyledBoxContent>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -1,11 +1,24 @@
|
||||
import { Alert, Link, styled } from '@mui/material';
|
||||
import { Alert, Box, Button, Link, styled } from '@mui/material';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import Input from 'component/common/Input/Input';
|
||||
import { Badge } from 'component/common/Badge/Badge';
|
||||
import { FormSwitch } from 'component/common/FormSwitch/FormSwitch';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { IAction, IActionSet } from 'interfaces/action';
|
||||
import { ProjectActionsFormErrors } from './useProjectActionsForm';
|
||||
import { IActionSet } from 'interfaces/action';
|
||||
import {
|
||||
IActionFilter,
|
||||
ProjectActionsFormErrors,
|
||||
} from './useProjectActionsForm';
|
||||
import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts';
|
||||
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useMemo } from 'react';
|
||||
import GeneralSelect, {} from 'component/common/GeneralSelect/GeneralSelect';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import { Row } from './InnerContainerBox';
|
||||
import { ActionItem, UIAction } from './ActionItem';
|
||||
import { FilterItem } from './FilterItem';
|
||||
|
||||
const StyledServiceAccountAlert = styled(Alert)(({ theme }) => ({
|
||||
marginBottom: theme.spacing(4),
|
||||
@ -27,30 +40,31 @@ const StyledInputDescription = styled('p')(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledInputSecondaryDescription = styled('p')(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
marginBottom: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledInput = styled(Input)(({ theme }) => ({
|
||||
const StyledInput = styled(Input)(() => ({
|
||||
width: '100%',
|
||||
maxWidth: theme.spacing(50),
|
||||
}));
|
||||
|
||||
const StyledSecondarySection = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(3),
|
||||
backgroundColor: theme.palette.background.elevation2,
|
||||
const StyledBadge = styled(Badge)(({ theme }) => ({
|
||||
color: 'primary',
|
||||
margin: 'auto',
|
||||
marginBottom: theme.spacing(1.5),
|
||||
}));
|
||||
|
||||
const StyledBox = styled(Box)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.palette.background.elevation1,
|
||||
marginTop: theme.spacing(2),
|
||||
padding: theme.spacing(2),
|
||||
borderRadius: theme.shape.borderRadiusMedium,
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledInlineContainer = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(0, 4),
|
||||
'& > p:not(:first-of-type)': {
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
const Step = ({ name, children }: any) => (
|
||||
<StyledBox>
|
||||
<StyledBadge color='secondary'>{name}</StyledBadge>
|
||||
{children}
|
||||
</StyledBox>
|
||||
);
|
||||
|
||||
interface IProjectActionsFormProps {
|
||||
action?: IActionSet;
|
||||
@ -60,12 +74,12 @@ interface IProjectActionsFormProps {
|
||||
setName: React.Dispatch<React.SetStateAction<string>>;
|
||||
sourceId: number;
|
||||
setSourceId: React.Dispatch<React.SetStateAction<number>>;
|
||||
filters: Record<string, unknown>;
|
||||
setFilters: React.Dispatch<React.SetStateAction<Record<string, unknown>>>;
|
||||
filters: IActionFilter[];
|
||||
setFilters: React.Dispatch<React.SetStateAction<IActionFilter[]>>;
|
||||
actorId: number;
|
||||
setActorId: React.Dispatch<React.SetStateAction<number>>;
|
||||
actions: IAction[];
|
||||
setActions: React.Dispatch<React.SetStateAction<IAction[]>>;
|
||||
actions: UIAction[];
|
||||
setActions: React.Dispatch<React.SetStateAction<UIAction[]>>;
|
||||
errors: ProjectActionsFormErrors;
|
||||
validateName: (name: string) => boolean;
|
||||
validated: boolean;
|
||||
@ -89,16 +103,83 @@ export const ProjectActionsForm = ({
|
||||
validateName,
|
||||
validated,
|
||||
}: IProjectActionsFormProps) => {
|
||||
const { serviceAccounts } = useServiceAccounts();
|
||||
const { serviceAccounts, loading: serviceAccountsLoading } =
|
||||
useServiceAccounts();
|
||||
const { incomingWebhooks, loading: incomingWebhooksLoading } =
|
||||
useIncomingWebhooks();
|
||||
|
||||
const handleOnBlur = (callback: Function) => {
|
||||
setTimeout(() => callback(), 300);
|
||||
};
|
||||
|
||||
const addFilter = () => {
|
||||
const id = uuidv4();
|
||||
setFilters((filters) => [
|
||||
...filters,
|
||||
{
|
||||
id,
|
||||
parameter: '',
|
||||
value: '',
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const updateInFilters = (updatedFilter: IActionFilter) => {
|
||||
setFilters((filters) =>
|
||||
filters.map((filter) =>
|
||||
filter.id === updatedFilter.id ? updatedFilter : filter,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const addAction = (projectId: string) => {
|
||||
const id = uuidv4();
|
||||
const action: UIAction = {
|
||||
id,
|
||||
action: '',
|
||||
sortOrder:
|
||||
actions
|
||||
.map((a) => a.sortOrder)
|
||||
.reduce((a, b) => Math.max(a, b), 0) + 1,
|
||||
executionParams: {
|
||||
project: projectId,
|
||||
},
|
||||
};
|
||||
setActions([...actions, action]);
|
||||
};
|
||||
|
||||
const updateInActions = (updatedAction: UIAction) => {
|
||||
setActions((actions) =>
|
||||
actions.map((action) =>
|
||||
action.id === updatedAction.id ? updatedAction : action,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const incomingWebhookOptions = useMemo(() => {
|
||||
if (incomingWebhooksLoading) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return incomingWebhooks.map((webhook) => ({
|
||||
label: webhook.name,
|
||||
key: `${webhook.id}`,
|
||||
}));
|
||||
}, [incomingWebhooksLoading, incomingWebhooks]);
|
||||
|
||||
const serviceAccountOptions = useMemo(() => {
|
||||
if (serviceAccountsLoading) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return serviceAccounts.map((sa) => ({
|
||||
label: sa.name,
|
||||
key: `${sa.id}`,
|
||||
}));
|
||||
}, [serviceAccountsLoading, serviceAccounts]);
|
||||
|
||||
const showErrors = validated && Object.values(errors).some(Boolean);
|
||||
|
||||
// TODO: Need to add the remaining fields. Refer to the design
|
||||
|
||||
const projectId = useRequiredPathParam('projectId');
|
||||
return (
|
||||
<div>
|
||||
<ConditionallyRender
|
||||
@ -138,6 +219,100 @@ export const ProjectActionsForm = ({
|
||||
onBlur={(e) => handleOnBlur(() => validateName(e.target.value))}
|
||||
autoComplete='off'
|
||||
/>
|
||||
|
||||
<Step name='Trigger'>
|
||||
<StyledInputDescription>
|
||||
Create incoming webhooks from
|
||||
<RouterLink to='/integrations/incoming-webhooks'>
|
||||
integrations section
|
||||
</RouterLink>
|
||||
.
|
||||
</StyledInputDescription>
|
||||
<GeneralSelect
|
||||
label='Incoming webhook'
|
||||
name='incoming-webhook'
|
||||
options={incomingWebhookOptions}
|
||||
value={`${sourceId}`}
|
||||
onChange={(v) => {
|
||||
setSourceId(parseInt(v));
|
||||
}}
|
||||
/>
|
||||
</Step>
|
||||
|
||||
<Step name='When this'>
|
||||
{filters.map((filter, index) => (
|
||||
<FilterItem
|
||||
key={filter.id}
|
||||
index={index}
|
||||
filter={filter}
|
||||
stateChanged={updateInFilters}
|
||||
onDelete={() =>
|
||||
setFilters((filters) =>
|
||||
filters.filter((f) => f.id !== filter.id),
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
|
||||
<hr />
|
||||
<Row>
|
||||
<Button
|
||||
type='button'
|
||||
startIcon={<Add />}
|
||||
onClick={addFilter}
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
>
|
||||
Add filter
|
||||
</Button>
|
||||
</Row>
|
||||
</Step>
|
||||
|
||||
<Step name='Do these action(s)'>
|
||||
<StyledInputDescription>
|
||||
Create service accounts from
|
||||
<RouterLink to='/admin/service-accounts'>
|
||||
service accounts section
|
||||
</RouterLink>
|
||||
.
|
||||
</StyledInputDescription>
|
||||
<GeneralSelect
|
||||
label='Service account'
|
||||
name='service-account'
|
||||
options={serviceAccountOptions}
|
||||
value={`${actorId}`}
|
||||
onChange={(v) => {
|
||||
setActorId(parseInt(v));
|
||||
}}
|
||||
/>
|
||||
<hr />
|
||||
{actions.map((action, index) => (
|
||||
<ActionItem
|
||||
index={index}
|
||||
key={action.id}
|
||||
action={action}
|
||||
stateChanged={updateInActions}
|
||||
onDelete={() =>
|
||||
setActions((actions) =>
|
||||
actions.filter((a) => a.id !== action.id),
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<hr />
|
||||
<Row>
|
||||
<Button
|
||||
type='button'
|
||||
startIcon={<Add />}
|
||||
onClick={() => addAction(projectId)}
|
||||
variant='outlined'
|
||||
color='primary'
|
||||
>
|
||||
Add action
|
||||
</Button>
|
||||
</Row>
|
||||
</Step>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={showErrors}
|
||||
show={() => (
|
||||
|
@ -1,14 +1,22 @@
|
||||
import { useActions } from 'hooks/api/getters/useActions/useActions';
|
||||
import { IAction, IActionSet } from 'interfaces/action';
|
||||
import { IActionSet } from 'interfaces/action';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { UIAction } from './ActionItem';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
enum ErrorField {
|
||||
export enum ErrorField {
|
||||
NAME = 'name',
|
||||
TRIGGER = 'trigger',
|
||||
ACTOR = 'actor',
|
||||
ACTIONS = 'actions',
|
||||
}
|
||||
|
||||
export interface IActionFilter {
|
||||
id: string;
|
||||
parameter: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const DEFAULT_PROJECT_ACTIONS_FORM_ERRORS = {
|
||||
[ErrorField.NAME]: undefined,
|
||||
[ErrorField.TRIGGER]: undefined,
|
||||
@ -24,14 +32,38 @@ export const useProjectActionsForm = (action?: IActionSet) => {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [name, setName] = useState('');
|
||||
const [sourceId, setSourceId] = useState<number>(0);
|
||||
const [filters, setFilters] = useState<Record<string, unknown>>({});
|
||||
const [filters, setFilters] = useState<IActionFilter[]>([]);
|
||||
const [actorId, setActorId] = useState<number>(0);
|
||||
const [actions, setActions] = useState<IAction[]>([]);
|
||||
const [actions, setActions] = useState<UIAction[]>([]);
|
||||
|
||||
const reloadForm = () => {
|
||||
setEnabled(action?.enabled ?? true);
|
||||
setName(action?.name || '');
|
||||
setValidated(false);
|
||||
if (action?.actorId) {
|
||||
setActorId(action?.actorId);
|
||||
}
|
||||
if (action?.match) {
|
||||
const { sourceId, payload } = action.match;
|
||||
setSourceId(sourceId);
|
||||
setFilters(
|
||||
Object.entries(payload).map(([parameter, value]) => ({
|
||||
id: uuidv4(),
|
||||
parameter,
|
||||
value: value as string,
|
||||
})),
|
||||
);
|
||||
}
|
||||
if (action?.actions) {
|
||||
setActions(
|
||||
action.actions.map((action) => ({
|
||||
id: uuidv4(),
|
||||
action: action.action,
|
||||
sortOrder: action.sortOrder,
|
||||
executionParams: action.executionParams,
|
||||
})),
|
||||
);
|
||||
}
|
||||
setErrors(DEFAULT_PROJECT_ACTIONS_FORM_ERRORS);
|
||||
};
|
||||
|
||||
@ -94,7 +126,7 @@ export const useProjectActionsForm = (action?: IActionSet) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const validateActions = (actions: IAction[]) => {
|
||||
const validateActions = (actions: UIAction[]) => {
|
||||
if (actions.length === 0) {
|
||||
setError(ErrorField.ACTIONS, 'At least one action is required.');
|
||||
return false;
|
||||
|
@ -83,10 +83,22 @@ export const ProjectActionsModal = ({
|
||||
match: {
|
||||
source: 'incoming-webhook',
|
||||
sourceId,
|
||||
payload: filters,
|
||||
payload: filters
|
||||
.filter((f) => f.parameter.length > 0)
|
||||
.reduce(
|
||||
(acc, filter) => ({
|
||||
...acc,
|
||||
[filter.parameter]: filter.value,
|
||||
}),
|
||||
{},
|
||||
),
|
||||
},
|
||||
actorId,
|
||||
actions,
|
||||
actions: actions.map(({ action, sortOrder, executionParams }) => ({
|
||||
action,
|
||||
sortOrder,
|
||||
executionParams,
|
||||
})),
|
||||
};
|
||||
|
||||
const formatApiCode = () => `curl --location --request ${
|
||||
|
@ -19,10 +19,7 @@ export interface IMatch {
|
||||
}
|
||||
|
||||
export interface IAction {
|
||||
id: number;
|
||||
action: string;
|
||||
sortOrder: number;
|
||||
executionParams: Record<string, unknown>;
|
||||
createdAt: string;
|
||||
createdByUserId: number;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user