From b7183fdf988b0b10839be17ebc9129ed8794183f Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Fri, 28 Oct 2022 09:43:49 +0200 Subject: [PATCH] feat: get suggested changeset draft (#2274) * feat: get suggested changeset draft * fix: update routes snapshot --- .../feature/FeatureView/FeatureView.tsx | 260 ++++++++++-------- .../__snapshots__/routes.test.tsx.snap | 1 + frontend/src/component/menu/routes.ts | 1 + .../src/component/project/Project/Project.tsx | 6 +- .../DraftBanner/DraftBanner.tsx | 12 +- .../SuggestedChangesSidebar.tsx | 161 +++++++---- .../SuggestedChangeset/SuggestedChangeset.tsx | 39 +-- .../useSuggestChange/useSuggestedChange.ts | 3 + .../useSuggestedChangesDraft.ts | 58 ++++ 9 files changed, 347 insertions(+), 194 deletions(-) create mode 100644 frontend/src/hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft.ts diff --git a/frontend/src/component/feature/FeatureView/FeatureView.tsx b/frontend/src/component/feature/FeatureView/FeatureView.tsx index eb691c5fd1..dbc1a5f8e6 100644 --- a/frontend/src/component/feature/FeatureView/FeatureView.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureView.tsx @@ -30,12 +30,16 @@ import StatusChip from 'component/common/StatusChip/StatusChip'; import { FeatureNotFound } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { FeatureArchiveDialog } from '../../common/FeatureArchiveDialog/FeatureArchiveDialog'; +import { DraftBanner } from 'component/suggestChanges/DraftBanner/DraftBanner'; +import { MainLayout } from 'component/layout/MainLayout/MainLayout'; +import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; export const FeatureView = () => { const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); const { refetch: projectRefetch } = useProject(projectId); const { refetchFeature } = useFeature(projectId, featureId); + const { uiConfig } = useUiConfig(); const [openTagDialog, setOpenTagDialog] = useState(false); const [showDelDialog, setShowDelDialog] = useState(false); @@ -81,123 +85,145 @@ export const FeatureView = () => { } return ( - -
-
-
-

- {feature.name}{' '} -

- } - /> -
- -
- - - - setShowDelDialog(true)} - > - - - setOpenStaleDialog(true)} - permission={UPDATE_FEATURE} - projectId={projectId} - tooltipProps={{ - title: 'Toggle stale state', - }} - data-loading - > - - - setOpenTagDialog(true)} - permission={UPDATE_FEATURE} - projectId={projectId} - tooltipProps={{ title: 'Add tag' }} - data-loading - > - -
-
-
-
- - {tabData.map(tab => ( - navigate(tab.path)} - className={styles.tabButton} - /> - ))} - -
-
- - } /> - } /> - } /> - } /> - } /> - - { - projectRefetch(); - navigate(`/projects/${projectId}`); - }} - onClose={() => setShowDelDialog(false)} - projectId={projectId} - featureId={featureId} - /> - { - setOpenStaleDialog(false); - refetchFeature(); - }} - featureId={featureId} - projectId={projectId} - /> - -
+ + ) : null } - /> + > + +
+
+
+

+ {feature.name}{' '} +

