diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/ChildrenTooltip.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/ChildrenTooltip.tsx similarity index 88% rename from frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/ChildrenTooltip.tsx rename to frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/ChildrenTooltip.tsx index 0b706731ed..f3d09a3fbe 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/ChildrenTooltip.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/ChildrenTooltip.tsx @@ -1,4 +1,4 @@ -import { StyledLink } from './StyledRow'; +import { StyledLink } from '../FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/StyledRow'; import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; import type { FC } from 'react'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyActions.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/DependencyActions.tsx similarity index 100% rename from frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyActions.tsx rename to frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/DependencyActions.tsx diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/DependencyRow.tsx similarity index 98% rename from frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx rename to frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/DependencyRow.tsx index 1bb62ab6cf..65ee8e276f 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/DependencyRow.tsx @@ -2,7 +2,12 @@ import { ConditionallyRender } from 'component/common/ConditionallyRender/Condit import { AddDependencyDialogue } from 'component/feature/Dependencies/AddDependencyDialogue'; import type { IFeatureToggle } from 'interfaces/featureToggle'; import { type FC, useState } from 'react'; -import { FlexRow, StyledDetail, StyledLabel, StyledLink } from './StyledRow'; +import { + FlexRow, + StyledDetail, + StyledLabel, + StyledLink, +} from '../FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/StyledRow'; import { DependencyActions } from './DependencyActions'; import { useDependentFeaturesApi } from 'hooks/api/actions/useDependentFeaturesApi/useDependentFeaturesApi'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.test.tsx similarity index 57% rename from frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx rename to frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.test.tsx index bf92921f8d..77178dbd12 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.test.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.test.tsx @@ -1,10 +1,11 @@ import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; import { render } from 'utils/testRenderer'; -import { FeatureOverviewSidePanelDetails } from './FeatureOverviewSidePanelDetails'; -import type { IDependency, IFeatureToggle } from 'interfaces/featureToggle'; +import FeatureOverviewMetaData from './FeatureOverviewMetaData'; import { testServerRoute, testServerSetup } from 'utils/testServer'; +import { Route, Routes } from 'react-router-dom'; +import type { IDependency, IFeatureToggle } from 'interfaces/featureToggle'; import ToastRenderer from 'component/common/ToastRenderer/ToastRenderer'; +import userEvent from '@testing-library/user-event'; const server = testServerSetup(); @@ -14,7 +15,6 @@ const setupApi = () => { current: { oss: 'irrelevant', enterprise: 'some value' }, }, }); - testServerRoute(server, '/api/admin/projects/default/features/feature', {}); testServerRoute( server, '/api/admin/projects/default/features/feature/parents', @@ -54,36 +54,43 @@ const setupChangeRequestApi = () => { ); testServerRoute( server, - 'api/admin/projects/default/change-requests/pending', + '/api/admin/projects/default/change-requests/pending', [], ); testServerRoute( server, - 'api/admin/projects/default/environments/development/change-requests', + '/api/admin/projects/default/environments/development/change-requests', {}, 'post', 200, ); }; +const setupFeatureApi = (feature: IFeatureToggle) => { + testServerRoute( + server, + '/api/admin/projects/default/features/feature', + feature, + ); +}; + beforeEach(() => { setupApi(); }); +const route = '/projects/default/features/feature'; + test('show dependency dialogue', async () => { + setupFeatureApi(feature); render( - , - children: [] as string[], - } as IFeatureToggle - } - header={''} - />, + + } + /> + , { + route, permissions: [ { permission: 'UPDATE_FEATURE_DEPENDENCY', project: 'default' }, ], @@ -101,19 +108,21 @@ test('show dependency dialogue', async () => { test('show dependency dialogue for OSS with dependencies', async () => { setupOssWithExistingDependencies(); + setupFeatureApi({ + name: 'feature', + project: 'default', + dependencies: [] as Array<{ feature: string }>, + children: [] as string[], + } as IFeatureToggle); render( - , - children: [] as string[], - } as IFeatureToggle - } - header={''} - />, + + } + /> + , { + route, permissions: [ { permission: 'UPDATE_FEATURE_DEPENDENCY', project: 'default' }, ], @@ -130,18 +139,20 @@ test('show dependency dialogue for OSS with dependencies', async () => { }); test('show child', async () => { + setupFeatureApi({ + name: 'feature', + project: 'default', + dependencies: [] as Array<{ feature: string }>, + children: ['some_child'], + } as IFeatureToggle); render( - , - children: ['some_child'], - } as IFeatureToggle - } - header={''} - />, + + } + /> + , + { route }, ); await screen.findByText('Children:'); @@ -149,18 +160,20 @@ test('show child', async () => { }); test('show children', async () => { + setupFeatureApi({ + name: 'feature', + project: 'default', + dependencies: [] as Array<{ feature: string }>, + children: ['some_child', 'some_other_child'], + } as IFeatureToggle); render( - , - children: ['some_child', 'some_other_child'], - } as IFeatureToggle - } - header={''} - />, + + } + /> + , + { route }, ); await screen.findByText('Children:'); @@ -175,18 +188,22 @@ const feature = { } as IFeatureToggle; test('delete dependency', async () => { + setupFeatureApi({ + ...feature, + dependencies: [{ feature: 'some_parent' }], + }); render( <> - + + } + /> + , { + route, permissions: [ { permission: 'UPDATE_FEATURE_DEPENDENCY', project: 'default' }, ], @@ -209,18 +226,22 @@ test('delete dependency', async () => { test('delete dependency with change request', async () => { setupChangeRequestApi(); + setupFeatureApi({ + ...feature, + dependencies: [{ feature: 'some_parent' }], + }); render( <> - + + } + /> + , { + route, permissions: [ /* deliberately no permissions */ ], @@ -242,15 +263,19 @@ test('delete dependency with change request', async () => { }); test('edit dependency', async () => { + setupFeatureApi({ + ...feature, + dependencies: [{ feature: 'some_parent', enabled: false }], + }); render( - , + + } + /> + , { + route, permissions: [ { permission: 'UPDATE_FEATURE_DEPENDENCY', project: 'default' }, ], @@ -274,20 +299,24 @@ test('edit dependency', async () => { }); test('show variant dependencies', async () => { + setupFeatureApi({ + ...feature, + dependencies: [ + { + feature: 'some_parent', + enabled: true, + variants: ['variantA', 'variantB'], + }, + ], + }); render( - , + + } + /> + , + { route }, ); const variants = await screen.findByText('2 variants'); @@ -299,39 +328,47 @@ test('show variant dependencies', async () => { }); test('show variant dependency', async () => { + setupFeatureApi({ + ...feature, + dependencies: [ + { + feature: 'some_parent', + enabled: true, + variants: ['variantA'], + }, + ], + }); render( - , + + } + /> + , + { route }, ); await screen.findByText('variantA'); }); test('show disabled dependency', async () => { + setupFeatureApi({ + ...feature, + dependencies: [ + { + feature: 'some_parent', + enabled: false, + }, + ], + }); render( - , + + } + /> + , + { route }, ); await screen.findByText('disabled'); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx index 300a2d8c1f..2d36a06160 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewMetaData/FeatureOverviewMetaData.tsx @@ -15,6 +15,14 @@ import { useState } from 'react'; import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog'; import { populateCurrentStage } from '../FeatureLifecycle/populateCurrentStage'; import useFeatureLifecycleApi from 'hooks/api/actions/useFeatureLifecycleApi/useFeatureLifecycleApi'; +import { StyledDetail } from '../FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/StyledRow'; +import { formatDateYMD } from 'utils/formatDate'; +import { parseISO } from 'date-fns'; +import { FeatureEnvironmentSeen } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen'; +import { DependencyRow } from './DependencyRow'; +import { useLocationSettings } from 'hooks/useLocationSettings'; +import { useShowDependentFeatures } from './useShowDependentFeatures'; +import type { ILastSeenEnvironments } from 'interfaces/featureToggle'; const StyledContainer = styled('div')(({ theme }) => ({ borderRadius: theme.shape.borderRadiusLarge, @@ -68,6 +76,12 @@ const StyledDescriptionContainer = styled('div')(({ theme }) => ({ alignItems: 'center', })); +const StyledDetailsContainer = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', +})); + const StyledDescription = styled('p')({ wordBreak: 'break-word', }); @@ -87,6 +101,15 @@ const FeatureOverviewMetaData = () => { useFeatureLifecycleApi(); const navigate = useNavigate(); const [showDelDialog, setShowDelDialog] = useState(false); + const { locationSettings } = useLocationSettings(); + const showDependentFeatures = useShowDependentFeatures(feature.project); + + const lastSeenEnvironments: ILastSeenEnvironments[] = + feature.environments?.map((env) => ({ + name: env.name, + lastSeenAt: env.lastSeenAt, + enabled: env.enabled, + })); const IconComponent = getFeatureTypeIcons(type); @@ -189,6 +212,29 @@ const FeatureOverviewMetaData = () => { } /> + + + + Created at: + + {formatDateYMD( + parseISO(feature.createdAt), + locationSettings.locale, + )} + + + + + + + } + /> - - Feature toggle details - - } - feature={feature} - /> - diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx deleted file mode 100644 index f1a247d80b..0000000000 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/FeatureOverviewSidePanelDetails.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import type { - IFeatureToggle, - ILastSeenEnvironments, -} from 'interfaces/featureToggle'; -import { styled } from '@mui/material'; -import { useLocationSettings } from 'hooks/useLocationSettings'; -import { formatDateYMD } from 'utils/formatDate'; -import { parseISO } from 'date-fns'; -import { FeatureEnvironmentSeen } from '../../../FeatureEnvironmentSeen/FeatureEnvironmentSeen'; -import { DependencyRow } from './DependencyRow'; -import { FlexRow, StyledDetail, StyledLabel } from './StyledRow'; -import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { useShowDependentFeatures } from './useShowDependentFeatures'; - -const StyledContainer = styled('div')(({ theme }) => ({ - display: 'flex', - flexDirection: 'column', - justifyItems: 'center', - padding: theme.spacing(3), - fontSize: theme.fontSizes.smallBody, -})); - -interface IFeatureOverviewSidePanelDetailsProps { - feature: IFeatureToggle; - header: React.ReactNode; -} -export const FeatureOverviewSidePanelDetails = ({ - feature, - header, -}: IFeatureOverviewSidePanelDetailsProps) => { - const { locationSettings } = useLocationSettings(); - const showDependentFeatures = useShowDependentFeatures(feature.project); - - const lastSeenEnvironments: ILastSeenEnvironments[] = - feature.environments?.map((env) => ({ - name: env.name, - lastSeenAt: env.lastSeenAt, - enabled: env.enabled, - })); - - return ( - - {header} - - - Created at: - - {formatDateYMD( - parseISO(feature.createdAt), - locationSettings.locale, - )} - - - - - - } - /> - - ); -}; diff --git a/frontend/src/component/feature/FeatureView/FeatureView.tsx b/frontend/src/component/feature/FeatureView/FeatureView.tsx index 1a28f57ecf..64df266621 100644 --- a/frontend/src/component/feature/FeatureView/FeatureView.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureView.tsx @@ -45,7 +45,7 @@ import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton'; import { ReactComponent as ChildLinkIcon } from 'assets/icons/link-child.svg'; import { ReactComponent as ParentLinkIcon } from 'assets/icons/link-parent.svg'; -import { ChildrenTooltip } from './FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/ChildrenTooltip'; +import { ChildrenTooltip } from './FeatureOverview/FeatureOverviewMetaData/ChildrenTooltip'; import copy from 'copy-to-clipboard'; import useToast from 'hooks/useToast';