mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-08 01:15:49 +02:00
feat: display change details (#2327)
* feat: display change details * refactor: reorganize components * feat: display deleted strategy name if present * feat: UI tweaks * fix: types * refactor: remove unnecessary checks for types
This commit is contained in:
parent
4b281d9513
commit
065833e5d1
@ -2,15 +2,11 @@ import { VFC } from 'react';
|
|||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange';
|
import { ChangeRequestFeatureToggleChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ChangeRequestFeatureToggleChange';
|
||||||
import { objectId } from 'utils/objectId';
|
import { objectId } from 'utils/objectId';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange';
|
import { ToggleStatusChange } from '../ChangeRequestOverview/ChangeRequestFeatureToggleChange/ToggleStatusChange';
|
||||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||||
import useToast from 'hooks/useToast';
|
import useToast from 'hooks/useToast';
|
||||||
import type {
|
import type { IChangeRequest } from '../changeRequest.types';
|
||||||
IChangeRequest,
|
|
||||||
IChangeRequestAddStrategy,
|
|
||||||
} from '../changeRequest.types';
|
|
||||||
import {
|
import {
|
||||||
StrategyAddedChange,
|
StrategyAddedChange,
|
||||||
StrategyDeletedChange,
|
StrategyDeletedChange,
|
||||||
@ -20,7 +16,7 @@ import {
|
|||||||
formatStrategyName,
|
formatStrategyName,
|
||||||
GetFeatureStrategyIcon,
|
GetFeatureStrategyIcon,
|
||||||
} from 'utils/strategyNames';
|
} from 'utils/strategyNames';
|
||||||
import { IChangeRequestEnabled } from '../changeRequest.types';
|
import { hasNameField } from '../changeRequest.types';
|
||||||
|
|
||||||
interface IChangeRequestProps {
|
interface IChangeRequestProps {
|
||||||
changeRequest: IChangeRequest;
|
changeRequest: IChangeRequest;
|
||||||
@ -62,46 +58,60 @@ export const ChangeRequest: VFC<IChangeRequestProps> = ({
|
|||||||
onNavigate={onNavigate}
|
onNavigate={onNavigate}
|
||||||
>
|
>
|
||||||
{featureToggleChange.changes.map(change => (
|
{featureToggleChange.changes.map(change => (
|
||||||
<Box key={objectId(change)}>
|
<Box
|
||||||
<ConditionallyRender
|
key={objectId(change)}
|
||||||
condition={change.action === 'updateEnabled'}
|
sx={theme => ({
|
||||||
show={
|
padding: 2,
|
||||||
|
borderTop: '1px solid',
|
||||||
|
borderColor: theme =>
|
||||||
|
theme.palette.dividerAlternative,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{change.action === 'updateEnabled' && (
|
||||||
<ToggleStatusChange
|
<ToggleStatusChange
|
||||||
enabled={
|
enabled={change.payload.enabled}
|
||||||
(change as IChangeRequestEnabled)
|
onDiscard={onDiscard(change.id)}
|
||||||
?.payload?.enabled
|
|
||||||
}
|
|
||||||
onDiscard={onDiscard(change.id!)}
|
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
|
{change.action === 'addStrategy' && (
|
||||||
|
<StrategyAddedChange
|
||||||
|
onDiscard={onDiscard(change.id)}
|
||||||
|
>
|
||||||
|
<GetFeatureStrategyIcon
|
||||||
|
strategyName={change.payload.name}
|
||||||
/>
|
/>
|
||||||
<ConditionallyRender
|
|
||||||
condition={change.action === 'addStrategy'}
|
{formatStrategyName(change.payload.name)}
|
||||||
show={
|
</StrategyAddedChange>
|
||||||
<StrategyAddedChange>
|
)}
|
||||||
|
{change.action === 'deleteStrategy' && (
|
||||||
|
<StrategyDeletedChange
|
||||||
|
onDiscard={onDiscard(change.id)}
|
||||||
|
>
|
||||||
|
{hasNameField(change.payload) && (
|
||||||
|
<>
|
||||||
<GetFeatureStrategyIcon
|
<GetFeatureStrategyIcon
|
||||||
strategyName={
|
strategyName={
|
||||||
(
|
change.payload.name
|
||||||
change as IChangeRequestAddStrategy
|
|
||||||
)?.payload.name!
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{formatStrategyName(
|
{formatStrategyName(
|
||||||
(
|
change.payload.name
|
||||||
change as IChangeRequestAddStrategy
|
|
||||||
)?.payload.name!
|
|
||||||
)}
|
)}
|
||||||
</StrategyAddedChange>
|
</>
|
||||||
}
|
)}
|
||||||
/>
|
</StrategyDeletedChange>
|
||||||
<ConditionallyRender
|
)}
|
||||||
condition={change.action === 'deleteStrategy'}
|
{change.action === 'updateStrategy' && (
|
||||||
show={<StrategyDeletedChange />}
|
<StrategyEditedChange
|
||||||
/>
|
onDiscard={onDiscard(change.id)}
|
||||||
<ConditionallyRender
|
>
|
||||||
condition={change.action === 'updateStrategy'}
|
<GetFeatureStrategyIcon
|
||||||
show={<StrategyEditedChange />}
|
strategyName={change.payload.name}
|
||||||
/>
|
/>
|
||||||
|
{formatStrategyName(change.payload.name)}
|
||||||
|
</StrategyEditedChange>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</ChangeRequestFeatureToggleChange>
|
</ChangeRequestFeatureToggleChange>
|
||||||
|
@ -26,7 +26,7 @@ export const ChangeRequestFeatureToggleChange: FC<
|
|||||||
<Box
|
<Box
|
||||||
sx={theme => ({
|
sx={theme => ({
|
||||||
backgroundColor: theme.palette.tableHeaderBackground,
|
backgroundColor: theme.palette.tableHeaderBackground,
|
||||||
p: 2,
|
padding: theme.spacing(3),
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
@ -42,7 +42,7 @@ export const ChangeRequestFeatureToggleChange: FC<
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ p: 2 }}>{children}</Box>
|
<Box>{children}</Box>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,27 +1,74 @@
|
|||||||
import { Box, Typography } from '@mui/material';
|
import { Box, Link, styled, Typography } from '@mui/material';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
|
||||||
export const StrategyAddedChange: FC = ({ children }) => {
|
interface IStrategyChangeProps {
|
||||||
|
onDiscard: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ChangeItemWrapper = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ChangeItemInfo: FC = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const Discard: FC<IStrategyChangeProps> = ({ onDiscard }) => (
|
||||||
|
<Box>
|
||||||
|
<Link onClick={onDiscard}>Discard</Link>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const StrategyAddedChange: FC<IStrategyChangeProps> = ({
|
||||||
|
children,
|
||||||
|
onDiscard,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 1, display: 'flex', gap: 1 }}>
|
<ChangeItemWrapper>
|
||||||
<Typography sx={theme => ({ color: theme.palette.success.main })}>
|
<ChangeItemInfo>
|
||||||
+ Strategy Added:
|
<Typography
|
||||||
|
sx={theme => ({ color: theme.palette.success.main })}
|
||||||
|
>
|
||||||
|
+ Adding strategy:
|
||||||
</Typography>
|
</Typography>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</ChangeItemInfo>
|
||||||
|
<Discard onDiscard={onDiscard} />
|
||||||
|
</ChangeItemWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StrategyEditedChange: FC = () => {
|
export const StrategyEditedChange: FC<IStrategyChangeProps> = ({
|
||||||
return <Box sx={{ p: 1 }}>Strategy Edited</Box>;
|
children,
|
||||||
};
|
onDiscard,
|
||||||
|
}) => {
|
||||||
export const StrategyDeletedChange: FC = () => {
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 1 }}>
|
<ChangeItemWrapper>
|
||||||
<Typography sx={theme => ({ color: theme.palette.error.main })}>
|
<ChangeItemInfo>
|
||||||
- Strategy Deleted
|
<Typography>Editing strategy:</Typography>
|
||||||
</Typography>
|
{children}
|
||||||
</Box>
|
</ChangeItemInfo>
|
||||||
|
<Discard onDiscard={onDiscard} />
|
||||||
|
</ChangeItemWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StrategyDeletedChange: FC<IStrategyChangeProps> = ({
|
||||||
|
onDiscard,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<ChangeItemWrapper>
|
||||||
|
<ChangeItemInfo>
|
||||||
|
<Typography sx={theme => ({ color: theme.palette.error.main })}>
|
||||||
|
- Deleting strategy
|
||||||
|
</Typography>
|
||||||
|
{children}
|
||||||
|
</ChangeItemInfo>
|
||||||
|
<Discard onDiscard={onDiscard} />
|
||||||
|
</ChangeItemWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,7 @@ import { VFC } from 'react';
|
|||||||
import { Link, Box } from '@mui/material';
|
import { Link, Box } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { Badge } from 'component/common/Badge/Badge';
|
import { Badge } from 'component/common/Badge/Badge';
|
||||||
|
import { ChangeItemWrapper } from './StrategyChange';
|
||||||
|
|
||||||
interface IPlaygroundResultsTable {
|
interface IPlaygroundResultsTable {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -13,7 +14,7 @@ export const ToggleStatusChange: VFC<IPlaygroundResultsTable> = ({
|
|||||||
onDiscard,
|
onDiscard,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 1, display: 'flex', justifyContent: 'space-between' }}>
|
<ChangeItemWrapper>
|
||||||
<Box>
|
<Box>
|
||||||
New status:{' '}
|
New status:{' '}
|
||||||
<Badge color={enabled ? 'success' : 'error'}>
|
<Badge color={enabled ? 'success' : 'error'}>
|
||||||
@ -28,6 +29,6 @@ export const ToggleStatusChange: VFC<IPlaygroundResultsTable> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</ChangeItemWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
styled,
|
styled,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Divider,
|
Divider,
|
||||||
|
IconButton,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||||
@ -17,6 +18,7 @@ import { ChangeRequest } from '../ChangeRequest/ChangeRequest';
|
|||||||
import { useChangeRequestOpen } from 'hooks/api/getters/useChangeRequestOpen/useChangeRequestOpen';
|
import { useChangeRequestOpen } from 'hooks/api/getters/useChangeRequestOpen/useChangeRequestOpen';
|
||||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||||
import { ChangeRequestStatusBadge } from '../ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
import { ChangeRequestStatusBadge } from '../ChangeRequestStatusBadge/ChangeRequestStatusBadge';
|
||||||
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
|
||||||
interface IChangeRequestSidebarProps {
|
interface IChangeRequestSidebarProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -112,8 +114,19 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
|||||||
header={
|
header={
|
||||||
<PageHeader
|
<PageHeader
|
||||||
secondary
|
secondary
|
||||||
|
actions={
|
||||||
|
<IconButton onClick={onClose}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
}
|
||||||
titleElement={
|
titleElement={
|
||||||
<>
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
Review your changes
|
Review your changes
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title="You can review your changes from this page.
|
title="You can review your changes from this page.
|
||||||
@ -122,6 +135,7 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
|||||||
>
|
>
|
||||||
<StyledHelpOutline />
|
<StyledHelpOutline />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
<StyledHeaderHint>
|
<StyledHeaderHint>
|
||||||
Make sure you are sending the right changes
|
Make sure you are sending the right changes
|
||||||
to be reviewed
|
to be reviewed
|
||||||
@ -228,7 +242,6 @@ export const ChangeRequestSidebar: VFC<IChangeRequestSidebarProps> = ({
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
<BackButton onClick={onClose}>Close</BackButton>
|
|
||||||
</StyledPageContent>
|
</StyledPageContent>
|
||||||
</SidebarModal>
|
</SidebarModal>
|
||||||
);
|
);
|
||||||
|
@ -18,7 +18,7 @@ export interface IChangeRequestFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IChangeRequestBase {
|
export interface IChangeRequestBase {
|
||||||
id?: number;
|
id: number;
|
||||||
action: ChangeRequestAction;
|
action: ChangeRequestAction;
|
||||||
payload: ChangeRequestPayload;
|
payload: ChangeRequestPayload;
|
||||||
conflict?: string;
|
conflict?: string;
|
||||||
@ -83,3 +83,6 @@ export type ChangeRequestAction =
|
|||||||
| 'addStrategy'
|
| 'addStrategy'
|
||||||
| 'updateStrategy'
|
| 'updateStrategy'
|
||||||
| 'deleteStrategy';
|
| 'deleteStrategy';
|
||||||
|
|
||||||
|
export const hasNameField = (payload: unknown): payload is { name: string } =>
|
||||||
|
typeof payload === 'object' && payload !== null && 'name' in payload;
|
||||||
|
@ -15,6 +15,7 @@ import PermissionIconButton from 'component/common/PermissionIconButton/Permissi
|
|||||||
import { Delete } from '@mui/icons-material';
|
import { Delete } from '@mui/icons-material';
|
||||||
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
import { useChangeRequestApi } from 'hooks/api/actions/useChangeRequestApi/useChangeRequestApi';
|
||||||
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
||||||
|
import { useChangeRequestOpen } from 'hooks/api/getters/useChangeRequestOpen/useChangeRequestOpen';
|
||||||
|
|
||||||
interface IFeatureStrategyRemoveProps {
|
interface IFeatureStrategyRemoveProps {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@ -130,6 +131,7 @@ const useOnSuggestRemove = ({
|
|||||||
strategyId,
|
strategyId,
|
||||||
}: IRemoveProps) => {
|
}: IRemoveProps) => {
|
||||||
const { addChangeRequest } = useChangeRequestApi();
|
const { addChangeRequest } = useChangeRequestApi();
|
||||||
|
const { refetch: refetchChangeRequests } = useChangeRequestOpen(projectId);
|
||||||
const { setToastData, setToastApiError } = useToast();
|
const { setToastData, setToastApiError } = useToast();
|
||||||
const onSuggestRemove = async (event: React.FormEvent) => {
|
const onSuggestRemove = async (event: React.FormEvent) => {
|
||||||
try {
|
try {
|
||||||
@ -145,6 +147,7 @@ const useOnSuggestRemove = ({
|
|||||||
title: 'Changes added to the draft!',
|
title: 'Changes added to the draft!',
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
|
await refetchChangeRequests();
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
setToastApiError(formatUnknownError(error));
|
setToastApiError(formatUnknownError(error));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user