diff --git a/frontend/src/component/incomingWebhooks/IncomingWebhooksModal/IncomingWebhooksForm/useIncomingWebhooksForm.ts b/frontend/src/component/incomingWebhooks/IncomingWebhooksModal/IncomingWebhooksForm/useIncomingWebhooksForm.ts index 08ddf450f5..5642ad8723 100644 --- a/frontend/src/component/incomingWebhooks/IncomingWebhooksModal/IncomingWebhooksForm/useIncomingWebhooksForm.ts +++ b/frontend/src/component/incomingWebhooks/IncomingWebhooksModal/IncomingWebhooksForm/useIncomingWebhooksForm.ts @@ -99,8 +99,6 @@ export const useIncomingWebhooksForm = (incomingWebhook?: IIncomingWebhook) => { return false; } - // TODO call backend to check if token name is unique - clearError(ErrorField.TOKEN_NAME); return true; }; diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsActionsCell.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsActionsCell.tsx index 19f6903416..4a3b1b3bca 100644 --- a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsActionsCell.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsActionsCell.tsx @@ -26,7 +26,7 @@ export const ProjectActionsActionsCell = ({ action, onCreateAction, }: IProjectActionsActionsCellProps) => { - const { id: actionSetId, actions } = action; + const { actions } = action; if (actions.length === 0) { if (!onCreateAction) return 0 actions; @@ -38,25 +38,21 @@ export const ProjectActionsActionsCell = ({ - {actions.map( - ({ action, executionParams, sortOrder }) => ( -
- {action} - - {Object.entries(executionParams).map( - ([param, value]) => ( -
  • - {param}:{' '} - {value} -
  • - ), - )} -
    -
    - ), - )} + {actions.map(({ id, action, executionParams }) => ( +
    + {action} + + {Object.entries(executionParams).map( + ([param, value]) => ( +
  • + {param}:{' '} + {value} +
  • + ), + )} +
    +
    + ))} } > diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/InnerContainerBox.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/InnerContainerBox.tsx index d2fc1d85f6..4401114167 100644 --- a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/InnerContainerBox.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/InnerContainerBox.tsx @@ -9,7 +9,7 @@ export const StyledInnerBox = styled(Box)(({ theme }) => ({ borderRadius: `${theme.shape.borderRadiusMedium}px`, })); -export const InnerBoxHeader = styled('div')(({ theme }) => ({ +export const StyledInnerBoxHeader = styled('div')(({ theme }) => ({ marginLeft: 'auto', whiteSpace: 'nowrap', [theme.breakpoints.down('sm')]: { @@ -18,31 +18,32 @@ export const InnerBoxHeader = styled('div')(({ theme }) => ({ })); // row for inner containers -export const Row = styled('div')({ +export const StyledRow = styled('div')({ display: 'flex', flexDirection: 'row', width: '100%', }); -export const Col = styled('div')({ +export const StyledCol = styled('div')({ flex: 1, 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 }) => { - 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 ( & { - id: string; -}; - -export const ActionItem = ({ +export const ProjectActionsActionItem = ({ action, index, stateChanged, onDelete, }: { - action: UIAction; + action: ActionsActionState; index: number; - stateChanged: (action: UIAction) => void; + stateChanged: (action: ActionsActionState) => void; onDelete: () => void; }) => { - const { id, action: actionName } = action; + const { action: actionName } = action; const projectId = useRequiredPathParam('projectId'); const environments = useProjectEnvironments(projectId); - const { features } = useFeatureSearch( - mapValues( - { - project: `IS:${projectId}`, - }, - (value) => (value ? `${value}` : undefined), - ), - {}, - ); + const { features } = useFeatureSearch({ project: `IS:${projectId}` }); return ( THEN} /> - + Action {index + 1} - + - - - - + + + + - - + + - - + + - - + + ); diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/FilterItem.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/ProjectActionsFilterItem.tsx similarity index 77% rename from frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/FilterItem.tsx rename to frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/ProjectActionsFilterItem.tsx index 7f5271b6a8..6595271ce0 100644 --- a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/FilterItem.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/ProjectActionsFilterItem.tsx @@ -1,13 +1,13 @@ import { Badge, IconButton, Tooltip, styled } from '@mui/material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { IActionFilter } from './useProjectActionsForm'; +import { ActionsFilterState } from './useProjectActionsForm'; import { Fragment } from 'react'; import { Delete } from '@mui/icons-material'; import Input from 'component/common/Input/Input'; import { BoxSeparator, - InnerBoxHeader, - Row, + StyledInnerBoxHeader, + StyledRow, StyledInnerBox, } from './InnerContainerBox'; @@ -20,18 +20,18 @@ const StyledBadge = styled(Badge)(({ theme }) => ({ margin: theme.spacing(1), })); -export const FilterItem = ({ +export const ProjectActionsFilterItem = ({ filter, index, stateChanged, onDelete, }: { - filter: IActionFilter; + filter: ActionsFilterState; index: number; - stateChanged: (updatedFilter: IActionFilter) => void; + stateChanged: (updatedFilter: ActionsFilterState) => void; onDelete: () => void; }) => { - const { id, parameter, value } = filter; + const { parameter, value } = filter; return ( AND} /> - + Filter {index + 1} - + - - - + + + stateChanged({ - id, + ...filter, parameter: e.target.value, - value, }) } /> @@ -67,13 +66,12 @@ export const FilterItem = ({ value={value} onChange={(e) => stateChanged({ - id, - parameter, + ...filter, value: e.target.value, }) } /> - + ); diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/ProjectActionsForm.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/ProjectActionsForm.tsx index bad5c72d95..791eb60b5c 100644 --- a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/ProjectActionsForm.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsForm/ProjectActionsForm.tsx @@ -4,9 +4,9 @@ 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 { IActionSet } from 'interfaces/action'; import { - IActionFilter, + ActionsFilterState, + ActionsActionState, ProjectActionsFormErrors, } from './useProjectActionsForm'; import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts'; @@ -16,9 +16,9 @@ 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'; +import { StyledRow } from './InnerContainerBox'; +import { ProjectActionsActionItem } from './ProjectActionsActionItem'; +import { ProjectActionsFilterItem } from './ProjectActionsFilterItem'; const StyledServiceAccountAlert = styled(Alert)(({ theme }) => ({ marginBottom: theme.spacing(4), @@ -67,26 +67,24 @@ const Step = ({ name, children }: any) => ( ); interface IProjectActionsFormProps { - action?: IActionSet; enabled: boolean; setEnabled: React.Dispatch>; name: string; setName: React.Dispatch>; sourceId: number; setSourceId: React.Dispatch>; - filters: IActionFilter[]; - setFilters: React.Dispatch>; + filters: ActionsFilterState[]; + setFilters: React.Dispatch>; actorId: number; setActorId: React.Dispatch>; - actions: UIAction[]; - setActions: React.Dispatch>; + actions: ActionsActionState[]; + setActions: React.Dispatch>; errors: ProjectActionsFormErrors; validateName: (name: string) => boolean; validated: boolean; } export const ProjectActionsForm = ({ - action, enabled, setEnabled, name, @@ -124,7 +122,7 @@ export const ProjectActionsForm = ({ ]); }; - const updateInFilters = (updatedFilter: IActionFilter) => { + const updateInFilters = (updatedFilter: ActionsFilterState) => { setFilters((filters) => filters.map((filter) => filter.id === updatedFilter.id ? updatedFilter : filter, @@ -134,7 +132,7 @@ export const ProjectActionsForm = ({ const addAction = (projectId: string) => { const id = uuidv4(); - const action: UIAction = { + const action: ActionsActionState = { id, action: '', sortOrder: @@ -148,7 +146,7 @@ export const ProjectActionsForm = ({ setActions([...actions, action]); }; - const updateInActions = (updatedAction: UIAction) => { + const updateInActions = (updatedAction: ActionsActionState) => { setActions((actions) => actions.map((action) => action.id === updatedAction.id ? updatedAction : action, @@ -241,7 +239,7 @@ export const ProjectActionsForm = ({ {filters.map((filter, index) => ( - - + - + @@ -287,7 +285,7 @@ export const ProjectActionsForm = ({ />
    {actions.map((action, index) => ( - ))}
    - + - +
    & { + id: string; +}; const DEFAULT_PROJECT_ACTIONS_FORM_ERRORS = { [ErrorField.NAME]: undefined, @@ -34,38 +40,33 @@ export const useProjectActionsForm = (action?: IActionSet) => { const [enabled, setEnabled] = useState(false); const [name, setName] = useState(''); const [sourceId, setSourceId] = useState(0); - const [filters, setFilters] = useState([]); + const [filters, setFilters] = useState([]); const [actorId, setActorId] = useState(0); - const [actions, setActions] = useState([]); + const [actions, setActions] = useState([]); 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]) => ({ + setSourceId(action?.match?.sourceId ?? 0); + setFilters( + Object.entries(action?.match?.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, - })), - ); - } + }), + ), + ); + setActorId(action?.actorId ?? 0); + setActions( + action?.actions?.map((action) => ({ + id: uuidv4(), + action: action.action, + sortOrder: action.sortOrder, + executionParams: action.executionParams, + })) ?? [], + ); + setValidated(false); setErrors(DEFAULT_PROJECT_ACTIONS_FORM_ERRORS); }; @@ -128,7 +129,7 @@ export const useProjectActionsForm = (action?: IActionSet) => { return true; }; - const validateActions = (actions: UIAction[]) => { + const validateActions = (actions: ActionsActionState[]) => { if (actions.length === 0) { setError(ErrorField.ACTIONS, 'At least one action is required.'); return false; diff --git a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsModal.tsx b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsModal.tsx index 89ed2eaa82..541e3ad2d7 100644 --- a/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsModal.tsx +++ b/frontend/src/component/project/Project/ProjectSettings/ProjectActions/ProjectActionsTable/ProjectActionsModal/ProjectActionsModal.tsx @@ -77,7 +77,6 @@ export const ProjectActionsModal = ({ const title = `${editing ? 'Edit' : 'New'} action`; const payload: ActionSetPayload = { - project: projectId, enabled, name, match: { @@ -151,7 +150,6 @@ export const ProjectActionsModal = ({ > & { actions: ActionPayload[]; }; diff --git a/frontend/src/hooks/api/getters/useActions/useActions.ts b/frontend/src/hooks/api/getters/useActions/useActions.ts index 46a291bd5a..ae1ea99264 100644 --- a/frontend/src/hooks/api/getters/useActions/useActions.ts +++ b/frontend/src/hooks/api/getters/useActions/useActions.ts @@ -6,6 +6,10 @@ import useUiConfig from '../useUiConfig/useUiConfig'; import { IActionSet } from 'interfaces/action'; import { useUiFlag } from 'hooks/useUiFlag'; +const DEFAULT_DATA = { + actions: [], +}; + export const useActions = (project: string) => { const { isEnterprise } = useUiConfig(); const actionsEnabled = useUiFlag('automatedActions'); @@ -14,14 +18,14 @@ export const useActions = (project: string) => { actions: IActionSet[]; }>( isEnterprise() && actionsEnabled, - { actions: [] }, + DEFAULT_DATA, formatApiPath(`api/admin/projects/${project}/actions`), fetcher, ); return useMemo( () => ({ - actions: (data?.actions ?? []) as IActionSet[], + actions: data?.actions ?? [], loading: !error && !data, refetch: () => mutate(), error, diff --git a/frontend/src/hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks.ts b/frontend/src/hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks.ts index 2f33f7f4d3..0336fc9206 100644 --- a/frontend/src/hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks.ts +++ b/frontend/src/hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks.ts @@ -16,7 +16,9 @@ export const useIncomingWebhooks = () => { const { isEnterprise } = useUiConfig(); const incomingWebhooksEnabled = useUiFlag('incomingWebhooks'); - const { data, error, mutate } = useConditionalSWR( + const { data, error, mutate } = useConditionalSWR<{ + incomingWebhooks: IIncomingWebhook[]; + }>( isEnterprise() && incomingWebhooksEnabled, DEFAULT_DATA, formatApiPath(ENDPOINT), @@ -25,8 +27,7 @@ export const useIncomingWebhooks = () => { return useMemo( () => ({ - incomingWebhooks: (data?.incomingWebhooks ?? - []) as IIncomingWebhook[], + incomingWebhooks: data?.incomingWebhooks ?? [], loading: !error && !data, refetch: () => mutate(), error, diff --git a/frontend/src/interfaces/action.ts b/frontend/src/interfaces/action.ts index 2bb98bb26e..d521c81f06 100644 --- a/frontend/src/interfaces/action.ts +++ b/frontend/src/interfaces/action.ts @@ -19,7 +19,10 @@ export interface IMatch { } export interface IAction { + id: number; action: string; sortOrder: number; executionParams: Record; + createdAt: string; + createdByUserId: number; }