mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
chore: new actions table UI (#6435)
https://linear.app/unleash/issue/2-1995/ui-feature-rename-adapt-the-actions-ui https://linear.app/unleash/issue/2-1988/nice-to-have-last-action-status-on-the-action-sets-table Brings the project actions table UI up to par with the new designs, implementing some features that were discussed with UX. ![image](https://github.com/Unleash/unleash/assets/14320932/1a8ef47d-ab33-4284-9650-4c2481dd16dc) ![image](https://github.com/Unleash/unleash/assets/14320932/c19f6691-67a5-4abc-a546-837e25cad388) ![image](https://github.com/Unleash/unleash/assets/14320932/61e3f221-ae93-4d87-98f1-db2782285d2e)
This commit is contained in:
parent
454f44dec5
commit
86a795e87c
@ -3,6 +3,13 @@ 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';
|
||||
import { ProjectActionsLastEvent } from './ProjectActionsLastEvent';
|
||||
|
||||
const StyledCell = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: theme.spacing(1),
|
||||
}));
|
||||
|
||||
const StyledActionItems = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -34,7 +41,8 @@ export const ProjectActionsActionsCell = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<TextCell>
|
||||
<StyledCell>
|
||||
<ProjectActionsLastEvent action={action} />
|
||||
<TooltipLink
|
||||
tooltip={
|
||||
<StyledActionItems>
|
||||
@ -60,6 +68,6 @@ export const ProjectActionsActionsCell = ({
|
||||
? '1 action'
|
||||
: `${actions.length} actions`}
|
||||
</TooltipLink>
|
||||
</TextCell>
|
||||
</StyledCell>
|
||||
);
|
||||
};
|
@ -0,0 +1,59 @@
|
||||
import { IActionSet } from 'interfaces/action';
|
||||
import { TooltipLink } from 'component/common/TooltipLink/TooltipLink';
|
||||
import { useActionEvents } from 'hooks/api/getters/useActionEvents/useActionEvents';
|
||||
import { ProjectActionsEventsDetails } from '../ProjectActionsEventsModal/ProjectActionsEventsDetails/ProjectActionsEventsDetails';
|
||||
import { CircularProgress, styled } from '@mui/material';
|
||||
import { CheckCircle, Error as ErrorIcon } from '@mui/icons-material';
|
||||
|
||||
const StyledTooltipLink = styled(TooltipLink)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
export const StyledSuccessIcon = styled(CheckCircle)(({ theme }) => ({
|
||||
color: theme.palette.success.main,
|
||||
}));
|
||||
|
||||
export const StyledFailedIcon = styled(ErrorIcon)(({ theme }) => ({
|
||||
color: theme.palette.error.main,
|
||||
}));
|
||||
|
||||
interface IProjectActionsLastEventProps {
|
||||
action: IActionSet;
|
||||
}
|
||||
|
||||
export const ProjectActionsLastEvent = ({
|
||||
action,
|
||||
}: IProjectActionsLastEventProps) => {
|
||||
const { id, project } = action;
|
||||
const { actionEvents } = useActionEvents(id, project, 1, {
|
||||
refreshInterval: 5000,
|
||||
});
|
||||
|
||||
if (actionEvents.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const actionSetEvent = actionEvents[0];
|
||||
|
||||
const icon =
|
||||
actionSetEvent.state === 'success' ? (
|
||||
<StyledSuccessIcon />
|
||||
) : actionSetEvent.state === 'failed' ? (
|
||||
<StyledFailedIcon />
|
||||
) : (
|
||||
<CircularProgress size={20} />
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTooltipLink
|
||||
tooltipProps={{
|
||||
maxWidth: 500,
|
||||
maxHeight: 600,
|
||||
}}
|
||||
tooltip={<ProjectActionsEventsDetails {...actionSetEvent} />}
|
||||
>
|
||||
{icon}
|
||||
</StyledTooltipLink>
|
||||
);
|
||||
};
|
@ -19,5 +19,11 @@ export const ProjectActionsActorCell = ({
|
||||
return <TextCell>No service account</TextCell>;
|
||||
}
|
||||
|
||||
return <LinkCell to='/admin/service-accounts'>{actor.name}</LinkCell>;
|
||||
return (
|
||||
<LinkCell
|
||||
to='/admin/service-accounts'
|
||||
title={actor.name}
|
||||
subtitle={actor.username}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -10,6 +10,10 @@ const StyledDetails = styled('div')(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
}));
|
||||
|
||||
const StyledAlert = styled(Alert)({
|
||||
fontSize: 'inherit',
|
||||
});
|
||||
|
||||
export const ProjectActionsEventsDetails = ({
|
||||
state,
|
||||
actionSet: { actions },
|
||||
@ -24,9 +28,9 @@ export const ProjectActionsEventsDetails = ({
|
||||
|
||||
return (
|
||||
<StyledDetails>
|
||||
<Alert severity={state === 'failed' ? 'error' : 'success'}>
|
||||
<StyledAlert severity={state === 'failed' ? 'error' : 'success'}>
|
||||
{stateText}
|
||||
</Alert>
|
||||
</StyledAlert>
|
||||
<ProjectActionsEventsDetailsSource signal={signal} />
|
||||
{actions.map((action, i) => (
|
||||
<ProjectActionsEventsDetailsAction
|
||||
|
@ -44,6 +44,7 @@ export const StyledFailedIcon = styled(ErrorOutline)(({ theme }) => ({
|
||||
|
||||
const StyledAlert = styled(Alert)(({ theme }) => ({
|
||||
marginTop: theme.spacing(2),
|
||||
fontSize: 'inherit',
|
||||
}));
|
||||
|
||||
const StyledDivider = styled(Divider)(({ theme }) => ({
|
||||
|
@ -24,6 +24,10 @@ const StyledAccordion = styled(Accordion)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledAccordionSummary = styled(AccordionSummary)({
|
||||
lineHeight: '1.5rem',
|
||||
});
|
||||
|
||||
const StyledLink = styled(Link)(({ theme }) => ({
|
||||
marginLeft: theme.spacing(1),
|
||||
}));
|
||||
@ -47,7 +51,7 @@ export const ProjectActionsEventsDetailsSourceSignalEndpoint = ({
|
||||
|
||||
return (
|
||||
<StyledAccordion>
|
||||
<AccordionSummary
|
||||
<StyledAccordionSummary
|
||||
expandIcon={
|
||||
<IconButton>
|
||||
<ExpandMore titleAccess='Toggle' />
|
||||
@ -58,7 +62,7 @@ export const ProjectActionsEventsDetailsSourceSignalEndpoint = ({
|
||||
<StyledLink to='/integrations/signals'>
|
||||
{signalEndpointName}
|
||||
</StyledLink>
|
||||
</AccordionSummary>
|
||||
</StyledAccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Suspense fallback={null}>
|
||||
<LazyReactJSONEditor
|
||||
|
@ -2,6 +2,7 @@ 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';
|
||||
import { formatOperatorDescription } from 'component/common/NewConstraintAccordion/ConstraintOperator/formatOperatorDescription';
|
||||
|
||||
const StyledItem = styled(Typography)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallerBody,
|
||||
@ -24,6 +25,9 @@ export const ProjectActionsFiltersCell = ({
|
||||
return (
|
||||
<TextCell>
|
||||
<TooltipLink
|
||||
tooltipProps={{
|
||||
maxWidth: 500,
|
||||
}}
|
||||
tooltip={
|
||||
<>
|
||||
{filters.map(
|
||||
@ -36,18 +40,33 @@ export const ProjectActionsFiltersCell = ({
|
||||
value,
|
||||
values,
|
||||
},
|
||||
]) => (
|
||||
<StyledItem key={parameter}>
|
||||
<strong>{parameter}</strong>{' '}
|
||||
{inverted ? 'NOT' : ''} {operator}{' '}
|
||||
{caseInsensitive
|
||||
? '(case insensitive)'
|
||||
: ''}{' '}
|
||||
<strong>
|
||||
{values ? values.join(', ') : value}
|
||||
</strong>
|
||||
</StyledItem>
|
||||
),
|
||||
]) => {
|
||||
const operatorDescription: string =
|
||||
formatOperatorDescription(operator);
|
||||
|
||||
const operatorText = inverted ? (
|
||||
<>
|
||||
is <u>not</u>{' '}
|
||||
{operatorDescription.substring(2)}
|
||||
</>
|
||||
) : (
|
||||
operatorDescription
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledItem key={parameter}>
|
||||
<strong>{parameter}</strong>{' '}
|
||||
{operatorText}
|
||||
{caseInsensitive
|
||||
? ' (case insensitive)'
|
||||
: ''}
|
||||
{': '}
|
||||
<strong>
|
||||
{values ? values.join(', ') : value}
|
||||
</strong>
|
||||
</StyledItem>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</>
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ export const useProjectActionsForm = (action?: IActionSet) => {
|
||||
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [name, setName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [sourceId, setSourceId] = useState<number>(0);
|
||||
const [filters, setFilters] = useState<ActionsFilterState[]>([]);
|
||||
const [actorId, setActorId] = useState<number>(0);
|
||||
@ -49,6 +50,7 @@ export const useProjectActionsForm = (action?: IActionSet) => {
|
||||
const reloadForm = () => {
|
||||
setEnabled(action?.enabled ?? true);
|
||||
setName(action?.name || '');
|
||||
setDescription(action?.description || '');
|
||||
setSourceId(action?.match?.sourceId ?? 0);
|
||||
setFilters(
|
||||
Object.entries(action?.match?.payload ?? {}).map(
|
||||
@ -171,6 +173,8 @@ export const useProjectActionsForm = (action?: IActionSet) => {
|
||||
setEnabled,
|
||||
name,
|
||||
setName,
|
||||
description,
|
||||
setDescription,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
filters,
|
||||
|
@ -69,6 +69,8 @@ export const ProjectActionsModal = ({
|
||||
setEnabled,
|
||||
name,
|
||||
setName,
|
||||
description,
|
||||
setDescription,
|
||||
sourceId,
|
||||
setSourceId,
|
||||
filters,
|
||||
@ -94,6 +96,7 @@ export const ProjectActionsModal = ({
|
||||
const payload: ActionSetPayload = {
|
||||
enabled,
|
||||
name,
|
||||
description,
|
||||
match: {
|
||||
source: 'signal-endpoint',
|
||||
sourceId,
|
||||
|
@ -31,43 +31,43 @@ const StyledLink = styled(Link)<{
|
||||
},
|
||||
}));
|
||||
|
||||
interface IProjectActionsTriggerCellProps {
|
||||
interface IProjectActionsSourceCellProps {
|
||||
action: IActionSet;
|
||||
signalEndpoints: ISignalEndpoint[];
|
||||
}
|
||||
|
||||
export const ProjectActionsTriggerCell = ({
|
||||
export const ProjectActionsSourceCell = ({
|
||||
action,
|
||||
signalEndpoints,
|
||||
}: IProjectActionsTriggerCellProps) => {
|
||||
}: IProjectActionsSourceCellProps) => {
|
||||
const { sourceId, source } = action.match;
|
||||
const trigger = signalEndpoints.find(({ id }) => id === sourceId);
|
||||
|
||||
if (!trigger) {
|
||||
return <TextCell>No trigger</TextCell>;
|
||||
if (source === 'signal-endpoint') {
|
||||
const signalEndpoint = signalEndpoints.find(
|
||||
({ id }) => id === sourceId,
|
||||
);
|
||||
|
||||
if (signalEndpoint) {
|
||||
return (
|
||||
<TextCell>
|
||||
<StyledCell>
|
||||
<HtmlTooltip title='Signal endpoint' arrow>
|
||||
<StyledIcon alt='Signal endpoint' variant='rounded'>
|
||||
<SignalsIcon />
|
||||
</StyledIcon>
|
||||
</HtmlTooltip>
|
||||
<StyledLink
|
||||
component={RouterLink}
|
||||
to='/integrations/signals'
|
||||
underline='hover'
|
||||
>
|
||||
{signalEndpoint.name}
|
||||
</StyledLink>
|
||||
</StyledCell>
|
||||
</TextCell>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const sourceIcon =
|
||||
source === 'signal-endpoint' ? (
|
||||
<HtmlTooltip title='Signal endpoint' arrow>
|
||||
<StyledIcon alt='Signal endpoint' variant='rounded'>
|
||||
<SignalsIcon />
|
||||
</StyledIcon>
|
||||
</HtmlTooltip>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<TextCell>
|
||||
<StyledCell>
|
||||
{sourceIcon}
|
||||
<StyledLink
|
||||
component={RouterLink}
|
||||
to='/integrations/signals'
|
||||
underline='hover'
|
||||
>
|
||||
{trigger.name}
|
||||
</StyledLink>
|
||||
</StyledCell>
|
||||
</TextCell>
|
||||
);
|
||||
return <TextCell>No source</TextCell>;
|
||||
};
|
@ -13,10 +13,10 @@ 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 { ProjectActionsSourceCell } from './ProjectActionsSourceCell';
|
||||
import { ProjectActionsFiltersCell } from './ProjectActionsFiltersCell';
|
||||
import { ProjectActionsActorCell } from './ProjectActionsActorCell';
|
||||
import { ProjectActionsActionsCell } from './ProjectActionsActionsCell';
|
||||
import { ProjectActionsActionsCell } from './ProjectActionsActionsCell/ProjectActionsActionsCell';
|
||||
import { ProjectActionsTableActionsCell } from './ProjectActionsTableActionsCell';
|
||||
import { ProjectActionsModal } from './ProjectActionsModal/ProjectActionsModal';
|
||||
import { ProjectActionsDeleteDialog } from './ProjectActionsDeleteDialog';
|
||||
@ -96,6 +96,7 @@ export const ProjectActionsTable = ({
|
||||
}: { row: { original: IActionSet } }) => (
|
||||
<LinkCell
|
||||
title={action.name}
|
||||
subtitle={action.description}
|
||||
onClick={() => {
|
||||
setSelectedAction(action);
|
||||
setModalOpen(true);
|
||||
@ -104,12 +105,12 @@ export const ProjectActionsTable = ({
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'trigger',
|
||||
Header: 'Trigger',
|
||||
id: 'source',
|
||||
Header: 'Source',
|
||||
Cell: ({
|
||||
row: { original: action },
|
||||
}: { row: { original: IActionSet } }) => (
|
||||
<ProjectActionsTriggerCell
|
||||
<ProjectActionsSourceCell
|
||||
action={action}
|
||||
signalEndpoints={signalEndpoints}
|
||||
/>
|
||||
|
@ -9,6 +9,7 @@ export interface IActionSet {
|
||||
id: number;
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
description: string;
|
||||
project: string;
|
||||
actorId: number;
|
||||
match: IMatch;
|
||||
|
Loading…
Reference in New Issue
Block a user