diff --git a/frontend/src/assets/icons/link-child.svg b/frontend/src/assets/icons/link-child.svg new file mode 100644 index 0000000000..0f1a31432f --- /dev/null +++ b/frontend/src/assets/icons/link-child.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/icons/link-parent.svg b/frontend/src/assets/icons/link-parent.svg new file mode 100644 index 0000000000..89e31eef2d --- /dev/null +++ b/frontend/src/assets/icons/link-parent.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/component/changeRequest/ChangeRequest.test.tsx b/frontend/src/component/changeRequest/ChangeRequest.test.tsx index e90fcdf9ac..186a349de4 100644 --- a/frontend/src/component/changeRequest/ChangeRequest.test.tsx +++ b/frontend/src/component/changeRequest/ChangeRequest.test.tsx @@ -179,6 +179,8 @@ const feature = ({ name, enabled }: { name: string; enabled: boolean }) => lastSeenAt: null, type: 'release', archived: false, + dependencies: [], + children: [], }); const otherRequests = (feature: string) => { diff --git a/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx b/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx index a53f14a7be..ea2140405d 100644 --- a/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx +++ b/frontend/src/component/changeRequest/ChangeRequestPermissions.test.tsx @@ -170,6 +170,8 @@ const featureEnvironments = ( lastSeenAt: null, type: 'release', archived: false, + children: [], + dependencies: [], }); }; diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/ChildrenTooltip.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/ChildrenTooltip.tsx new file mode 100644 index 0000000000..1ff3de5e9f --- /dev/null +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/ChildrenTooltip.tsx @@ -0,0 +1,24 @@ +import { StyledLink } from './StyledRow'; +import { TooltipLink } from 'component/common/TooltipLink/TooltipLink'; +import { FC } from 'react'; + +export const ChildrenTooltip: FC<{ + childFeatures: string[]; + project: string; +}> = ({ childFeatures, project }) => ( + + {childFeatures.map(child => ( + +
{child}
+
+ ))} + + } + > + {childFeatures.length === 1 + ? '1 feature' + : `${childFeatures.length} features`} +
+); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx index 3d55e570df..97e59d5418 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/DependencyRow.tsx @@ -9,6 +9,7 @@ import { FlexRow, StyledDetail, StyledLabel, StyledLink } from './StyledRow'; import { DependencyActions } from './DependencyActions'; import { useDependentFeaturesApi } from 'hooks/api/actions/useDependentFeaturesApi/useDependentFeaturesApi'; import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; +import { ChildrenTooltip } from './ChildrenTooltip'; export const DependencyRow: FC<{ feature: IFeatureToggle }> = ({ feature }) => { const { removeDependencies } = useDependentFeaturesApi(feature.project); @@ -71,23 +72,10 @@ export const DependencyRow: FC<{ feature: IFeatureToggle }> = ({ feature }) => { Children: - - {feature.children.map(child => ( - -
{child}
-
- ))} - - } - > - {feature.children.length === 1 - ? '1 feature' - : `${feature.children.length} features`} -
+
} diff --git a/frontend/src/component/feature/FeatureView/FeatureView.tsx b/frontend/src/component/feature/FeatureView/FeatureView.tsx index 4d1fd577a5..f675873a42 100644 --- a/frontend/src/component/feature/FeatureView/FeatureView.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureView.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { styled, Tab, Tabs, useMediaQuery } from '@mui/material'; +import { styled, Tab, Tabs, useMediaQuery, Box, Card } from '@mui/material'; import { Archive, FileCopy, Label, WatchLater } from '@mui/icons-material'; import { Link, @@ -31,6 +31,11 @@ import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog'; import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/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 { TooltipLink } from '../../common/TooltipLink/TooltipLink'; +import { ChildrenTooltip } from './FeatureOverview/FeatureOverviewSidePanel/FeatureOverviewSidePanelDetails/ChildrenTooltip'; +import { useUiFlag } from '../../../hooks/useUiFlag'; const StyledHeader = styled('div')(({ theme }) => ({ backgroundColor: theme.palette.background.paper, @@ -53,6 +58,28 @@ const StyledToggleInfoContainer = styled('div')({ alignItems: 'center', }); +const StyledDependency = styled('div')(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), + marginTop: theme.spacing(1), + fontSize: theme.fontSizes.smallBody, + padding: theme.spacing(0.75, 1.5), + backgroundColor: theme.palette.background.elevation2, + borderRadius: `${theme.shape.borderRadiusMedium}px`, + width: 'max-content', +})); + +const StyleChildLinkIcon = styled(ChildLinkIcon)(({ theme }) => ({ + width: theme.fontSizes.smallBody, + height: theme.fontSizes.smallBody, +})); + +const StyledParentLinkIcon = styled(ParentLinkIcon)(({ theme }) => ({ + width: theme.fontSizes.smallBody, + height: theme.fontSizes.smallBody, +})); + const StyledFeatureViewHeader = styled('h1')(({ theme }) => ({ fontSize: theme.fontSizes.mainHeader, fontWeight: 'normal', @@ -86,12 +113,21 @@ const StyledTabButton = styled(Tab)(({ theme }) => ({ }, })); +export const StyledLink = styled(Link)(({ theme }) => ({ + maxWidth: '100%', + textDecoration: 'none', + '&:hover, &:focus': { + textDecoration: 'underline', + }, +})); + export const FeatureView = () => { const projectId = useRequiredPathParam('projectId'); const featureId = useRequiredPathParam('featureId'); const { refetch: projectRefetch } = useProject(projectId); const { favorite, unfavorite } = useFavoriteFeaturesApi(); const { refetchFeature } = useFeature(projectId, featureId); + const dependentFeatures = useUiFlag('dependentFeatures'); const [openTagDialog, setOpenTagDialog] = useState(false); const [showDelDialog, setShowDelDialog] = useState(false); @@ -157,13 +193,56 @@ export const FeatureView = () => { onClick={onFavorite} isFavorite={feature?.favorite} /> - - {feature.name}{' '} - - } - /> +
+ + + {feature.name}{' '} + + + } + /> + + 0 + } + show={ + + {' '} + Child feature + {' < '} + + {feature?.dependencies[0]?.feature} + + + } + /> + 0 + } + show={ + + {' '} + Parent feature + {' > '} + + + } + /> +