mirror of
https://github.com/Unleash/unleash.git
synced 2025-03-04 00:18:40 +01:00
Suggest changes dialog (#2247)
* refactor: suggested changes folder structure * feat: add dialogue confirmation
This commit is contained in:
parent
2304ea1d1e
commit
ea2cf144f9
@ -10,6 +10,9 @@ import React from 'react';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
import { useStyles } from './FeatureOverviewEnvSwitch.styles';
|
||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { useSuggestToggle } from 'hooks/useSuggestToggle';
|
||||
import { SuggestChangesDialogue } from 'component/suggestChanges/SuggestChangeConfirmDialog/SuggestChangeConfirmDialog';
|
||||
|
||||
interface IFeatureOverviewEnvSwitchProps {
|
||||
env: IFeatureEnvironment;
|
||||
@ -31,6 +34,12 @@ const FeatureOverviewEnvSwitch = ({
|
||||
const { refetchFeature } = useFeature(projectId, featureId);
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
const { classes: styles } = useStyles();
|
||||
const { uiConfig } = useUiConfig();
|
||||
const {
|
||||
onSuggestToggle,
|
||||
onSuggestToggleClose,
|
||||
suggestChangesDialogDetails,
|
||||
} = useSuggestToggle();
|
||||
|
||||
const handleToggleEnvironmentOn = async () => {
|
||||
try {
|
||||
@ -74,6 +83,11 @@ const FeatureOverviewEnvSwitch = ({
|
||||
};
|
||||
|
||||
const toggleEnvironment = async (e: React.ChangeEvent) => {
|
||||
if (uiConfig?.flags?.suggestChanges && env.name === 'production') {
|
||||
e.preventDefault();
|
||||
onSuggestToggle(featureId, env.name, env.enabled);
|
||||
return;
|
||||
}
|
||||
if (env.enabled) {
|
||||
await handleToggleEnvironmentOff();
|
||||
return;
|
||||
@ -104,6 +118,13 @@ const FeatureOverviewEnvSwitch = ({
|
||||
/>
|
||||
{content}
|
||||
</label>
|
||||
<SuggestChangesDialogue
|
||||
isOpen={suggestChangesDialogDetails.isOpen}
|
||||
onClose={onSuggestToggleClose}
|
||||
featureName={featureId}
|
||||
environment={suggestChangesDialogDetails?.environment}
|
||||
onConfirm={() => {}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -24,8 +24,8 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { Routes, Route, useLocation } from 'react-router-dom';
|
||||
import { DeleteProjectDialogue } from './DeleteProject/DeleteProjectDialogue';
|
||||
import { ProjectLog } from './ProjectLog/ProjectLog';
|
||||
import { SuggestedChangeOverview } from './SuggestedChanges/SuggestedChangeOverview/SuggestedChangeOverview';
|
||||
import { DraftBanner } from './SuggestedChanges/DraftBanner/DraftBanner';
|
||||
import { SuggestedChangeOverview } from 'component/suggestChanges/SuggestedChangeOverview/SuggestedChangeOverview';
|
||||
import { DraftBanner } from 'component/suggestChanges/DraftBanner/DraftBanner';
|
||||
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
|
||||
|
||||
const StyledDiv = styled('div')(() => ({
|
||||
|
@ -36,6 +36,8 @@ import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/Feat
|
||||
import { useSearch } from 'hooks/useSearch';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
import { Search } from 'component/common/Search/Search';
|
||||
import { useSuggestToggle } from 'hooks/useSuggestToggle';
|
||||
import { SuggestChangesDialogue } from 'component/suggestChanges/SuggestChangeConfirmDialog/SuggestChangeConfirmDialog';
|
||||
|
||||
interface IProjectFeatureTogglesProps {
|
||||
features: IProject['features'];
|
||||
@ -99,6 +101,11 @@ export const ProjectFeatureToggles = ({
|
||||
|
||||
const { toggleFeatureEnvironmentOn, toggleFeatureEnvironmentOff } =
|
||||
useFeatureApi();
|
||||
const {
|
||||
onSuggestToggle,
|
||||
onSuggestToggleClose,
|
||||
suggestChangesDialogDetails,
|
||||
} = useSuggestToggle();
|
||||
|
||||
const onToggle = useCallback(
|
||||
async (
|
||||
@ -107,6 +114,13 @@ export const ProjectFeatureToggles = ({
|
||||
environment: string,
|
||||
enabled: boolean
|
||||
) => {
|
||||
if (
|
||||
uiConfig?.flags?.suggestChanges &&
|
||||
environment === 'production'
|
||||
) {
|
||||
onSuggestToggle(featureName, environment, enabled);
|
||||
throw new Error('Additional approval required');
|
||||
}
|
||||
try {
|
||||
if (enabled) {
|
||||
await toggleFeatureEnvironmentOn(
|
||||
@ -501,6 +515,13 @@ export const ProjectFeatureToggles = ({
|
||||
}}
|
||||
featureId={featureArchiveState || ''}
|
||||
projectId={projectId}
|
||||
/>{' '}
|
||||
<SuggestChangesDialogue
|
||||
isOpen={suggestChangesDialogDetails.isOpen}
|
||||
onClose={onSuggestToggleClose}
|
||||
featureName={suggestChangesDialogDetails?.featureName}
|
||||
environment={suggestChangesDialogDetails?.environment}
|
||||
onConfirm={() => {}}
|
||||
/>
|
||||
</PageContent>
|
||||
);
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { VFC } from 'react';
|
||||
import { ISuggestChange } from 'interfaces/suggestChangeset';
|
||||
import { Box } from '@mui/system';
|
||||
import { PlaygroundResultChip } from 'component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip'; // FIXME: refactor - extract to common
|
||||
|
||||
export const ChangeItem: VFC<ISuggestChange> = ({ action, id, payload }) => {
|
||||
if (action === 'updateEnabled') {
|
||||
return (
|
||||
<Box key={id}>
|
||||
New status:{' '}
|
||||
<PlaygroundResultChip
|
||||
showIcon={false}
|
||||
label={payload ? 'Enabled' : 'Disabled'}
|
||||
enabled={Boolean(payload)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return <Box key={id}>Change with ID: {id}</Box>;
|
||||
};
|
@ -1,83 +0,0 @@
|
||||
import { VFC } from 'react';
|
||||
import { Box, Typography, Card, styled } from '@mui/material';
|
||||
import { ISuggestChange } from 'interfaces/suggestChangeset';
|
||||
import EnvironmentIcon from 'component/common/EnvironmentIcon/EnvironmentIcon';
|
||||
import StringTruncator from 'component/common/StringTruncator/StringTruncator';
|
||||
import { PlaygroundResultChip } from 'component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip'; // FIXME: refactor - extract to common
|
||||
import { ChangeItem } from './ChangeItem/ChangeItem';
|
||||
|
||||
type ChangesetDiffProps = {
|
||||
changes?: ISuggestChange[];
|
||||
state: string;
|
||||
};
|
||||
|
||||
const StyledHeader = styled('div')(({ theme }) => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
[theme.breakpoints.down(560)]: {
|
||||
flexDirection: 'column',
|
||||
textAlign: 'center',
|
||||
},
|
||||
paddingBottom: theme.spacing(1),
|
||||
}));
|
||||
|
||||
export const ChangesetDiff: VFC<ChangesetDiffProps> = ({ changes, state }) => (
|
||||
<Box
|
||||
sx={{
|
||||
p: 3,
|
||||
border: '2px solid',
|
||||
borderColor: theme => theme.palette.playgroundBackground,
|
||||
display: 'flex',
|
||||
gap: 2,
|
||||
flexDirection: 'column',
|
||||
borderRadius: theme => `${theme.shape.borderRadiusExtraLarge}px`,
|
||||
}}
|
||||
>
|
||||
<StyledHeader>
|
||||
<EnvironmentIcon enabled={true} />
|
||||
<Box>
|
||||
<StringTruncator
|
||||
text={`production`}
|
||||
maxWidth="100"
|
||||
maxLength={15}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ ml: 'auto' }}>
|
||||
<PlaygroundResultChip
|
||||
showIcon={false}
|
||||
label={state === 'CREATED' ? 'Draft mode' : '???'}
|
||||
enabled="unknown"
|
||||
/>
|
||||
</Box>
|
||||
</StyledHeader>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
You request changes for these feature toggles:
|
||||
</Typography>
|
||||
{/* TODO: group by feature name */}
|
||||
{changes?.map(item => (
|
||||
<Card
|
||||
key={item.feature}
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: theme => `${theme.shape.borderRadius}px`,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid',
|
||||
borderColor: theme => theme.palette.dividerAlternative,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: theme =>
|
||||
theme.palette.tableHeaderBackground,
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Typography>{item.feature}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<ChangeItem {...item} />
|
||||
</Box>
|
||||
</Card>
|
||||
))}
|
||||
</Box>
|
||||
);
|
@ -1,90 +0,0 @@
|
||||
import { FC } from 'react';
|
||||
import { Box, Paper } from '@mui/material';
|
||||
import { SuggestedFeatureToggleChange } from '../SuggestedFeatureToggleChange/SuggestedFeatureToggleChange';
|
||||
import { objectId } from '../../../../../../utils/objectId';
|
||||
import { ConditionallyRender } from '../../../../../common/ConditionallyRender/ConditionallyRender';
|
||||
import { ToggleStatusChange } from '../SuggestedFeatureToggleChange/ToggleStatusChange';
|
||||
import {
|
||||
StrategyAddedChange,
|
||||
StrategyDeletedChange,
|
||||
StrategyEditedChange,
|
||||
} from '../SuggestedFeatureToggleChange/StrategyChange';
|
||||
import {
|
||||
formatStrategyName,
|
||||
GetFeatureStrategyIcon,
|
||||
} from '../../../../../../utils/strategyNames';
|
||||
|
||||
export const SuggestedChangeSet: FC<{ suggestedChange: any }> = ({
|
||||
suggestedChange,
|
||||
}) => {
|
||||
return (
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={theme => ({
|
||||
marginTop: theme.spacing(2),
|
||||
marginLeft: theme.spacing(2),
|
||||
width: '70%',
|
||||
padding: 2,
|
||||
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
sx={theme => ({
|
||||
padding: theme.spacing(2),
|
||||
})}
|
||||
>
|
||||
Changes
|
||||
{suggestedChange.changes?.map((featureToggleChange: any) => (
|
||||
<SuggestedFeatureToggleChange
|
||||
key={featureToggleChange.feature}
|
||||
featureToggleName={featureToggleChange.feature}
|
||||
>
|
||||
{featureToggleChange.changeSet.map((change: any) => (
|
||||
<Box key={objectId(change)}>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
change.action === 'updateEnabled'
|
||||
}
|
||||
show={
|
||||
<ToggleStatusChange
|
||||
enabled={
|
||||
change?.payload?.data?.data
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={change.action === 'addStrategy'}
|
||||
show={
|
||||
<StrategyAddedChange>
|
||||
<GetFeatureStrategyIcon
|
||||
strategyName={
|
||||
change.payload.name
|
||||
}
|
||||
/>
|
||||
{formatStrategyName(
|
||||
change.payload.name
|
||||
)}
|
||||
</StrategyAddedChange>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
change.action === 'deleteStrategy'
|
||||
}
|
||||
show={<StrategyDeletedChange />}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={
|
||||
change.action === 'updateStrategy'
|
||||
}
|
||||
show={<StrategyEditedChange />}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</SuggestedFeatureToggleChange>
|
||||
))}
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
@ -3,6 +3,7 @@ import { Box, Button, Typography } from '@mui/material';
|
||||
import { useStyles as useAppStyles } from 'component/App.styles';
|
||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { SuggestedChangesSidebar } from '../SuggestedChangesSidebar/SuggestedChangesSidebar';
|
||||
|
||||
interface IDraftBannerProps {
|
||||
environment?: string;
|
||||
@ -10,7 +11,7 @@ interface IDraftBannerProps {
|
||||
|
||||
export const DraftBanner: VFC<IDraftBannerProps> = ({ environment }) => {
|
||||
const { classes } = useAppStyles();
|
||||
const [reviewChangesOpen, setReviewChangesOpen] = useState(false);
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -49,7 +50,9 @@ export const DraftBanner: VFC<IDraftBannerProps> = ({ environment }) => {
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setReviewChangesOpen(true)}
|
||||
onClick={() => {
|
||||
setIsSidebarOpen(true);
|
||||
}}
|
||||
sx={{ ml: 'auto' }}
|
||||
>
|
||||
Review changes
|
||||
@ -59,6 +62,12 @@ export const DraftBanner: VFC<IDraftBannerProps> = ({ environment }) => {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
<SuggestedChangesSidebar
|
||||
open={isSidebarOpen}
|
||||
onClose={() => {
|
||||
setIsSidebarOpen(false);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -0,0 +1,58 @@
|
||||
import { FC } from 'react';
|
||||
import { Alert, Box, Typography } from '@mui/material';
|
||||
import { Dialogue } from 'component/common/Dialogue/Dialogue';
|
||||
import useToast from 'hooks/useToast';
|
||||
import { formatUnknownError } from 'utils/formatUnknownError';
|
||||
|
||||
interface ISuggestChangesDialogueProps {
|
||||
isOpen: boolean;
|
||||
onConfirm: () => void;
|
||||
onClose: () => void;
|
||||
featureName?: string;
|
||||
environment?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export const SuggestChangesDialogue: FC<ISuggestChangesDialogueProps> = ({
|
||||
isOpen,
|
||||
onConfirm,
|
||||
onClose,
|
||||
enabled,
|
||||
featureName,
|
||||
environment,
|
||||
}) => {
|
||||
const { setToastData, setToastApiError } = useToast();
|
||||
|
||||
const onSuggestClick = async () => {
|
||||
try {
|
||||
alert('Suggesting changes');
|
||||
onConfirm();
|
||||
} catch (error: unknown) {
|
||||
setToastApiError(formatUnknownError(error));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialogue
|
||||
open={isOpen}
|
||||
primaryButtonText="Add to draft"
|
||||
secondaryButtonText="Cancel"
|
||||
onClick={onSuggestClick}
|
||||
onClose={onClose}
|
||||
title="Suggest changes"
|
||||
>
|
||||
<Alert severity="info" sx={{ mb: 2 }}>
|
||||
Suggest changes is enabled for {environment}. Your changes needs
|
||||
to be approved before they will be live. All the changes you do
|
||||
now will be added into a draft that you can submit for review.
|
||||
</Alert>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Suggested changes:
|
||||
</Typography>
|
||||
<Typography>
|
||||
<strong>{enabled ? 'Disable' : 'Enable'}</strong> feature toggle{' '}
|
||||
<strong>{featureName}</strong> in <strong>{environment}</strong>
|
||||
</Typography>
|
||||
</Dialogue>
|
||||
);
|
||||
};
|
@ -1,7 +1,7 @@
|
||||
import { FC } from 'react';
|
||||
import { Avatar, Box, Card, Paper, Typography } from '@mui/material';
|
||||
import { StyledTrueChip } from '../../../../../playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip';
|
||||
import { ReactComponent as ChangesAppliedIcon } from '../../../../../../assets/icons/merge.svg';
|
||||
import { PlaygroundResultChip } from 'component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip';
|
||||
import { ReactComponent as ChangesAppliedIcon } from 'assets/icons/merge.svg';
|
||||
import TimeAgo from 'react-timeago';
|
||||
|
||||
export const SuggestedChangeHeader: FC<{ suggestedChange: any }> = ({
|
||||
@ -35,9 +35,10 @@ export const SuggestedChangeHeader: FC<{ suggestedChange: any }> = ({
|
||||
#{suggestedChange.id}
|
||||
</Typography>
|
||||
</Typography>
|
||||
<StyledTrueChip
|
||||
icon={<ChangesAppliedIcon strokeWidth="0.25" />}
|
||||
<PlaygroundResultChip
|
||||
// icon={<ChangesAppliedIcon strokeWidth="0.25" />}
|
||||
label="Changes applied"
|
||||
enabled="unknown"
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', verticalAlign: 'center', gap: 2 }}>
|
@ -1,10 +1,10 @@
|
||||
import { FC } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { Box, Paper } from '@mui/material';
|
||||
import { useSuggestedChange } from 'hooks/api/getters/useSuggestChange/useSuggestedChange';
|
||||
import { SuggestedChangeHeader } from './SuggestedChangeHeader/SuggestedChangeHeader';
|
||||
import { SuggestedChangeTimeline } from './SuggestedChangeTimeline/SuggestedChangeTimeline';
|
||||
import { SuggestedChangeReviewers } from './SuggestedChangeReviewers/SuggestedChangeReviewers';
|
||||
import { SuggestedChangeSet } from './SuggestedChangeSet/SuggestedChangeSet';
|
||||
import { SuggestedChangeset } from '../SuggestedChangeset/SuggestedChangeset';
|
||||
|
||||
export const SuggestedChangeOverview: FC = () => {
|
||||
const { data: suggestedChange } = useSuggestedChange();
|
||||
@ -23,8 +23,25 @@ export const SuggestedChangeOverview: FC = () => {
|
||||
<SuggestedChangeTimeline />
|
||||
<SuggestedChangeReviewers />
|
||||
</Box>
|
||||
|
||||
<SuggestedChangeSet suggestedChange={suggestedChange} />
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={theme => ({
|
||||
marginTop: theme.spacing(2),
|
||||
marginLeft: theme.spacing(2),
|
||||
width: '70%',
|
||||
padding: 2,
|
||||
borderRadius: theme =>
|
||||
`${theme.shape.borderRadiusLarge}px`,
|
||||
})}
|
||||
>
|
||||
<Box
|
||||
sx={theme => ({
|
||||
padding: theme.spacing(2),
|
||||
})}
|
||||
>
|
||||
<SuggestedChangeset suggestedChange={suggestedChange} />
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
</>
|
||||
);
|
@ -1,6 +1,6 @@
|
||||
import { Box } from '@mui/material';
|
||||
import { FC } from 'react';
|
||||
import { PlaygroundResultChip } from '../../../../../playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip';
|
||||
import { PlaygroundResultChip } from 'component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip';
|
||||
|
||||
export const ToggleStatusChange: FC<{ enabled: boolean }> = ({ enabled }) => {
|
||||
return (
|
@ -0,0 +1,135 @@
|
||||
import React, { useState, VFC } from 'react';
|
||||
import { Box, Button, Typography, styled, Tooltip } from '@mui/material';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
||||
import { PageContent } from 'component/common/PageContent/PageContent';
|
||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
|
||||
import { HelpOutline } from '@mui/icons-material';
|
||||
import { useSuggestedChange } from 'hooks/api/getters/useSuggestChange/useSuggestedChange';
|
||||
import { SuggestedChangeset } from '../SuggestedChangeset/SuggestedChangeset';
|
||||
interface ISuggestedChangesSidebarProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
const StyledPageContent = styled(PageContent)(({ theme }) => ({
|
||||
height: '100vh',
|
||||
overflow: 'auto',
|
||||
padding: theme.spacing(7.5, 6),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(4, 2),
|
||||
},
|
||||
'& .header': {
|
||||
padding: theme.spacing(0, 0, 2, 0),
|
||||
},
|
||||
'& .body': {
|
||||
padding: theme.spacing(3, 0, 0, 0),
|
||||
},
|
||||
borderRadius: `${theme.spacing(1.5, 0, 0, 1.5)} !important`,
|
||||
}));
|
||||
const StyledHelpOutline = styled(HelpOutline)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
marginLeft: '0.3rem',
|
||||
color: theme.palette.grey[700],
|
||||
}));
|
||||
const StyledHeaderHint = styled('div')(({ theme }) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
|
||||
export const SuggestedChangesSidebar: VFC<ISuggestedChangesSidebarProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
}) => {
|
||||
const { data: suggestedChange } = useSuggestedChange();
|
||||
|
||||
const onReview = async () => {
|
||||
console.log('approve');
|
||||
};
|
||||
const onDiscard = async () => {
|
||||
console.log('discard');
|
||||
};
|
||||
const onApply = async () => {
|
||||
try {
|
||||
console.log('apply');
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<SidebarModal open={open} onClose={onClose} label="Review changes">
|
||||
<StyledPageContent
|
||||
header={
|
||||
<PageHeader
|
||||
secondary
|
||||
titleElement={
|
||||
<>
|
||||
Review your changes
|
||||
<Tooltip
|
||||
title="You can review your changes from this page.
|
||||
Needs a text to explain the process."
|
||||
arrow
|
||||
>
|
||||
<StyledHelpOutline />
|
||||
</Tooltip>
|
||||
<StyledHeaderHint>
|
||||
Make sure you are sending the right changes
|
||||
suggestions to be reviewed
|
||||
</StyledHeaderHint>
|
||||
</>
|
||||
}
|
||||
></PageHeader>
|
||||
}
|
||||
>
|
||||
{/* TODO: multiple environments (changesets) */}
|
||||
<Typography>{suggestedChange?.state}</Typography>
|
||||
<br />
|
||||
<SuggestedChangeset suggestedChange={suggestedChange} />
|
||||
<Box sx={{ display: 'flex' }}>
|
||||
<ConditionallyRender
|
||||
condition={suggestedChange?.state === 'APPROVED'}
|
||||
show={<Typography>Applied</Typography>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={suggestedChange?.state === 'CLOSED'}
|
||||
show={<Typography>Applied</Typography>}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={suggestedChange?.state === 'APPROVED'}
|
||||
show={
|
||||
<>
|
||||
<Button
|
||||
sx={{ mt: 2 }}
|
||||
variant="contained"
|
||||
onClick={onApply}
|
||||
>
|
||||
Apply changes
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={suggestedChange?.state === 'CREATED'}
|
||||
show={
|
||||
<>
|
||||
<Button
|
||||
sx={{ mt: 2, ml: 'auto' }}
|
||||
variant="contained"
|
||||
onClick={onReview}
|
||||
>
|
||||
Request changes
|
||||
</Button>
|
||||
<Button
|
||||
sx={{ mt: 2, ml: 2 }}
|
||||
variant="outlined"
|
||||
onClick={onDiscard}
|
||||
>
|
||||
Discard changes
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</StyledPageContent>
|
||||
</SidebarModal>
|
||||
);
|
||||
};
|
@ -0,0 +1,65 @@
|
||||
import { FC } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { SuggestedFeatureToggleChange } from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/SuggestedFeatureToggleChange';
|
||||
import { objectId } from 'utils/objectId';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
import { ToggleStatusChange } from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/ToggleStatusChange';
|
||||
import {
|
||||
StrategyAddedChange,
|
||||
StrategyDeletedChange,
|
||||
StrategyEditedChange,
|
||||
} from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/StrategyChange';
|
||||
import {
|
||||
formatStrategyName,
|
||||
GetFeatureStrategyIcon,
|
||||
} from 'utils/strategyNames';
|
||||
|
||||
export const SuggestedChangeset: FC<{ suggestedChange: any }> = ({
|
||||
suggestedChange,
|
||||
}) => {
|
||||
return (
|
||||
<Box>
|
||||
Changes
|
||||
{suggestedChange.changes?.map((featureToggleChange: any) => (
|
||||
<SuggestedFeatureToggleChange
|
||||
key={featureToggleChange.feature}
|
||||
featureToggleName={featureToggleChange.feature}
|
||||
>
|
||||
{featureToggleChange.changeSet.map((change: any) => (
|
||||
<Box key={objectId(change)}>
|
||||
<ConditionallyRender
|
||||
condition={change.action === 'updateEnabled'}
|
||||
show={
|
||||
<ToggleStatusChange
|
||||
enabled={change?.payload?.data?.data}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={change.action === 'addStrategy'}
|
||||
show={
|
||||
<StrategyAddedChange>
|
||||
<GetFeatureStrategyIcon
|
||||
strategyName={change.payload.name}
|
||||
/>
|
||||
{formatStrategyName(
|
||||
change.payload.name
|
||||
)}
|
||||
</StrategyAddedChange>
|
||||
}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={change.action === 'deleteStrategy'}
|
||||
show={<StrategyDeletedChange />}
|
||||
/>
|
||||
<ConditionallyRender
|
||||
condition={change.action === 'updateStrategy'}
|
||||
show={<StrategyEditedChange />}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</SuggestedFeatureToggleChange>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
33
frontend/src/hooks/useSuggestToggle.ts
Normal file
33
frontend/src/hooks/useSuggestToggle.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export const useSuggestToggle = () => {
|
||||
const [suggestChangesDialogDetails, setSuggestChangesDialogDetails] =
|
||||
useState<{
|
||||
enabled?: boolean;
|
||||
featureName?: string;
|
||||
environment?: string;
|
||||
isOpen: boolean;
|
||||
}>({ isOpen: false });
|
||||
|
||||
const onSuggestToggle = useCallback(
|
||||
(featureName: string, environment: string, enabled: boolean) => {
|
||||
setSuggestChangesDialogDetails({
|
||||
featureName,
|
||||
environment,
|
||||
enabled,
|
||||
isOpen: true,
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const onSuggestToggleClose = useCallback(() => {
|
||||
setSuggestChangesDialogDetails({ isOpen: false });
|
||||
}, []);
|
||||
|
||||
return {
|
||||
onSuggestToggle,
|
||||
onSuggestToggleClose,
|
||||
suggestChangesDialogDetails,
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user