diff --git a/frontend/src/assets/icons/merge.svg b/frontend/src/assets/icons/merge.svg
new file mode 100644
index 0000000000..b7a15a8864
--- /dev/null
+++ b/frontend/src/assets/icons/merge.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip.tsx b/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip.tsx
index e6618ac7ea..6c02cd3320 100644
--- a/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip.tsx
+++ b/frontend/src/component/playground/Playground/PlaygroundResultsTable/PlaygroundResultChip/PlaygroundResultChip.tsx
@@ -43,6 +43,7 @@ export const StyledTrueChip = styled(StyledChip)(({ theme }) => ({
},
['& .MuiChip-icon']: {
color: theme.palette.success.main,
+ marginRight: 0,
},
}));
diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx
index 1c5073f0e1..f2555a6eb2 100644
--- a/frontend/src/component/project/Project/Project.tsx
+++ b/frontend/src/component/project/Project/Project.tsx
@@ -24,7 +24,7 @@ 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 { SuggestedChanges } from './SuggestedChanges/SuggestedChanges';
+import { SuggestedChangeOverview } from './SuggestedChanges/SuggestedChangeOverview/SuggestedChangeOverview';
import { DraftBanner } from './SuggestedChanges/DraftBanner/DraftBanner';
import { MainLayout } from 'component/layout/MainLayout/MainLayout';
@@ -224,6 +224,16 @@ const Project = () => {
} />
} />
} />
+ }
+ />
+ }
+ />
+
} />
diff --git a/frontend/src/component/project/Project/SuggestedChanges/DraftBanner/DraftBanner.tsx b/frontend/src/component/project/Project/SuggestedChanges/DraftBanner/DraftBanner.tsx
index 01d1e9ce7c..c5f98de4d8 100644
--- a/frontend/src/component/project/Project/SuggestedChanges/DraftBanner/DraftBanner.tsx
+++ b/frontend/src/component/project/Project/SuggestedChanges/DraftBanner/DraftBanner.tsx
@@ -3,8 +3,6 @@ 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 { EditGroupUsers } from '../../../../admin/groups/Group/EditGroupUsers/EditGroupUsers';
-import { SuggestedChanges } from '../SuggestedChanges';
interface IDraftBannerProps {
environment?: string;
@@ -61,10 +59,6 @@ export const DraftBanner: VFC = ({ environment }) => {
-
);
};
diff --git a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeHeader/SuggestedChangeHeader.tsx b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeHeader/SuggestedChangeHeader.tsx
new file mode 100644
index 0000000000..558851ba32
--- /dev/null
+++ b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeHeader/SuggestedChangeHeader.tsx
@@ -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 (
+ ({
+ p: theme.spacing(2, 4),
+ borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
+ })}
+ >
+ ({
+ display: 'flex',
+ alignItems: 'center',
+ gap: 2,
+ marginBottom: theme.spacing(2),
+ })}
+ >
+
+ Suggestion
+
+ #{suggestedChange.id}
+
+
+ }
+ label="Changes applied"
+ />
+
+
+
+ Created{' '}
+ by
+
+
+ ({
+ padding: 1,
+ backgroundColor: theme.palette.tertiary.light,
+ })}
+ >
+ Environment:{' '}
+
+ {suggestedChange?.environment}
+ {' '}
+ | Updates:{' '}
+
+ {suggestedChange?.changes.length} feature toggles
+
+
+
+
+ );
+};
diff --git a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeOverview.tsx b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeOverview.tsx
new file mode 100644
index 0000000000..cc90a3fac3
--- /dev/null
+++ b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeOverview.tsx
@@ -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 (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeReviewers/SuggestedChangeReviewers.tsx b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeReviewers/SuggestedChangeReviewers.tsx
new file mode 100644
index 0000000000..d4a0a31f05
--- /dev/null
+++ b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeReviewers/SuggestedChangeReviewers.tsx
@@ -0,0 +1,16 @@
+import { Box, Paper } from '@mui/material';
+
+export const SuggestedChangeReviewers = () => {
+ return (
+ ({
+ marginTop: theme.spacing(2),
+ padding: 2,
+ borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
+ })}
+ >
+ ({ padding: theme.spacing(2) })}>Reviewers
+
+ );
+};
diff --git a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeSet/SuggestedChangeSet.tsx b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeSet/SuggestedChangeSet.tsx
new file mode 100644
index 0000000000..d79f178b79
--- /dev/null
+++ b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeSet/SuggestedChangeSet.tsx
@@ -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 (
+ ({
+ marginTop: theme.spacing(2),
+ marginLeft: theme.spacing(2),
+ width: '70%',
+ padding: 2,
+ borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
+ })}
+ >
+ ({
+ padding: theme.spacing(2),
+ })}
+ >
+ Changes
+ {suggestedChange.changes?.map((featureToggleChange: any) => (
+
+ {featureToggleChange.changeSet.map((change: any) => (
+
+
+ }
+ />
+
+
+ {formatStrategyName(
+ change.payload.name
+ )}
+
+ }
+ />
+ }
+ />
+ }
+ />
+
+ ))}
+
+ ))}
+
+
+ );
+};
diff --git a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeTimeline/SuggestedChangeTimeline.tsx b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeTimeline/SuggestedChangeTimeline.tsx
new file mode 100644
index 0000000000..80b04bc997
--- /dev/null
+++ b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedChangeTimeline/SuggestedChangeTimeline.tsx
@@ -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 (
+ ({
+ marginTop: theme.spacing(2),
+ borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
+ })}
+ >
+ ({ padding: theme.spacing(2) })}>
+
+
+
+
+
+
+ Draft
+
+
+
+
+
+
+ Approved
+
+
+
+
+
+ Applied
+
+
+
+
+ );
+};
diff --git a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedFeatureToggleChange/StrategyChange.tsx b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedFeatureToggleChange/StrategyChange.tsx
new file mode 100644
index 0000000000..32728cca0e
--- /dev/null
+++ b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedFeatureToggleChange/StrategyChange.tsx
@@ -0,0 +1,27 @@
+import { Box, Typography } from '@mui/material';
+import { FC } from 'react';
+
+export const StrategyAddedChange: FC = ({ children }) => {
+ return (
+
+ ({ color: theme.palette.success.main })}>
+ + Strategy Added:
+
+ {children}
+
+ );
+};
+
+export const StrategyEditedChange: FC = () => {
+ return Strategy Edited;
+};
+
+export const StrategyDeletedChange: FC = () => {
+ return (
+
+ ({ color: theme.palette.error.main })}>
+ - Strategy Deleted
+
+
+ );
+};
diff --git a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedFeatureToggleChange/SuggestedFeatureToggleChange.tsx b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedFeatureToggleChange/SuggestedFeatureToggleChange.tsx
new file mode 100644
index 0000000000..a134ffe46f
--- /dev/null
+++ b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedFeatureToggleChange/SuggestedFeatureToggleChange.tsx
@@ -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 (
+ ({
+ marginTop: theme.spacing(2),
+ borderRadius: theme => `${theme.shape.borderRadiusLarge}px`,
+ overflow: 'hidden',
+ border: '1px solid',
+ borderColor: theme => theme.palette.dividerAlternative,
+ })}
+ >
+ ({
+ backgroundColor: theme.palette.tableHeaderBackground,
+ p: 2,
+ })}
+ >
+
+
+ {featureToggleName}
+
+
+ {children}
+
+ );
+};
diff --git a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedFeatureToggleChange/ToggleStatusChange.tsx b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedFeatureToggleChange/ToggleStatusChange.tsx
new file mode 100644
index 0000000000..fa68484a50
--- /dev/null
+++ b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChangeOverview/SuggestedFeatureToggleChange/ToggleStatusChange.tsx
@@ -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 (
+
+ New status:{' '}
+
+
+ );
+};
diff --git a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChanges.tsx b/frontend/src/component/project/Project/SuggestedChanges/SuggestedChanges.tsx
deleted file mode 100644
index 7388ee096d..0000000000
--- a/frontend/src/component/project/Project/SuggestedChanges/SuggestedChanges.tsx
+++ /dev/null
@@ -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>;
-}
-
-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 = ({
- 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 (
- {
- setOpen(false);
- }}
- label="Review changes"
- >
-
- Review your changes
-
-
-
-
- Make sure you are sending the right changes
- suggestions to be reviewed
-
- >
- }
- >
- }
- >
- {/* TODO: multiple environments (changesets) */}
- {changeRequest?.state}
-
-
-
- Applied}
- />
- Applied}
- />
-
-
- >
- }
- />
-
-
-
- >
- }
- />
-
-
-
- );
-};
diff --git a/frontend/src/hooks/api/getters/useChangeRequest/useChangeRequest.ts b/frontend/src/hooks/api/getters/useChangeRequest/useChangeRequest.ts
deleted file mode 100644
index 6d4ac42873..0000000000
--- a/frontend/src/hooks/api/getters/useChangeRequest/useChangeRequest.ts
+++ /dev/null
@@ -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());
-};
diff --git a/frontend/src/hooks/api/getters/useSuggestChange/useSuggestedChange.ts b/frontend/src/hooks/api/getters/useSuggestChange/useSuggestedChange.ts
new file mode 100644
index 0000000000..e0e4623f47
--- /dev/null
+++ b/frontend/src/hooks/api/getters/useSuggestChange/useSuggestedChange.ts
@@ -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());
+};
diff --git a/frontend/src/utils/strategyNames.tsx b/frontend/src/utils/strategyNames.tsx
index cea5275588..315453c4ac 100644
--- a/frontend/src/utils/strategyNames.tsx
+++ b/frontend/src/utils/strategyNames.tsx
@@ -36,6 +36,13 @@ export const getFeatureStrategyIcon = (strategyName: string): ElementType => {
}
};
+export const GetFeatureStrategyIcon: FC<{ strategyName: string }> = ({
+ strategyName,
+}) => {
+ const Icon = getFeatureStrategyIcon(strategyName);
+ return ;
+};
+
export const formattedStrategyNames: Record = {
applicationHostname: 'Hosts',
default: 'Standard',