1
0
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:
Mateusz Kwasniewski 2022-11-04 12:52:47 +01:00 committed by GitHub
parent 4b281d9513
commit 065833e5d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 76 deletions

View File

@ -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>

View File

@ -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>
); );
}; };

View File

@ -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>
); );
}; };

View File

@ -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>
); );
}; };

View File

@ -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>
); );

View File

@ -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;

View File

@ -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));
} }