mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
chore: project actions table (#6039)
https://linear.app/unleash/issue/2-1877/ui-add-actions-table Implements the new project actions table. ![image](https://github.com/Unleash/unleash/assets/14320932/2ce96669-4b8f-46cd-9a87-8b14f0682694) ![image](https://github.com/Unleash/unleash/assets/14320932/d73327f2-1e1a-4d57-8ef8-1f4518c4b5d9) ![image](https://github.com/Unleash/unleash/assets/14320932/27b9ffab-4fff-4fdf-808f-b778987fa198)
This commit is contained in:
parent
00b3cbaa8b
commit
32484460ef
@ -57,7 +57,12 @@ export const ProjectActions = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PermissionGuard permissions={ADMIN}>
|
<PermissionGuard permissions={ADMIN}>
|
||||||
<ProjectActionsTable />
|
<ProjectActionsTable
|
||||||
|
modalOpen={actionModalOpen}
|
||||||
|
setModalOpen={setActionModalOpen}
|
||||||
|
selectedAction={selectedAction}
|
||||||
|
setSelectedAction={setSelectedAction}
|
||||||
|
/>
|
||||||
</PermissionGuard>
|
</PermissionGuard>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
import { styled } from '@mui/material';
|
||||||
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
|
import { IActionSet } from 'interfaces/action';
|
||||||
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||||
|
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
||||||
|
|
||||||
|
const StyledActionItems = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledParameterList = styled('ul')(({ theme }) => ({
|
||||||
|
listStyle: 'none',
|
||||||
|
paddingLeft: theme.spacing(1),
|
||||||
|
margin: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IProjectActionsActionsCellProps {
|
||||||
|
action: IActionSet;
|
||||||
|
onCreateAction?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectActionsActionsCell = ({
|
||||||
|
action,
|
||||||
|
onCreateAction,
|
||||||
|
}: IProjectActionsActionsCellProps) => {
|
||||||
|
const { actions } = action;
|
||||||
|
|
||||||
|
if (actions.length === 0) {
|
||||||
|
if (!onCreateAction) return <TextCell>0 actions</TextCell>;
|
||||||
|
else return <LinkCell title='Create action' onClick={onCreateAction} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextCell>
|
||||||
|
<TooltipLink
|
||||||
|
tooltip={
|
||||||
|
<StyledActionItems>
|
||||||
|
{actions.map(({ id, action, executionParams }) => (
|
||||||
|
<div key={id}>
|
||||||
|
<strong>{action}</strong>
|
||||||
|
<StyledParameterList>
|
||||||
|
{Object.entries(executionParams).map(
|
||||||
|
([param, value]) => (
|
||||||
|
<li key={param}>
|
||||||
|
{param}: {value}
|
||||||
|
</li>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</StyledParameterList>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</StyledActionItems>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{actions.length === 1
|
||||||
|
? '1 action'
|
||||||
|
: `${actions.length} actions`}
|
||||||
|
</TooltipLink>
|
||||||
|
</TextCell>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,23 @@
|
|||||||
|
import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
|
||||||
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
|
import { IActionSet } from 'interfaces/action';
|
||||||
|
import { IServiceAccount } from 'interfaces/service-account';
|
||||||
|
|
||||||
|
interface IProjectActionsActorCellProps {
|
||||||
|
action: IActionSet;
|
||||||
|
serviceAccounts: IServiceAccount[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectActionsActorCell = ({
|
||||||
|
action,
|
||||||
|
serviceAccounts,
|
||||||
|
}: IProjectActionsActorCellProps) => {
|
||||||
|
const { actorId } = action;
|
||||||
|
const actor = serviceAccounts.find(({ id }) => id === actorId);
|
||||||
|
|
||||||
|
if (!actor) {
|
||||||
|
return <TextCell>No service account</TextCell>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <LinkCell to='/admin/service-accounts'>{actor.name}</LinkCell>;
|
||||||
|
};
|
@ -0,0 +1,31 @@
|
|||||||
|
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||||
|
import { IActionSet } from 'interfaces/action';
|
||||||
|
|
||||||
|
interface IProjectActionsDeleteDialogProps {
|
||||||
|
action?: IActionSet;
|
||||||
|
open: boolean;
|
||||||
|
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
onConfirm: (action: IActionSet) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectActionsDeleteDialog = ({
|
||||||
|
action,
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
onConfirm,
|
||||||
|
}: IProjectActionsDeleteDialogProps) => (
|
||||||
|
<Dialogue
|
||||||
|
title='Delete action?'
|
||||||
|
open={open}
|
||||||
|
primaryButtonText='Delete action'
|
||||||
|
secondaryButtonText='Cancel'
|
||||||
|
onClick={() => onConfirm(action!)}
|
||||||
|
onClose={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
You are about to delete action: <strong>{action?.name}</strong>
|
||||||
|
</p>
|
||||||
|
</Dialogue>
|
||||||
|
);
|
@ -0,0 +1,43 @@
|
|||||||
|
import { styled, Typography } from '@mui/material';
|
||||||
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
|
import { IActionSet } from 'interfaces/action';
|
||||||
|
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
||||||
|
|
||||||
|
const StyledItem = styled(Typography)(({ theme }) => ({
|
||||||
|
fontSize: theme.fontSizes.smallerBody,
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IProjectActionsFiltersCellProps {
|
||||||
|
action: IActionSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectActionsFiltersCell = ({
|
||||||
|
action,
|
||||||
|
}: IProjectActionsFiltersCellProps) => {
|
||||||
|
const { payload } = action.match;
|
||||||
|
const filters = Object.entries(payload);
|
||||||
|
|
||||||
|
if (filters.length === 0) {
|
||||||
|
return <TextCell>0 filters</TextCell>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextCell>
|
||||||
|
<TooltipLink
|
||||||
|
tooltip={
|
||||||
|
<>
|
||||||
|
{filters.map(([parameter, value]) => (
|
||||||
|
<StyledItem key={parameter}>
|
||||||
|
{parameter}: {value}
|
||||||
|
</StyledItem>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{filters.length === 1
|
||||||
|
? '1 filter'
|
||||||
|
: `${filters.length} filters`}
|
||||||
|
</TooltipLink>
|
||||||
|
</TextCell>
|
||||||
|
);
|
||||||
|
};
|
@ -1,3 +1,253 @@
|
|||||||
export const ProjectActionsTable = () => {
|
import { useMemo, useState } from 'react';
|
||||||
return <span>TODO</span>;
|
import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import useToast from 'hooks/useToast';
|
||||||
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
|
import { useMediaQuery } from '@mui/material';
|
||||||
|
import { useFlexLayout, useSortBy, useTable } from 'react-table';
|
||||||
|
import { sortTypes } from 'utils/sortTypes';
|
||||||
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
|
import theme from 'themes/theme';
|
||||||
|
import { useConditionallyHiddenColumns } from 'hooks/useConditionallyHiddenColumns';
|
||||||
|
import { useActions } from 'hooks/api/getters/useActions/useActions';
|
||||||
|
import { useActionsApi } from 'hooks/api/actions/useActionsApi/useActionsApi';
|
||||||
|
import { IActionSet } from 'interfaces/action';
|
||||||
|
import { ToggleCell } from 'component/common/Table/cells/ToggleCell/ToggleCell';
|
||||||
|
import { ProjectActionsTriggerCell } from './ProjectActionsTriggerCell';
|
||||||
|
import { ProjectActionsFiltersCell } from './ProjectActionsFiltersCell';
|
||||||
|
import { ProjectActionsActorCell } from './ProjectActionsActorCell';
|
||||||
|
import { ProjectActionsActionsCell } from './ProjectActionsActionsCell';
|
||||||
|
import { ProjectActionsTableActionsCell } from './ProjectActionsTableActionsCell';
|
||||||
|
// import { ProjectActionsModal } from '../ProjectActionsModal/ProjectActionsModal';
|
||||||
|
import { ProjectActionsDeleteDialog } from './ProjectActionsDeleteDialog';
|
||||||
|
import { useServiceAccounts } from 'hooks/api/getters/useServiceAccounts/useServiceAccounts';
|
||||||
|
import { useIncomingWebhooks } from 'hooks/api/getters/useIncomingWebhooks/useIncomingWebhooks';
|
||||||
|
|
||||||
|
interface IProjectActionsTableProps {
|
||||||
|
modalOpen: boolean;
|
||||||
|
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
selectedAction?: IActionSet;
|
||||||
|
setSelectedAction: React.Dispatch<
|
||||||
|
React.SetStateAction<IActionSet | undefined>
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectActionsTable = ({
|
||||||
|
modalOpen,
|
||||||
|
setModalOpen,
|
||||||
|
selectedAction,
|
||||||
|
setSelectedAction,
|
||||||
|
}: IProjectActionsTableProps) => {
|
||||||
|
const { setToastData, setToastApiError } = useToast();
|
||||||
|
|
||||||
|
const { actions, refetch } = useActions();
|
||||||
|
const { toggleActionSet, removeActionSet } = useActionsApi();
|
||||||
|
|
||||||
|
const { incomingWebhooks } = useIncomingWebhooks();
|
||||||
|
const { serviceAccounts } = useServiceAccounts();
|
||||||
|
|
||||||
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||||
|
|
||||||
|
const onToggleAction = async (action: IActionSet, enabled: boolean) => {
|
||||||
|
try {
|
||||||
|
await toggleActionSet(action.id, enabled);
|
||||||
|
setToastData({
|
||||||
|
title: `"${action.name}" has been ${
|
||||||
|
enabled ? 'enabled' : 'disabled'
|
||||||
|
}`,
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDeleteConfirm = async (action: IActionSet) => {
|
||||||
|
try {
|
||||||
|
await removeActionSet(action.id);
|
||||||
|
setToastData({
|
||||||
|
title: `"${action.name}" has been deleted`,
|
||||||
|
type: 'success',
|
||||||
|
});
|
||||||
|
refetch();
|
||||||
|
setDeleteOpen(false);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
setToastApiError(formatUnknownError(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const isMediumScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: 'Name',
|
||||||
|
accessor: 'name',
|
||||||
|
minWidth: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'trigger',
|
||||||
|
Header: 'Trigger',
|
||||||
|
Cell: ({
|
||||||
|
row: { original: action },
|
||||||
|
}: { row: { original: IActionSet } }) => (
|
||||||
|
<ProjectActionsTriggerCell
|
||||||
|
action={action}
|
||||||
|
incomingWebhooks={incomingWebhooks}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'filters',
|
||||||
|
Header: 'Filters',
|
||||||
|
Cell: ({
|
||||||
|
row: { original: action },
|
||||||
|
}: {
|
||||||
|
row: { original: IActionSet };
|
||||||
|
}) => <ProjectActionsFiltersCell action={action} />,
|
||||||
|
maxWidth: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actor',
|
||||||
|
Header: 'Service account',
|
||||||
|
Cell: ({
|
||||||
|
row: { original: action },
|
||||||
|
}: {
|
||||||
|
row: { original: IActionSet };
|
||||||
|
}) => (
|
||||||
|
<ProjectActionsActorCell
|
||||||
|
action={action}
|
||||||
|
serviceAccounts={serviceAccounts}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
minWidth: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
Header: 'Actions',
|
||||||
|
Cell: ({
|
||||||
|
row: { original: action },
|
||||||
|
}: {
|
||||||
|
row: { original: IActionSet };
|
||||||
|
}) => (
|
||||||
|
<ProjectActionsActionsCell
|
||||||
|
action={action}
|
||||||
|
onCreateAction={() => {
|
||||||
|
setSelectedAction(action);
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
maxWidth: 130,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: 'Enabled',
|
||||||
|
accessor: 'enabled',
|
||||||
|
Cell: ({
|
||||||
|
row: { original: action },
|
||||||
|
}: { row: { original: IActionSet } }) => (
|
||||||
|
<ToggleCell
|
||||||
|
checked={action.enabled}
|
||||||
|
setChecked={(enabled) =>
|
||||||
|
onToggleAction(action, enabled)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
sortType: 'boolean',
|
||||||
|
width: 90,
|
||||||
|
maxWidth: 90,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'table-actions',
|
||||||
|
Header: '',
|
||||||
|
align: 'center',
|
||||||
|
Cell: ({
|
||||||
|
row: { original: action },
|
||||||
|
}: { row: { original: IActionSet } }) => (
|
||||||
|
<ProjectActionsTableActionsCell
|
||||||
|
actionId={action.id}
|
||||||
|
onEdit={() => {
|
||||||
|
setSelectedAction(action);
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
setSelectedAction(action);
|
||||||
|
setDeleteOpen(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
width: 50,
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[actions, incomingWebhooks, serviceAccounts],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [initialState] = useState({
|
||||||
|
sortBy: [{ id: 'name', desc: true }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { headerGroups, rows, prepareRow, setHiddenColumns } = useTable(
|
||||||
|
{
|
||||||
|
columns: columns as any,
|
||||||
|
data: actions,
|
||||||
|
initialState,
|
||||||
|
sortTypes,
|
||||||
|
autoResetHiddenColumns: false,
|
||||||
|
autoResetSortBy: false,
|
||||||
|
disableSortRemove: true,
|
||||||
|
disableMultiSort: true,
|
||||||
|
defaultColumn: {
|
||||||
|
Cell: TextCell,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useSortBy,
|
||||||
|
useFlexLayout,
|
||||||
|
);
|
||||||
|
|
||||||
|
useConditionallyHiddenColumns(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
condition: isMediumScreen,
|
||||||
|
columns: ['actor', 'enabled'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
condition: isExtraSmallScreen,
|
||||||
|
columns: ['filters', 'actions'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
setHiddenColumns,
|
||||||
|
columns,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<VirtualizedTable
|
||||||
|
rows={rows}
|
||||||
|
headerGroups={headerGroups}
|
||||||
|
prepareRow={prepareRow}
|
||||||
|
/>
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={rows.length === 0}
|
||||||
|
show={
|
||||||
|
<TablePlaceholder>
|
||||||
|
No actions available. Get started by adding one.
|
||||||
|
</TablePlaceholder>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/* <ProjectActionsModal
|
||||||
|
action={selectedAction}
|
||||||
|
open={modalOpen}
|
||||||
|
setOpen={setModalOpen}
|
||||||
|
/> */}
|
||||||
|
<ProjectActionsDeleteDialog
|
||||||
|
action={selectedAction}
|
||||||
|
open={deleteOpen}
|
||||||
|
setOpen={setDeleteOpen}
|
||||||
|
onConfirm={onDeleteConfirm}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
IconButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
Popover,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
styled,
|
||||||
|
} from '@mui/material';
|
||||||
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
|
import { Delete, Edit } from '@mui/icons-material';
|
||||||
|
import { PermissionHOC } from 'component/common/PermissionHOC/PermissionHOC';
|
||||||
|
import { ADMIN } from 'component/providers/AccessProvider/permissions';
|
||||||
|
import { defaultBorderRadius } from 'themes/themeStyles';
|
||||||
|
|
||||||
|
const StyledBoxCell = styled(Box)({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IProjectActionsTableActionsCellProps {
|
||||||
|
actionId: number;
|
||||||
|
onEdit: (event: React.SyntheticEvent) => void;
|
||||||
|
onDelete: (event: React.SyntheticEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectActionsTableActionsCell = ({
|
||||||
|
actionId,
|
||||||
|
onEdit,
|
||||||
|
onDelete,
|
||||||
|
}: IProjectActionsTableActionsCellProps) => {
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
|
||||||
|
const open = Boolean(anchorEl);
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = `action-${actionId}-actions`;
|
||||||
|
const menuId = `${id}-menu`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledBoxCell>
|
||||||
|
<Tooltip title='Action actions' arrow describeChild>
|
||||||
|
<IconButton
|
||||||
|
id={id}
|
||||||
|
data-loading
|
||||||
|
aria-controls={open ? menuId : undefined}
|
||||||
|
aria-haspopup='true'
|
||||||
|
aria-expanded={open ? 'true' : undefined}
|
||||||
|
onClick={handleClick}
|
||||||
|
type='button'
|
||||||
|
>
|
||||||
|
<MoreVertIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Popover
|
||||||
|
id={menuId}
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={open}
|
||||||
|
onClose={handleClose}
|
||||||
|
onClick={handleClose}
|
||||||
|
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
|
||||||
|
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
|
||||||
|
disableScrollLock={true}
|
||||||
|
PaperProps={{
|
||||||
|
sx: (theme) => ({
|
||||||
|
borderRadius: `${theme.shape.borderRadius}px`,
|
||||||
|
padding: theme.spacing(1, 1.5),
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuList aria-labelledby={id}>
|
||||||
|
<PermissionHOC permission={ADMIN}>
|
||||||
|
{({ hasAccess }) => (
|
||||||
|
<MenuItem
|
||||||
|
sx={defaultBorderRadius}
|
||||||
|
onClick={onEdit}
|
||||||
|
disabled={!hasAccess}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Edit />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
<Typography variant='body2'>
|
||||||
|
Edit
|
||||||
|
</Typography>
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</PermissionHOC>
|
||||||
|
<PermissionHOC permission={ADMIN}>
|
||||||
|
{({ hasAccess }) => (
|
||||||
|
<MenuItem
|
||||||
|
sx={defaultBorderRadius}
|
||||||
|
onClick={onDelete}
|
||||||
|
disabled={!hasAccess}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Delete />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
<Typography variant='body2'>
|
||||||
|
Remove
|
||||||
|
</Typography>
|
||||||
|
</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</PermissionHOC>
|
||||||
|
</MenuList>
|
||||||
|
</Popover>
|
||||||
|
</StyledBoxCell>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,66 @@
|
|||||||
|
import { Avatar, Box, Link, styled } from '@mui/material';
|
||||||
|
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
|
||||||
|
import { IActionSet } from 'interfaces/action';
|
||||||
|
import { IIncomingWebhook } from 'interfaces/incomingWebhook';
|
||||||
|
import webhooksIcon from 'assets/icons/webhooks.svg';
|
||||||
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
import { ComponentType } from 'react';
|
||||||
|
import { wrapperStyles } from 'component/common/Table/cells/LinkCell/LinkCell.styles';
|
||||||
|
|
||||||
|
const StyledCell = styled(Box)({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledIcon = styled(Avatar)(({ theme }) => ({
|
||||||
|
borderRadius: theme.shape.borderRadius,
|
||||||
|
overflow: 'hidden',
|
||||||
|
width: theme.spacing(3),
|
||||||
|
height: theme.spacing(3),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const StyledLink = styled(Link)<{
|
||||||
|
component?: ComponentType<any>;
|
||||||
|
to?: string;
|
||||||
|
}>(({ theme }) => ({
|
||||||
|
...wrapperStyles(theme),
|
||||||
|
'&:hover, &:focus': {
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface IProjectActionsTriggerCellProps {
|
||||||
|
action: IActionSet;
|
||||||
|
incomingWebhooks: IIncomingWebhook[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectActionsTriggerCell = ({
|
||||||
|
action,
|
||||||
|
incomingWebhooks,
|
||||||
|
}: IProjectActionsTriggerCellProps) => {
|
||||||
|
const { sourceId } = action.match;
|
||||||
|
const trigger = incomingWebhooks.find(({ id }) => id === sourceId);
|
||||||
|
|
||||||
|
if (!trigger) {
|
||||||
|
return <TextCell>No trigger</TextCell>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextCell>
|
||||||
|
<StyledCell>
|
||||||
|
<StyledIcon
|
||||||
|
src={webhooksIcon}
|
||||||
|
alt='Incoming webhook'
|
||||||
|
variant='rounded'
|
||||||
|
/>
|
||||||
|
<StyledLink
|
||||||
|
component={RouterLink}
|
||||||
|
to='/integrations/incoming-webhooks'
|
||||||
|
underline='hover'
|
||||||
|
>
|
||||||
|
{trigger.name}
|
||||||
|
</StyledLink>
|
||||||
|
</StyledCell>
|
||||||
|
</TextCell>
|
||||||
|
);
|
||||||
|
};
|
@ -52,6 +52,40 @@ export const useActionsApi = () => {
|
|||||||
await makeRequest(req.caller, req.id);
|
await makeRequest(req.caller, req.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const enableActionSet = async (actionSetId: number) => {
|
||||||
|
const requestId = 'enableActionSet';
|
||||||
|
const req = createRequest(
|
||||||
|
`${ENDPOINT}/${actionSetId}/on`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
requestId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableActionSet = async (actionSetId: number) => {
|
||||||
|
const requestId = 'disableActionSet';
|
||||||
|
const req = createRequest(
|
||||||
|
`${ENDPOINT}/${actionSetId}/off`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
},
|
||||||
|
requestId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await makeRequest(req.caller, req.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleActionSet = async (actionSetId: number, enabled: boolean) => {
|
||||||
|
if (enabled) {
|
||||||
|
await enableActionSet(actionSetId);
|
||||||
|
} else {
|
||||||
|
await disableActionSet(actionSetId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const removeActionSet = async (actionSetId: number) => {
|
const removeActionSet = async (actionSetId: number) => {
|
||||||
const requestId = 'removeActionSet';
|
const requestId = 'removeActionSet';
|
||||||
const req = createRequest(
|
const req = createRequest(
|
||||||
@ -67,6 +101,7 @@ export const useActionsApi = () => {
|
|||||||
addActionSet,
|
addActionSet,
|
||||||
updateActionSet,
|
updateActionSet,
|
||||||
removeActionSet,
|
removeActionSet,
|
||||||
|
toggleActionSet,
|
||||||
errors,
|
errors,
|
||||||
loading,
|
loading,
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export interface IActionSet {
|
export interface IActionSet {
|
||||||
id: number;
|
id: number;
|
||||||
|
enabled: boolean;
|
||||||
name: string;
|
name: string;
|
||||||
project: string;
|
project: string;
|
||||||
actorId: number;
|
actorId: number;
|
||||||
|
Loading…
Reference in New Issue
Block a user