mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
Feat/apply changes (#2258)
* feat: add suggested change component * fix: build * feat: suggestion header * ui sketching different toggle changes * feat: strategy change sets UI tweaks * refactor: extract nested components Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
4aa1a34fef
commit
0dba973881
3
frontend/src/assets/icons/merge.svg
Normal file
3
frontend/src/assets/icons/merge.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="18" height="14" viewBox="0 0 18 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M0.59 2.00016L2 0.590157L5.41 4.00016L4 5.41016L0.59 2.00016ZM13 11.5002L13 8.00016L7.41 8.00016L2 13.4102L0.59 12.0002L6.59 6.00016L13 6.00016L13 2.50016L17.5 7.00016L13 11.5002Z" fill="currentColor"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 319 B |
@ -43,6 +43,7 @@ export const StyledTrueChip = styled(StyledChip)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
['& .MuiChip-icon']: {
|
['& .MuiChip-icon']: {
|
||||||
color: theme.palette.success.main,
|
color: theme.palette.success.main,
|
||||||
|
marginRight: 0,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|||||||
import { Routes, Route, useLocation } from 'react-router-dom';
|
import { Routes, Route, useLocation } from 'react-router-dom';
|
||||||
import { DeleteProjectDialogue } from './DeleteProject/DeleteProjectDialogue';
|
import { DeleteProjectDialogue } from './DeleteProject/DeleteProjectDialogue';
|
||||||
import { ProjectLog } from './ProjectLog/ProjectLog';
|
import { ProjectLog } from './ProjectLog/ProjectLog';
|
||||||
import { SuggestedChanges } from './SuggestedChanges/SuggestedChanges';
|
import { SuggestedChangeOverview } from './SuggestedChanges/SuggestedChangeOverview/SuggestedChangeOverview';
|
||||||
import { DraftBanner } from './SuggestedChanges/DraftBanner/DraftBanner';
|
import { DraftBanner } from './SuggestedChanges/DraftBanner/DraftBanner';
|
||||||
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
|
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
|
||||||
|
|
||||||
@ -224,6 +224,16 @@ const Project = () => {
|
|||||||
<Route path="environments" element={<ProjectEnvironment />} />
|
<Route path="environments" element={<ProjectEnvironment />} />
|
||||||
<Route path="archive" element={<ProjectFeaturesArchive />} />
|
<Route path="archive" element={<ProjectFeaturesArchive />} />
|
||||||
<Route path="logs" element={<ProjectLog />} />
|
<Route path="logs" element={<ProjectLog />} />
|
||||||
|
<Route
|
||||||
|
path="suggest-changes/:id"
|
||||||
|
element={
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={Boolean(uiConfig?.flags?.suggestChanges)}
|
||||||
|
show={<SuggestedChangeOverview />}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route path="*" element={<ProjectOverview />} />
|
<Route path="*" element={<ProjectOverview />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
@ -3,8 +3,6 @@ import { Box, Button, Typography } from '@mui/material';
|
|||||||
import { useStyles as useAppStyles } from 'component/App.styles';
|
import { useStyles as useAppStyles } from 'component/App.styles';
|
||||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import { EditGroupUsers } from '../../../../admin/groups/Group/EditGroupUsers/EditGroupUsers';
|
|
||||||
import { SuggestedChanges } from '../SuggestedChanges';
|
|
||||||
|
|
||||||
interface IDraftBannerProps {
|
interface IDraftBannerProps {
|
||||||
environment?: string;
|
environment?: string;
|
||||||
@ -61,10 +59,6 @@ export const DraftBanner: VFC<IDraftBannerProps> = ({ environment }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<SuggestedChanges
|
|
||||||
open={reviewChangesOpen}
|
|
||||||
setOpen={setReviewChangesOpen}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
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 TimeAgo from 'react-timeago';
|
||||||
|
|
||||||
|
export const SuggestedChangeHeader: FC<{ suggestedChange: any }> = ({
|
||||||
|
suggestedChange,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
sx={theme => ({
|
||||||
|
p: theme.spacing(2, 4),
|
||||||
|
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={theme => ({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 2,
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
variant="h1"
|
||||||
|
>
|
||||||
|
Suggestion
|
||||||
|
<Typography variant="h1" component="p">
|
||||||
|
#{suggestedChange.id}
|
||||||
|
</Typography>
|
||||||
|
</Typography>
|
||||||
|
<StyledTrueChip
|
||||||
|
icon={<ChangesAppliedIcon strokeWidth="0.25" />}
|
||||||
|
label="Changes applied"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', verticalAlign: 'center', gap: 2 }}>
|
||||||
|
<Typography sx={{ margin: 'auto 0' }}>
|
||||||
|
Created{' '}
|
||||||
|
<TimeAgo date={new Date(suggestedChange.createdAt)} /> by
|
||||||
|
</Typography>
|
||||||
|
<Avatar src={suggestedChange?.createdBy?.avatar} />
|
||||||
|
<Card
|
||||||
|
variant="outlined"
|
||||||
|
sx={theme => ({
|
||||||
|
padding: 1,
|
||||||
|
backgroundColor: theme.palette.tertiary.light,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Environment:{' '}
|
||||||
|
<Typography display="inline" fontWeight="bold">
|
||||||
|
{suggestedChange?.environment}
|
||||||
|
</Typography>{' '}
|
||||||
|
| Updates:{' '}
|
||||||
|
<Typography display="inline" fontWeight="bold">
|
||||||
|
{suggestedChange?.changes.length} feature toggles
|
||||||
|
</Typography>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,31 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Box } 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';
|
||||||
|
|
||||||
|
export const SuggestedChangeOverview: FC = () => {
|
||||||
|
const { data: suggestedChange } = useSuggestedChange();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SuggestedChangeHeader suggestedChange={suggestedChange} />
|
||||||
|
<Box sx={{ display: 'flex' }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: '30%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SuggestedChangeTimeline />
|
||||||
|
<SuggestedChangeReviewers />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<SuggestedChangeSet suggestedChange={suggestedChange} />
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
import { Box, Paper } from '@mui/material';
|
||||||
|
|
||||||
|
export const SuggestedChangeReviewers = () => {
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
sx={theme => ({
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
padding: 2,
|
||||||
|
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Box sx={theme => ({ padding: theme.spacing(2) })}>Reviewers</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,90 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,52 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Box, Paper } from '@mui/material';
|
||||||
|
import Timeline from '@mui/lab/Timeline';
|
||||||
|
import TimelineItem, { timelineItemClasses } from '@mui/lab/TimelineItem';
|
||||||
|
import TimelineSeparator from '@mui/lab/TimelineSeparator';
|
||||||
|
import TimelineDot from '@mui/lab/TimelineDot';
|
||||||
|
import TimelineConnector from '@mui/lab/TimelineConnector';
|
||||||
|
import TimelineContent from '@mui/lab/TimelineContent';
|
||||||
|
|
||||||
|
export const SuggestedChangeTimeline: FC = () => {
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
sx={theme => ({
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Box sx={theme => ({ padding: theme.spacing(2) })}>
|
||||||
|
<Timeline
|
||||||
|
sx={{
|
||||||
|
[`& .${timelineItemClasses.root}:before`]: {
|
||||||
|
flex: 0,
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TimelineItem>
|
||||||
|
<TimelineSeparator>
|
||||||
|
<TimelineDot color="success" />
|
||||||
|
<TimelineConnector color="success" />
|
||||||
|
</TimelineSeparator>
|
||||||
|
<TimelineContent>Draft</TimelineContent>
|
||||||
|
</TimelineItem>
|
||||||
|
<TimelineItem>
|
||||||
|
<TimelineSeparator>
|
||||||
|
<TimelineDot color="success" />
|
||||||
|
<TimelineConnector />
|
||||||
|
</TimelineSeparator>
|
||||||
|
<TimelineContent>Approved</TimelineContent>
|
||||||
|
</TimelineItem>
|
||||||
|
<TimelineItem>
|
||||||
|
<TimelineSeparator>
|
||||||
|
<TimelineDot />
|
||||||
|
</TimelineSeparator>
|
||||||
|
<TimelineContent>Applied</TimelineContent>
|
||||||
|
</TimelineItem>
|
||||||
|
</Timeline>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
import { Box, Typography } from '@mui/material';
|
||||||
|
import { FC } from 'react';
|
||||||
|
|
||||||
|
export const StrategyAddedChange: FC = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1, display: 'flex', gap: 1 }}>
|
||||||
|
<Typography sx={theme => ({ color: theme.palette.success.main })}>
|
||||||
|
+ Strategy Added:
|
||||||
|
</Typography>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StrategyEditedChange: FC = () => {
|
||||||
|
return <Box sx={{ p: 1 }}>Strategy Edited</Box>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const StrategyDeletedChange: FC = () => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1 }}>
|
||||||
|
<Typography sx={theme => ({ color: theme.palette.error.main })}>
|
||||||
|
- Strategy Deleted
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,37 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Box, Card, Typography } from '@mui/material';
|
||||||
|
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
|
||||||
|
|
||||||
|
interface ISuggestedFeatureToggleChange {
|
||||||
|
featureToggleName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SuggestedFeatureToggleChange: FC<
|
||||||
|
ISuggestedFeatureToggleChange
|
||||||
|
> = ({ featureToggleName, children }) => {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
elevation={0}
|
||||||
|
sx={theme => ({
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: theme => theme.palette.dividerAlternative,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={theme => ({
|
||||||
|
backgroundColor: theme.palette.tableHeaderBackground,
|
||||||
|
p: 2,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<ToggleOnIcon color="disabled" />
|
||||||
|
<Typography color="primary">{featureToggleName}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 2 }}>{children}</Box>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,16 @@
|
|||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { PlaygroundResultChip } from '../../../../../playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip';
|
||||||
|
|
||||||
|
export const ToggleStatusChange: FC<{ enabled: boolean }> = ({ enabled }) => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 1 }}>
|
||||||
|
New status:{' '}
|
||||||
|
<PlaygroundResultChip
|
||||||
|
showIcon={false}
|
||||||
|
label={enabled ? ' Enabled' : 'Disabled'}
|
||||||
|
enabled={enabled}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -1,163 +0,0 @@
|
|||||||
import React, { useState, VFC } from 'react';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Paper,
|
|
||||||
Button,
|
|
||||||
Typography,
|
|
||||||
Popover,
|
|
||||||
Radio,
|
|
||||||
FormControl,
|
|
||||||
FormControlLabel,
|
|
||||||
RadioGroup,
|
|
||||||
styled,
|
|
||||||
Tooltip,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { useChangeRequest } from 'hooks/api/getters/useChangeRequest/useChangeRequest';
|
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
||||||
import { ChangesetDiff } from './ChangesetDiff/ChangesetDiff';
|
|
||||||
import { SidebarModal } from 'component/common/SidebarModal/SidebarModal';
|
|
||||||
import { PageContent } from '../../../common/PageContent/PageContent';
|
|
||||||
import { PageHeader } from '../../../common/PageHeader/PageHeader';
|
|
||||||
import { HelpOutline } from '@mui/icons-material';
|
|
||||||
interface ISuggestedChangesProps {
|
|
||||||
open: boolean;
|
|
||||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 SuggestedChanges: VFC<ISuggestedChangesProps> = ({
|
|
||||||
open,
|
|
||||||
setOpen,
|
|
||||||
}) => {
|
|
||||||
const [selectedValue, setSelectedValue] = useState('');
|
|
||||||
const { data: changeRequest } = useChangeRequest();
|
|
||||||
|
|
||||||
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={() => {
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
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>{changeRequest?.state}</Typography>
|
|
||||||
<br />
|
|
||||||
<ChangesetDiff
|
|
||||||
changes={changeRequest?.changes}
|
|
||||||
state={changeRequest?.state}
|
|
||||||
/>
|
|
||||||
<Box sx={{ display: 'flex' }}>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={changeRequest?.state === 'APPROVED'}
|
|
||||||
show={<Typography>Applied</Typography>}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={changeRequest?.state === 'CLOSED'}
|
|
||||||
show={<Typography>Applied</Typography>}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={changeRequest?.state === 'APPROVED'}
|
|
||||||
show={
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
sx={{ mt: 2 }}
|
|
||||||
variant="contained"
|
|
||||||
onClick={onApply}
|
|
||||||
>
|
|
||||||
Apply changes
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ConditionallyRender
|
|
||||||
condition={changeRequest?.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>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,50 +0,0 @@
|
|||||||
// import useSWR from 'swr';
|
|
||||||
// import { formatApiPath } from 'utils/formatPath';
|
|
||||||
import { ISuggestChangeset } from 'interfaces/suggestChangeset';
|
|
||||||
import handleErrorResponses from '../httpErrorResponseHandler';
|
|
||||||
|
|
||||||
// FIXME: mock
|
|
||||||
const data: ISuggestChangeset = {
|
|
||||||
id: 123,
|
|
||||||
environment: 'production',
|
|
||||||
state: 'CREATED',
|
|
||||||
createdAt: new Date('2021-03-01T12:00:00.000Z'),
|
|
||||||
project: 'default',
|
|
||||||
createdBy: '123412341',
|
|
||||||
changes: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
feature: 'feature1',
|
|
||||||
action: 'updateEnabled',
|
|
||||||
payload: true,
|
|
||||||
createdAt: new Date('2021-03-01T12:00:00.000Z'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
feature: 'feature2',
|
|
||||||
action: 'updateEnabled',
|
|
||||||
payload: false,
|
|
||||||
createdAt: new Date('2022-09-30T16:34:00.000Z'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useChangeRequest = () => {
|
|
||||||
// const { data, error, mutate } = useSWR(
|
|
||||||
// formatApiPath(`api/admin/suggest-changes/${id}`),
|
|
||||||
// fetcher
|
|
||||||
// );
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
// loading: !error && !data,
|
|
||||||
// refetchChangeRequest: () => mutate(),
|
|
||||||
// error,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetcher = (path: string) => {
|
|
||||||
return fetch(path)
|
|
||||||
.then(handleErrorResponses('Request changes'))
|
|
||||||
.then(res => res.json());
|
|
||||||
};
|
|
@ -0,0 +1,101 @@
|
|||||||
|
// import useSWR from 'swr';
|
||||||
|
// import { formatApiPath } from 'utils/formatPath';
|
||||||
|
import { ISuggestChangeset } from 'interfaces/suggestChangeset';
|
||||||
|
import handleErrorResponses from '../httpErrorResponseHandler';
|
||||||
|
|
||||||
|
// FIXME: mock
|
||||||
|
const data: any = {
|
||||||
|
id: '12',
|
||||||
|
environment: 'production',
|
||||||
|
state: 'DRAFT',
|
||||||
|
project: 'default',
|
||||||
|
createdBy: {
|
||||||
|
email: 'mateusz@getunleash.ai',
|
||||||
|
avatar: 'https://gravatar-uri.com/1321',
|
||||||
|
},
|
||||||
|
createdAt: '2020-10-20T12:00:00.000Z',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
feature: 'my-feature-toggle',
|
||||||
|
changeSet: [
|
||||||
|
{
|
||||||
|
id: 'f79d399f-cb38-4982-b9b6-4141sdsdaad',
|
||||||
|
action: 'updateEnabled',
|
||||||
|
payload: { data: { data: true } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'f79d399f-cb38-4982-b9b6-4141sdsdaad',
|
||||||
|
action: 'addStrategy',
|
||||||
|
payload: {
|
||||||
|
name: 'flexibleRollout',
|
||||||
|
constraints: [],
|
||||||
|
parameters: {
|
||||||
|
rollout: '50',
|
||||||
|
stickiness: 'default',
|
||||||
|
groupId: 'suggest-changes',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'f79d399f-cb38-4982-b9b6-4141sdsdaad',
|
||||||
|
action: 'updateStrategy',
|
||||||
|
payload: {
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'f79d399f-cb38-4982-b9b6-4141sdsdaad',
|
||||||
|
action: 'deleteStrategy',
|
||||||
|
payload: {
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
feature: 'new-feature-toggle',
|
||||||
|
changeSet: [
|
||||||
|
{
|
||||||
|
id: 'f79d399f-cb38-4982-b9b6-4141sdsdaad',
|
||||||
|
action: 'updateEnabled',
|
||||||
|
payload: {
|
||||||
|
data: { data: false },
|
||||||
|
strategyId: '123-14',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
feature: 'add-strategy-feature-toggle',
|
||||||
|
changeSet: [
|
||||||
|
{
|
||||||
|
id: 'f79d399f-cb38-4982-b9b6-4141sdsdaad',
|
||||||
|
action: 'addStrategy',
|
||||||
|
payload: {
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSuggestedChange = () => {
|
||||||
|
// const { data, error, mutate } = useSWR(
|
||||||
|
// formatApiPath(`api/admin/suggest-changes/${id}`),
|
||||||
|
// fetcher
|
||||||
|
// );
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
// loading: !error && !data,
|
||||||
|
// refetchChangeRequest: () => mutate(),
|
||||||
|
// error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetcher = (path: string) => {
|
||||||
|
return fetch(path)
|
||||||
|
.then(handleErrorResponses('Request changes'))
|
||||||
|
.then(res => res.json());
|
||||||
|
};
|
@ -36,6 +36,13 @@ export const getFeatureStrategyIcon = (strategyName: string): ElementType => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GetFeatureStrategyIcon: FC<{ strategyName: string }> = ({
|
||||||
|
strategyName,
|
||||||
|
}) => {
|
||||||
|
const Icon = getFeatureStrategyIcon(strategyName);
|
||||||
|
return <Icon />;
|
||||||
|
};
|
||||||
|
|
||||||
export const formattedStrategyNames: Record<string, string> = {
|
export const formattedStrategyNames: Record<string, string> = {
|
||||||
applicationHostname: 'Hosts',
|
applicationHostname: 'Hosts',
|
||||||
default: 'Standard',
|
default: 'Standard',
|
||||||
|
Loading…
Reference in New Issue
Block a user