+ + } + /> +
+ +
+ + + + setShowDelDialog(true)} + > + + + setOpenStaleDialog(true)} + permission={UPDATE_FEATURE} + projectId={projectId} + tooltipProps={{ + title: 'Toggle stale state', + }} + data-loading + > + + + setOpenTagDialog(true)} + permission={UPDATE_FEATURE} + projectId={projectId} + tooltipProps={{ title: 'Add tag' }} + data-loading + > + +
+
+
+
+ + {tabData.map(tab => ( + navigate(tab.path)} + className={styles.tabButton} + /> + ))} + +
+
+ + } + /> + } /> + } + /> + } + /> + } /> + + { + projectRefetch(); + navigate(`/projects/${projectId}`); + }} + onClose={() => setShowDelDialog(false)} + projectId={projectId} + featureId={featureId} + /> + { + setOpenStaleDialog(false); + refetchFeature(); + }} + featureId={featureId} + projectId={projectId} + /> + +
+ } + /> +
); }; diff --git a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap index 13ca9096e9..6b8ff72792 100644 --- a/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap +++ b/frontend/src/component/menu/__tests__/__snapshots__/routes.test.tsx.snap @@ -54,6 +54,7 @@ exports[`returns all baseRoutes 1`] = ` }, { "component": [Function], + "isStandalone": true, "menu": {}, "parent": "/projects", "path": "/projects/:projectId/features/:featureId/*", diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 01915bd13f..6cbb46a04f 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -121,6 +121,7 @@ export const routes: IRoute[] = [ title: 'FeatureView', component: FeatureView, type: 'protected', + isStandalone: true, menu: {}, }, { diff --git a/frontend/src/component/project/Project/Project.tsx b/frontend/src/component/project/Project/Project.tsx index f1944b98ab..4c003fa5ce 100644 --- a/frontend/src/component/project/Project/Project.tsx +++ b/frontend/src/component/project/Project/Project.tsx @@ -117,7 +117,11 @@ const Project = () => { return ( : null} + subheader={ + uiConfig?.flags?.suggestChanges ? ( + + ) : null + } >
diff --git a/frontend/src/component/suggestChanges/DraftBanner/DraftBanner.tsx b/frontend/src/component/suggestChanges/DraftBanner/DraftBanner.tsx index 8d60898522..e40558bed8 100644 --- a/frontend/src/component/suggestChanges/DraftBanner/DraftBanner.tsx +++ b/frontend/src/component/suggestChanges/DraftBanner/DraftBanner.tsx @@ -4,14 +4,21 @@ 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'; +import { useSuggestedChangesDraft } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft'; interface IDraftBannerProps { - environment?: string; + project: string; } -export const DraftBanner: VFC = ({ environment }) => { +export const DraftBanner: VFC = ({ project }) => { const { classes } = useAppStyles(); const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const { draft, loading } = useSuggestedChangesDraft(project); + const environment = ''; + + if (!loading && !draft) { + return null; + } return ( = ({ environment }) => { { setIsSidebarOpen(false); diff --git a/frontend/src/component/suggestChanges/SuggestedChangesSidebar/SuggestedChangesSidebar.tsx b/frontend/src/component/suggestChanges/SuggestedChangesSidebar/SuggestedChangesSidebar.tsx index ebf6c8fdad..406118fe9c 100644 --- a/frontend/src/component/suggestChanges/SuggestedChangesSidebar/SuggestedChangesSidebar.tsx +++ b/frontend/src/component/suggestChanges/SuggestedChangesSidebar/SuggestedChangesSidebar.tsx @@ -1,14 +1,16 @@ -import React, { useState, VFC } from 'react'; +import { 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'; +import { useSuggestedChangesDraft } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft'; + interface ISuggestedChangesSidebarProps { open: boolean; + project: string; onClose: () => void; } const StyledPageContent = styled(PageContent)(({ theme }) => ({ @@ -26,11 +28,13 @@ const StyledPageContent = styled(PageContent)(({ theme }) => ({ }, 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, @@ -38,23 +42,43 @@ const StyledHeaderHint = styled('div')(({ theme }) => ({ export const SuggestedChangesSidebar: VFC = ({ open, + project, onClose, }) => { - const { data: suggestedChange } = useSuggestedChange(); + const { draft, loading } = useSuggestedChangesDraft(project); const onReview = async () => { - console.log('approve'); + alert('approve'); }; const onDiscard = async () => { - console.log('discard'); + alert('discard'); }; const onApply = async () => { try { - console.log('apply'); + alert('apply'); } catch (e) { console.log(e); } }; + + if (!loading && !draft) { + return ( + + + } + > + There are no changes to review. + {/* FIXME: empty state */} + + + ); + } + return ( = ({ > } > - {/* TODO: multiple environments (changesets) */} - {suggestedChange?.state} -
- - - Applied} - /> - Applied} - /> - - - - } - /> - - - - - } - /> - + {draft?.map(environmentChangeset => ( + theme.palette.neutral.light, + borderRadius: theme => + `${theme.shape.borderRadiusLarge}px`, + }} + > + + env: {environmentChangeset?.environment} + + + state: {environmentChangeset?.state} + +
+ + + Applied} + /> + Applied} + /> + + + + } + /> + + + + + } + /> + +
+ ))}
); diff --git a/frontend/src/component/suggestChanges/SuggestedChangeset/SuggestedChangeset.tsx b/frontend/src/component/suggestChanges/SuggestedChangeset/SuggestedChangeset.tsx index ee6d9c373f..d8ff7a9d41 100644 --- a/frontend/src/component/suggestChanges/SuggestedChangeset/SuggestedChangeset.tsx +++ b/frontend/src/component/suggestChanges/SuggestedChangeset/SuggestedChangeset.tsx @@ -4,38 +4,39 @@ import { SuggestedFeatureToggleChange } from '../SuggestedChangeOverview/Suggest 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'; +// import { +// StrategyAddedChange, +// StrategyDeletedChange, +// StrategyEditedChange, +// } from '../SuggestedChangeOverview/SuggestedFeatureToggleChange/StrategyChange'; +// import { +// formatStrategyName, +// GetFeatureStrategyIcon, +// } from 'utils/strategyNames'; +import type { ISuggestChangesResponse } from 'hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft'; -export const SuggestedChangeset: FC<{ suggestedChange: any }> = ({ - suggestedChange, -}) => { +export const SuggestedChangeset: FC<{ + suggestedChange: ISuggestChangesResponse; +}> = ({ suggestedChange }) => { return ( Changes - {suggestedChange.changes?.map((featureToggleChange: any) => ( + {suggestedChange.features?.map(featureToggleChange => ( - {featureToggleChange.changeSet.map((change: any) => ( + {featureToggleChange.changes.map(change => ( } /> - @@ -55,7 +56,7 @@ export const SuggestedChangeset: FC<{ suggestedChange: any }> = ({ } - /> + /> */} ))} diff --git a/frontend/src/hooks/api/getters/useSuggestChange/useSuggestedChange.ts b/frontend/src/hooks/api/getters/useSuggestChange/useSuggestedChange.ts index e0e4623f47..265e7afee0 100644 --- a/frontend/src/hooks/api/getters/useSuggestChange/useSuggestedChange.ts +++ b/frontend/src/hooks/api/getters/useSuggestChange/useSuggestedChange.ts @@ -80,6 +80,9 @@ const data: any = { ], }; +/** + * @deprecated for draft: useSuggestedChangesDraft + */ export const useSuggestedChange = () => { // const { data, error, mutate } = useSWR( // formatApiPath(`api/admin/suggest-changes/${id}`), diff --git a/frontend/src/hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft.ts b/frontend/src/hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft.ts new file mode 100644 index 0000000000..46760f94ad --- /dev/null +++ b/frontend/src/hooks/api/getters/useSuggestedChangesDraft/useSuggestedChangesDraft.ts @@ -0,0 +1,58 @@ +import useSWR from 'swr'; +import { useMemo } from 'react'; +import { formatApiPath } from 'utils/formatPath'; +import handleErrorResponses from '../httpErrorResponseHandler'; + +interface IChange { + id: number; + action: string; + payload: { + enabled: boolean; // FIXME: add other action types + }; + createdAt: Date; + createdBy: { + id: number; + username?: any; + imageUrl?: any; + }; +} + +export interface ISuggestChangesResponse { + id: number; + environment: string; + state: string; + project: string; + createdBy: { + id: number; + username?: any; + imageUrl?: any; + }; + createdAt: Date; + features: Array<{ + name: string; + changes: IChange[]; + }>; +} + +const fetcher = (path: string) => { + return fetch(path) + .then(handleErrorResponses('SuggestedChanges')) + .then(res => res.json()); +}; + +export const useSuggestedChangesDraft = (project: string) => { + const { data, error, mutate } = useSWR( + formatApiPath(`api/admin/projects/${project}/suggest-changes/draft`), + fetcher + ); + + return useMemo( + () => ({ + draft: data, + loading: !error && !data, + refetch: () => mutate(), + error, + }), + [data, error, mutate] + ); +};