1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-23 00:16:25 +01:00

refactor: project actions (#6203)

https://linear.app/unleash/issue/2-1938/refactor-project-actions

Refactors project actions to not include the project in the payload.

Includes other misc scouting.
This commit is contained in:
Nuno Góis 2024-02-12 17:10:33 +00:00 committed by GitHub
parent c224d7dc4c
commit 9511e64027
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 132 additions and 147 deletions

View File

@ -99,8 +99,6 @@ export const useIncomingWebhooksForm = (incomingWebhook?: IIncomingWebhook) => {
return false; return false;
} }
// TODO call backend to check if token name is unique
clearError(ErrorField.TOKEN_NAME); clearError(ErrorField.TOKEN_NAME);
return true; return true;
}; };

View File

@ -26,7 +26,7 @@ export const ProjectActionsActionsCell = ({
action, action,
onCreateAction, onCreateAction,
}: IProjectActionsActionsCellProps) => { }: IProjectActionsActionsCellProps) => {
const { id: actionSetId, actions } = action; const { actions } = action;
if (actions.length === 0) { if (actions.length === 0) {
if (!onCreateAction) return <TextCell>0 actions</TextCell>; if (!onCreateAction) return <TextCell>0 actions</TextCell>;
@ -38,25 +38,21 @@ export const ProjectActionsActionsCell = ({
<TooltipLink <TooltipLink
tooltip={ tooltip={
<StyledActionItems> <StyledActionItems>
{actions.map( {actions.map(({ id, action, executionParams }) => (
({ action, executionParams, sortOrder }) => ( <div key={id}>
<div <strong>{action}</strong>
key={`${actionSetId}/${sortOrder}_${action}`} <StyledParameterList>
> {Object.entries(executionParams).map(
<strong>{action}</strong> ([param, value]) => (
<StyledParameterList> <li key={param}>
{Object.entries(executionParams).map( <strong>{param}</strong>:{' '}
([param, value]) => ( {value}
<li key={param}> </li>
<strong>{param}</strong>:{' '} ),
{value} )}
</li> </StyledParameterList>
), </div>
)} ))}
</StyledParameterList>
</div>
),
)}
</StyledActionItems> </StyledActionItems>
} }
> >

View File

@ -9,7 +9,7 @@ export const StyledInnerBox = styled(Box)(({ theme }) => ({
borderRadius: `${theme.shape.borderRadiusMedium}px`, borderRadius: `${theme.shape.borderRadiusMedium}px`,
})); }));
export const InnerBoxHeader = styled('div')(({ theme }) => ({ export const StyledInnerBoxHeader = styled('div')(({ theme }) => ({
marginLeft: 'auto', marginLeft: 'auto',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
[theme.breakpoints.down('sm')]: { [theme.breakpoints.down('sm')]: {
@ -18,31 +18,32 @@ export const InnerBoxHeader = styled('div')(({ theme }) => ({
})); }));
// row for inner containers // row for inner containers
export const Row = styled('div')({ export const StyledRow = styled('div')({
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
width: '100%', width: '100%',
}); });
export const Col = styled('div')({ export const StyledCol = styled('div')({
flex: 1, flex: 1,
margin: '0 4px', margin: '0 4px',
}); });
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,
}));
export const BoxSeparator: React.FC = ({ children }) => { 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 ( return (
<Box <Box
sx={{ sx={{

View File

@ -1,48 +1,35 @@
import { IconButton, Tooltip } from '@mui/material'; import { IconButton, Tooltip } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IAction } from 'interfaces/action';
import { Fragment } from 'react'; import { Fragment } from 'react';
import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect'; import GeneralSelect from 'component/common/GeneralSelect/GeneralSelect';
import { Delete } from '@mui/icons-material'; import { Delete } from '@mui/icons-material';
import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments'; import { useProjectEnvironments } from 'hooks/api/getters/useProjectEnvironments/useProjectEnvironments';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import mapValues from 'lodash.mapvalues';
import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureSearch'; import { useFeatureSearch } from 'hooks/api/getters/useFeatureSearch/useFeatureSearch';
import { import {
BoxSeparator, BoxSeparator,
Col, StyledCol,
InnerBoxHeader, StyledInnerBoxHeader,
Row, StyledRow,
StyledInnerBox, StyledInnerBox,
} from './InnerContainerBox'; } from './InnerContainerBox';
import { ActionsActionState } from './useProjectActionsForm';
export type UIAction = Omit<IAction, 'id' | 'createdAt' | 'createdByUserId'> & { export const ProjectActionsActionItem = ({
id: string;
};
export const ActionItem = ({
action, action,
index, index,
stateChanged, stateChanged,
onDelete, onDelete,
}: { }: {
action: UIAction; action: ActionsActionState;
index: number; index: number;
stateChanged: (action: UIAction) => void; stateChanged: (action: ActionsActionState) => void;
onDelete: () => void; onDelete: () => void;
}) => { }) => {
const { id, action: actionName } = action; const { action: actionName } = action;
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const environments = useProjectEnvironments(projectId); const environments = useProjectEnvironments(projectId);
const { features } = useFeatureSearch( const { features } = useFeatureSearch({ project: `IS:${projectId}` });
mapValues(
{
project: `IS:${projectId}`,
},
(value) => (value ? `${value}` : undefined),
),
{},
);
return ( return (
<Fragment> <Fragment>
<ConditionallyRender <ConditionallyRender
@ -50,18 +37,18 @@ export const ActionItem = ({
show={<BoxSeparator>THEN</BoxSeparator>} show={<BoxSeparator>THEN</BoxSeparator>}
/> />
<StyledInnerBox> <StyledInnerBox>
<Row> <StyledRow>
<span>Action {index + 1}</span> <span>Action {index + 1}</span>
<InnerBoxHeader> <StyledInnerBoxHeader>
<Tooltip title='Delete action' arrow> <Tooltip title='Delete action' arrow>
<IconButton onClick={onDelete}> <IconButton onClick={onDelete}>
<Delete /> <Delete />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</InnerBoxHeader> </StyledInnerBoxHeader>
</Row> </StyledRow>
<Row> <StyledRow>
<Col> <StyledCol>
<GeneralSelect <GeneralSelect
label='Action' label='Action'
name='action' name='action'
@ -84,8 +71,8 @@ export const ActionItem = ({
} }
fullWidth fullWidth
/> />
</Col> </StyledCol>
<Col> <StyledCol>
<GeneralSelect <GeneralSelect
label='Environment' label='Environment'
name='environment' name='environment'
@ -105,8 +92,8 @@ export const ActionItem = ({
} }
fullWidth fullWidth
/> />
</Col> </StyledCol>
<Col> <StyledCol>
<GeneralSelect <GeneralSelect
label='Flag name' label='Flag name'
name='flag' name='flag'
@ -126,8 +113,8 @@ export const ActionItem = ({
} }
fullWidth fullWidth
/> />
</Col> </StyledCol>
</Row> </StyledRow>
</StyledInnerBox> </StyledInnerBox>
</Fragment> </Fragment>
); );

View File

@ -1,13 +1,13 @@
import { Badge, IconButton, Tooltip, styled } from '@mui/material'; import { Badge, IconButton, Tooltip, styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IActionFilter } from './useProjectActionsForm'; import { ActionsFilterState } from './useProjectActionsForm';
import { Fragment } from 'react'; import { Fragment } from 'react';
import { Delete } from '@mui/icons-material'; import { Delete } from '@mui/icons-material';
import Input from 'component/common/Input/Input'; import Input from 'component/common/Input/Input';
import { import {
BoxSeparator, BoxSeparator,
InnerBoxHeader, StyledInnerBoxHeader,
Row, StyledRow,
StyledInnerBox, StyledInnerBox,
} from './InnerContainerBox'; } from './InnerContainerBox';
@ -20,18 +20,18 @@ const StyledBadge = styled(Badge)(({ theme }) => ({
margin: theme.spacing(1), margin: theme.spacing(1),
})); }));
export const FilterItem = ({ export const ProjectActionsFilterItem = ({
filter, filter,
index, index,
stateChanged, stateChanged,
onDelete, onDelete,
}: { }: {
filter: IActionFilter; filter: ActionsFilterState;
index: number; index: number;
stateChanged: (updatedFilter: IActionFilter) => void; stateChanged: (updatedFilter: ActionsFilterState) => void;
onDelete: () => void; onDelete: () => void;
}) => { }) => {
const { id, parameter, value } = filter; const { parameter, value } = filter;
return ( return (
<Fragment> <Fragment>
<ConditionallyRender <ConditionallyRender
@ -39,25 +39,24 @@ export const FilterItem = ({
show={<BoxSeparator>AND</BoxSeparator>} show={<BoxSeparator>AND</BoxSeparator>}
/> />
<StyledInnerBox> <StyledInnerBox>
<Row> <StyledRow>
<span>Filter {index + 1}</span> <span>Filter {index + 1}</span>
<InnerBoxHeader> <StyledInnerBoxHeader>
<Tooltip title='Delete filter' arrow> <Tooltip title='Delete filter' arrow>
<IconButton type='button' onClick={onDelete}> <IconButton type='button' onClick={onDelete}>
<Delete /> <Delete />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
</InnerBoxHeader> </StyledInnerBoxHeader>
</Row> </StyledRow>
<Row> <StyledRow>
<StyledInput <StyledInput
label='Parameter' label='Parameter'
value={parameter} value={parameter}
onChange={(e) => onChange={(e) =>
stateChanged({ stateChanged({
id, ...filter,
parameter: e.target.value, parameter: e.target.value,
value,
}) })
} }
/> />
@ -67,13 +66,12 @@ export const FilterItem = ({
value={value} value={value}
onChange={(e) => onChange={(e) =>
stateChanged({ stateChanged({
id, ...filter,
parameter,
value: e.target.value, value: e.target.value,
}) })
} }
/> />
</Row> </StyledRow>
</StyledInnerBox> </StyledInnerBox>
</Fragment> </Fragment>
); );

View File

@ -4,9 +4,9 @@ import Input from 'component/common/Input/Input';
import { Badge } from 'component/common/Badge/Badge'; import { Badge } from 'component/common/Badge/Badge';
import { FormSwitch } from 'component/common/FormSwitch/FormSwitch'; import { FormSwitch } from 'component/common/FormSwitch/FormSwitch';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { IActionSet } from 'interfaces/action';
import { import {
IActionFilter, ActionsFilterState,
ActionsActionState,
ProjectActionsFormErrors, ProjectActionsFormErrors,
} from './useProjectActionsForm'; } from './useProjectActionsForm';
import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts'; import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts';
@ -16,9 +16,9 @@ import { useMemo } from 'react';
import GeneralSelect, {} from 'component/common/GeneralSelect/GeneralSelect'; import GeneralSelect, {} from 'component/common/GeneralSelect/GeneralSelect';
import { Add } from '@mui/icons-material'; import { Add } from '@mui/icons-material';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { Row } from './InnerContainerBox'; import { StyledRow } from './InnerContainerBox';
import { ActionItem, UIAction } from './ActionItem'; import { ProjectActionsActionItem } from './ProjectActionsActionItem';
import { FilterItem } from './FilterItem'; import { ProjectActionsFilterItem } from './ProjectActionsFilterItem';
const StyledServiceAccountAlert = styled(Alert)(({ theme }) => ({ const StyledServiceAccountAlert = styled(Alert)(({ theme }) => ({
marginBottom: theme.spacing(4), marginBottom: theme.spacing(4),
@ -67,26 +67,24 @@ const Step = ({ name, children }: any) => (
); );
interface IProjectActionsFormProps { interface IProjectActionsFormProps {
action?: IActionSet;
enabled: boolean; enabled: boolean;
setEnabled: React.Dispatch<React.SetStateAction<boolean>>; setEnabled: React.Dispatch<React.SetStateAction<boolean>>;
name: string; name: string;
setName: React.Dispatch<React.SetStateAction<string>>; setName: React.Dispatch<React.SetStateAction<string>>;
sourceId: number; sourceId: number;
setSourceId: React.Dispatch<React.SetStateAction<number>>; setSourceId: React.Dispatch<React.SetStateAction<number>>;
filters: IActionFilter[]; filters: ActionsFilterState[];
setFilters: React.Dispatch<React.SetStateAction<IActionFilter[]>>; setFilters: React.Dispatch<React.SetStateAction<ActionsFilterState[]>>;
actorId: number; actorId: number;
setActorId: React.Dispatch<React.SetStateAction<number>>; setActorId: React.Dispatch<React.SetStateAction<number>>;
actions: UIAction[]; actions: ActionsActionState[];
setActions: React.Dispatch<React.SetStateAction<UIAction[]>>; setActions: React.Dispatch<React.SetStateAction<ActionsActionState[]>>;
errors: ProjectActionsFormErrors; errors: ProjectActionsFormErrors;
validateName: (name: string) => boolean; validateName: (name: string) => boolean;
validated: boolean; validated: boolean;
} }
export const ProjectActionsForm = ({ export const ProjectActionsForm = ({
action,
enabled, enabled,
setEnabled, setEnabled,
name, name,
@ -124,7 +122,7 @@ export const ProjectActionsForm = ({
]); ]);
}; };
const updateInFilters = (updatedFilter: IActionFilter) => { const updateInFilters = (updatedFilter: ActionsFilterState) => {
setFilters((filters) => setFilters((filters) =>
filters.map((filter) => filters.map((filter) =>
filter.id === updatedFilter.id ? updatedFilter : filter, filter.id === updatedFilter.id ? updatedFilter : filter,
@ -134,7 +132,7 @@ export const ProjectActionsForm = ({
const addAction = (projectId: string) => { const addAction = (projectId: string) => {
const id = uuidv4(); const id = uuidv4();
const action: UIAction = { const action: ActionsActionState = {
id, id,
action: '', action: '',
sortOrder: sortOrder:
@ -148,7 +146,7 @@ export const ProjectActionsForm = ({
setActions([...actions, action]); setActions([...actions, action]);
}; };
const updateInActions = (updatedAction: UIAction) => { const updateInActions = (updatedAction: ActionsActionState) => {
setActions((actions) => setActions((actions) =>
actions.map((action) => actions.map((action) =>
action.id === updatedAction.id ? updatedAction : action, action.id === updatedAction.id ? updatedAction : action,
@ -241,7 +239,7 @@ export const ProjectActionsForm = ({
<Step name='When this'> <Step name='When this'>
{filters.map((filter, index) => ( {filters.map((filter, index) => (
<FilterItem <ProjectActionsFilterItem
key={filter.id} key={filter.id}
index={index} index={index}
filter={filter} filter={filter}
@ -255,7 +253,7 @@ export const ProjectActionsForm = ({
))} ))}
<hr /> <hr />
<Row> <StyledRow>
<Button <Button
type='button' type='button'
startIcon={<Add />} startIcon={<Add />}
@ -265,7 +263,7 @@ export const ProjectActionsForm = ({
> >
Add filter Add filter
</Button> </Button>
</Row> </StyledRow>
</Step> </Step>
<Step name='Do these action(s)'> <Step name='Do these action(s)'>
@ -287,7 +285,7 @@ export const ProjectActionsForm = ({
/> />
<hr /> <hr />
{actions.map((action, index) => ( {actions.map((action, index) => (
<ActionItem <ProjectActionsActionItem
index={index} index={index}
key={action.id} key={action.id}
action={action} action={action}
@ -300,7 +298,7 @@ export const ProjectActionsForm = ({
/> />
))} ))}
<hr /> <hr />
<Row> <StyledRow>
<Button <Button
type='button' type='button'
startIcon={<Add />} startIcon={<Add />}
@ -310,7 +308,7 @@ export const ProjectActionsForm = ({
> >
Add action Add action
</Button> </Button>
</Row> </StyledRow>
</Step> </Step>
<ConditionallyRender <ConditionallyRender

View File

@ -1,22 +1,28 @@
import { useActions } from 'hooks/api/getters/useActions/useActions'; import { useActions } from 'hooks/api/getters/useActions/useActions';
import { IActionSet } from 'interfaces/action'; import { IAction, IActionSet } from 'interfaces/action';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { UIAction } from './ActionItem';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
export enum ErrorField { enum ErrorField {
NAME = 'name', NAME = 'name',
TRIGGER = 'trigger', TRIGGER = 'trigger',
ACTOR = 'actor', ACTOR = 'actor',
ACTIONS = 'actions', ACTIONS = 'actions',
} }
export interface IActionFilter { export type ActionsFilterState = {
id: string; id: string;
parameter: string; parameter: string;
value: string; value: string;
} };
export type ActionsActionState = Omit<
IAction,
'id' | 'createdAt' | 'createdByUserId'
> & {
id: string;
};
const DEFAULT_PROJECT_ACTIONS_FORM_ERRORS = { const DEFAULT_PROJECT_ACTIONS_FORM_ERRORS = {
[ErrorField.NAME]: undefined, [ErrorField.NAME]: undefined,
@ -34,38 +40,33 @@ export const useProjectActionsForm = (action?: IActionSet) => {
const [enabled, setEnabled] = useState(false); const [enabled, setEnabled] = useState(false);
const [name, setName] = useState(''); const [name, setName] = useState('');
const [sourceId, setSourceId] = useState<number>(0); const [sourceId, setSourceId] = useState<number>(0);
const [filters, setFilters] = useState<IActionFilter[]>([]); const [filters, setFilters] = useState<ActionsFilterState[]>([]);
const [actorId, setActorId] = useState<number>(0); const [actorId, setActorId] = useState<number>(0);
const [actions, setActions] = useState<UIAction[]>([]); const [actions, setActions] = useState<ActionsActionState[]>([]);
const reloadForm = () => { const reloadForm = () => {
setEnabled(action?.enabled ?? true); setEnabled(action?.enabled ?? true);
setName(action?.name || ''); setName(action?.name || '');
setValidated(false); setSourceId(action?.match?.sourceId ?? 0);
if (action?.actorId) { setFilters(
setActorId(action?.actorId); Object.entries(action?.match?.payload ?? {}).map(
} ([parameter, value]) => ({
if (action?.match) {
const { sourceId, payload } = action.match;
setSourceId(sourceId);
setFilters(
Object.entries(payload).map(([parameter, value]) => ({
id: uuidv4(), id: uuidv4(),
parameter, parameter,
value: value as string, value: value as string,
})), }),
); ),
} );
if (action?.actions) { setActorId(action?.actorId ?? 0);
setActions( setActions(
action.actions.map((action) => ({ action?.actions?.map((action) => ({
id: uuidv4(), id: uuidv4(),
action: action.action, action: action.action,
sortOrder: action.sortOrder, sortOrder: action.sortOrder,
executionParams: action.executionParams, executionParams: action.executionParams,
})), })) ?? [],
); );
} setValidated(false);
setErrors(DEFAULT_PROJECT_ACTIONS_FORM_ERRORS); setErrors(DEFAULT_PROJECT_ACTIONS_FORM_ERRORS);
}; };
@ -128,7 +129,7 @@ export const useProjectActionsForm = (action?: IActionSet) => {
return true; return true;
}; };
const validateActions = (actions: UIAction[]) => { const validateActions = (actions: ActionsActionState[]) => {
if (actions.length === 0) { if (actions.length === 0) {
setError(ErrorField.ACTIONS, 'At least one action is required.'); setError(ErrorField.ACTIONS, 'At least one action is required.');
return false; return false;

View File

@ -77,7 +77,6 @@ export const ProjectActionsModal = ({
const title = `${editing ? 'Edit' : 'New'} action`; const title = `${editing ? 'Edit' : 'New'} action`;
const payload: ActionSetPayload = { const payload: ActionSetPayload = {
project: projectId,
enabled, enabled,
name, name,
match: { match: {
@ -151,7 +150,6 @@ export const ProjectActionsModal = ({
> >
<StyledForm onSubmit={onSubmit}> <StyledForm onSubmit={onSubmit}>
<ProjectActionsForm <ProjectActionsForm
action={action}
enabled={enabled} enabled={enabled}
setEnabled={setEnabled} setEnabled={setEnabled}
name={name} name={name}

View File

@ -8,7 +8,7 @@ export type ActionPayload = Omit<
export type ActionSetPayload = Omit< export type ActionSetPayload = Omit<
IActionSet, IActionSet,
'id' | 'createdAt' | 'createdByUserId' 'id' | 'project' | 'actions' | 'createdAt' | 'createdByUserId'
> & { > & {
actions: ActionPayload[]; actions: ActionPayload[];
}; };

View File

@ -6,6 +6,10 @@ import useUiConfig from '../useUiConfig/useUiConfig';
import { IActionSet } from 'interfaces/action'; import { IActionSet } from 'interfaces/action';
import { useUiFlag } from 'hooks/useUiFlag'; import { useUiFlag } from 'hooks/useUiFlag';
const DEFAULT_DATA = {
actions: [],
};
export const useActions = (project: string) => { export const useActions = (project: string) => {
const { isEnterprise } = useUiConfig(); const { isEnterprise } = useUiConfig();
const actionsEnabled = useUiFlag('automatedActions'); const actionsEnabled = useUiFlag('automatedActions');
@ -14,14 +18,14 @@ export const useActions = (project: string) => {
actions: IActionSet[]; actions: IActionSet[];
}>( }>(
isEnterprise() && actionsEnabled, isEnterprise() && actionsEnabled,
{ actions: [] }, DEFAULT_DATA,
formatApiPath(`api/admin/projects/${project}/actions`), formatApiPath(`api/admin/projects/${project}/actions`),
fetcher, fetcher,
); );
return useMemo( return useMemo(
() => ({ () => ({
actions: (data?.actions ?? []) as IActionSet[], actions: data?.actions ?? [],
loading: !error && !data, loading: !error && !data,
refetch: () => mutate(), refetch: () => mutate(),
error, error,

View File

@ -16,7 +16,9 @@ export const useIncomingWebhooks = () => {
const { isEnterprise } = useUiConfig(); const { isEnterprise } = useUiConfig();
const incomingWebhooksEnabled = useUiFlag('incomingWebhooks'); const incomingWebhooksEnabled = useUiFlag('incomingWebhooks');
const { data, error, mutate } = useConditionalSWR( const { data, error, mutate } = useConditionalSWR<{
incomingWebhooks: IIncomingWebhook[];
}>(
isEnterprise() && incomingWebhooksEnabled, isEnterprise() && incomingWebhooksEnabled,
DEFAULT_DATA, DEFAULT_DATA,
formatApiPath(ENDPOINT), formatApiPath(ENDPOINT),
@ -25,8 +27,7 @@ export const useIncomingWebhooks = () => {
return useMemo( return useMemo(
() => ({ () => ({
incomingWebhooks: (data?.incomingWebhooks ?? incomingWebhooks: data?.incomingWebhooks ?? [],
[]) as IIncomingWebhook[],
loading: !error && !data, loading: !error && !data,
refetch: () => mutate(), refetch: () => mutate(),
error, error,

View File

@ -19,7 +19,10 @@ export interface IMatch {
} }
export interface IAction { export interface IAction {
id: number;
action: string; action: string;
sortOrder: number; sortOrder: number;
executionParams: Record<string, unknown>; executionParams: Record<string, unknown>;
createdAt: string;
createdByUserId: number;
} }