diff --git a/frontend/src/component/feature/FeatureView/FeatureView.tsx b/frontend/src/component/feature/FeatureView/FeatureView.tsx
index c1f14ffc0f6f..95b83e39f812 100644
--- a/frontend/src/component/feature/FeatureView/FeatureView.tsx
+++ b/frontend/src/component/feature/FeatureView/FeatureView.tsx
@@ -1,170 +1,15 @@
-import { type PropsWithChildren, useState, type FC } from 'react';
-import {
- IconButton,
- styled,
- Tab,
- Tabs,
- Tooltip,
- Typography,
- useMediaQuery,
-} from '@mui/material';
-import Archive from '@mui/icons-material/Archive';
-import ArchiveOutlined from '@mui/icons-material/ArchiveOutlined';
-import FileCopy from '@mui/icons-material/FileCopy';
-import FileCopyOutlined from '@mui/icons-material/FileCopyOutlined';
-import Label from '@mui/icons-material/Label';
-import WatchLater from '@mui/icons-material/WatchLater';
-import WatchLaterOutlined from '@mui/icons-material/WatchLaterOutlined';
-import LibraryAdd from '@mui/icons-material/LibraryAdd';
-import LibraryAddOutlined from '@mui/icons-material/LibraryAddOutlined';
-import Check from '@mui/icons-material/Check';
-import Star from '@mui/icons-material/Star';
-import {
- Link,
- Route,
- Routes,
- useLocation,
- useNavigate,
-} from 'react-router-dom';
+import { Link, Route, Routes } from 'react-router-dom';
import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
-import {
- CREATE_FEATURE,
- DELETE_FEATURE,
- UPDATE_FEATURE,
-} from 'component/providers/AccessProvider/permissions';
-import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
import FeatureLog from './FeatureLog/FeatureLog';
import FeatureOverview from './FeatureOverview/FeatureOverview';
import { FeatureEnvironmentVariants } from './FeatureVariants/FeatureEnvironmentVariants/FeatureEnvironmentVariants';
import { FeatureMetrics } from './FeatureMetrics/FeatureMetrics';
import { FeatureSettings } from './FeatureSettings/FeatureSettings';
import useLoading from 'hooks/useLoading';
-import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
-import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog';
-import { ManageTagsDialog } from './FeatureOverview/ManageTagsDialog/ManageTagsDialog';
-import { FeatureStatusChip } from 'component/common/FeatureStatusChip/FeatureStatusChip';
import { FeatureNotFound } from 'component/feature/FeatureView/FeatureNotFound/FeatureNotFound';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
-import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
-import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
-import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
-import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton';
-import { ChildrenTooltip } from './FeatureOverview/FeatureOverviewMetaData/ChildrenTooltip';
-import copy from 'copy-to-clipboard';
-import useToast from 'hooks/useToast';
-import { useUiFlag } from 'hooks/useUiFlag';
-import type { IFeatureToggle } from 'interfaces/featureToggle';
-import { Collaborators } from './Collaborators';
-import StarBorder from '@mui/icons-material/StarBorder';
-import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
-
-const NewStyledHeader = styled('div')(({ theme }) => ({
- backgroundColor: 'none',
- marginBottom: theme.spacing(2),
- borderBottom: `1px solid ${theme.palette.divider}`,
-}));
-
-const LowerHeaderRow = styled('div')(({ theme }) => ({
- display: 'flex',
- flexFlow: 'row nowrap',
- justifyContent: 'space-between',
- gap: theme.spacing(4),
-}));
-
-const HeaderActions = styled('div')(({ theme }) => ({
- display: 'flex',
- flexFlow: 'row nowrap',
- alignItems: 'center',
-}));
-
-const IconButtonWithTooltip: FC<
- PropsWithChildren<{
- onClick: () => void;
- label: string;
- }>
-> = ({ children, label, onClick }) => {
- return (
- e.preventDefault()}
- >
-
- {children}
-
-
- );
-};
-
-const StyledHeader = styled('div')(({ theme }) => ({
- backgroundColor: theme.palette.background.paper,
- borderRadius: theme.shape.borderRadiusLarge,
- marginBottom: theme.spacing(2),
-}));
-
-const StyledInnerContainer = styled('div')(({ theme }) => ({
- padding: theme.spacing(2, 4, 2, 2),
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- [theme.breakpoints.down(500)]: {
- flexDirection: 'column',
- },
-}));
-
-const StyledFlagInfoContainer = styled('div')({
- display: 'flex',
- 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 StyledFeatureViewHeader = styled('h1')(({ theme }) => ({
- fontSize: theme.fontSizes.mainHeader,
- fontWeight: 'normal',
- display: 'flex',
- alignItems: 'center',
- wordBreak: 'break-all',
-}));
-
-const StyledToolbarContainer = styled('div')({
- flexShrink: 0,
- display: 'flex',
-});
-
-const StyledSeparator = styled('div')(({ theme }) => ({
- width: '100%',
- backgroundColor: theme.palette.divider,
- height: '1px',
-}));
-
-const StyledTabRow = styled('div')(({ theme }) => ({
- display: 'flex',
- flexFlow: 'row nowrap',
- gap: theme.spacing(4),
- paddingInline: theme.spacing(4),
- justifyContent: 'space-between',
-}));
-
-const StyledTabButton = styled(Tab)(({ theme }) => ({
- textTransform: 'none',
- width: 'auto',
- fontSize: theme.fontSizes.bodySize,
- padding: '0 !important',
- [theme.breakpoints.up('md')]: {
- minWidth: 160,
- },
-}));
+import { FeatureViewHeader } from './FeatureViewHeader';
+import { styled } from '@mui/material';
export const StyledLink = styled(Link)(() => ({
maxWidth: '100%',
@@ -174,85 +19,17 @@ export const StyledLink = styled(Link)(() => ({
},
}));
-const useLegacyVariants = (environments: IFeatureToggle['environments']) => {
- const enableLegacyVariants = useUiFlag('enableLegacyVariants');
- const existingLegacyVariantsExist = environments.some(
- (environment) => environment.variants?.length,
- );
- return enableLegacyVariants || existingLegacyVariantsExist;
-};
-
export const FeatureView = () => {
const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId');
- const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
- const { favorite, unfavorite } = useFavoriteFeaturesApi();
- const { refetchFeature } = useFeature(projectId, featureId);
- const { setToastData, setToastApiError } = useToast();
-
- const [openTagDialog, setOpenTagDialog] = useState(false);
- const [showDelDialog, setShowDelDialog] = useState(false);
- const [openStaleDialog, setOpenStaleDialog] = useState(false);
- const [isFeatureNameCopied, setIsFeatureNameCopied] = useState(false);
- const smallScreen = useMediaQuery(`(max-width:${500}px)`);
const { feature, loading, error, status } = useFeature(
projectId,
featureId,
);
- const navigate = useNavigate();
- const { pathname } = useLocation();
const ref = useLoading(loading);
- const basePath = `/projects/${projectId}/features/${featureId}`;
-
- const showLegacyVariants = useLegacyVariants(feature.environments);
-
- const tabData = [
- {
- title: 'Overview',
- path: `${basePath}`,
- name: 'overview',
- },
- {
- title: 'Metrics',
- path: `${basePath}/metrics`,
- name: 'Metrics',
- },
- ...(showLegacyVariants
- ? [
- {
- title: 'Variants',
- path: `${basePath}/variants`,
- name: 'Variants',
- },
- ]
- : []),
- { title: 'Settings', path: `${basePath}/settings`, name: 'Settings' },
- {
- title: 'Event log',
- path: `${basePath}/logs`,
- name: 'Event log',
- },
- ];
-
- const activeTab =
- tabData.find((tab) => tab.path === pathname) ?? tabData[0];
-
- const onFavorite = async () => {
- try {
- if (feature?.favorite) {
- await unfavorite(projectId, feature.name);
- } else {
- await favorite(projectId, feature.name);
- }
- refetchFeature();
- } catch (error) {
- setToastApiError('Something went wrong, could not update favorite');
- }
- };
-
if (status === 404) {
return ;
}
@@ -261,245 +38,9 @@ export const FeatureView = () => {
return
;
}
- const handleCopyToClipboard = () => {
- try {
- copy(feature.name);
- setIsFeatureNameCopied(true);
- setTimeout(() => {
- setIsFeatureNameCopied(false);
- }, 3000);
- } catch (error: unknown) {
- setToastData({
- type: 'error',
- text: 'Could not copy feature name',
- });
- }
- };
-
return (
- {flagOverviewRedesign ? (
-
- {feature.name}
-
-
- {tabData.map((tab) => (
- navigate(tab.path)}
- data-testid={`TAB-${tab.title}`}
- />
- ))}
-
-
-
- {feature?.favorite ? : }
-
-
-
- {isFeatureNameCopied ? (
-
- ) : (
-
- )}
-
-
-
-
-
- setShowDelDialog(true)}
- >
-
-
- setOpenStaleDialog(true)}
- permission={UPDATE_FEATURE}
- projectId={projectId}
- tooltipProps={{
- title: 'Toggle stale state',
- }}
- data-loading
- >
-
-
-
-
-
- ) : (
-
-
-
-
-
-
-
- {feature.name}{' '}
-
-
-
- {isFeatureNameCopied ? (
-
- ) : (
-
- )}
-
-
-
- }
- />
-
- 0}
- show={
-
- Has parent:
-
- {
- feature?.dependencies[0]
- ?.feature
- }
-
-
- }
- />
- 0}
- show={
-
- Has children:
-
-
- }
- />
-
-
-
-
-
-
-
- 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)}
- data-testid={`TAB-${tab.title}`}
- />
- ))}
-
-
-
-
- )}
+
} />
} />
@@ -510,40 +51,6 @@ export const FeatureView = () => {
} />
} />
-
0}
- show={
- setShowDelDialog(false)}
- />
- }
- elseShow={
- {
- navigate(`/projects/${projectId}`);
- }}
- onClose={() => setShowDelDialog(false)}
- projectId={projectId}
- featureIds={[featureId]}
- />
- }
- />
-
- {
- setOpenStaleDialog(false);
- refetchFeature();
- }}
- featureId={featureId}
- projectId={projectId}
- />
-
);
};
diff --git a/frontend/src/component/feature/FeatureView/FeatureViewHeader.tsx b/frontend/src/component/feature/FeatureView/FeatureViewHeader.tsx
new file mode 100644
index 000000000000..2c502496cbe7
--- /dev/null
+++ b/frontend/src/component/feature/FeatureView/FeatureViewHeader.tsx
@@ -0,0 +1,515 @@
+import { type PropsWithChildren, useState, type FC } from 'react';
+import {
+ IconButton,
+ styled,
+ Tab,
+ Tabs,
+ Tooltip,
+ Typography,
+ useMediaQuery,
+} from '@mui/material';
+import Archive from '@mui/icons-material/Archive';
+import ArchiveOutlined from '@mui/icons-material/ArchiveOutlined';
+import FileCopy from '@mui/icons-material/FileCopy';
+import FileCopyOutlined from '@mui/icons-material/FileCopyOutlined';
+import Label from '@mui/icons-material/Label';
+import WatchLater from '@mui/icons-material/WatchLater';
+import WatchLaterOutlined from '@mui/icons-material/WatchLaterOutlined';
+import LibraryAdd from '@mui/icons-material/LibraryAdd';
+import LibraryAddOutlined from '@mui/icons-material/LibraryAddOutlined';
+import Check from '@mui/icons-material/Check';
+import Star from '@mui/icons-material/Star';
+import { Link, useLocation, useNavigate } from 'react-router-dom';
+import { useFeature } from 'hooks/api/getters/useFeature/useFeature';
+import {
+ CREATE_FEATURE,
+ DELETE_FEATURE,
+ UPDATE_FEATURE,
+} from 'component/providers/AccessProvider/permissions';
+import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton';
+import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
+import { FeatureStatusChip } from 'component/common/FeatureStatusChip/FeatureStatusChip';
+import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
+import { useFavoriteFeaturesApi } from 'hooks/api/actions/useFavoriteFeaturesApi/useFavoriteFeaturesApi';
+import { FavoriteIconButton } from 'component/common/FavoriteIconButton/FavoriteIconButton';
+import { ChildrenTooltip } from './FeatureOverview/FeatureOverviewMetaData/ChildrenTooltip';
+import copy from 'copy-to-clipboard';
+import useToast from 'hooks/useToast';
+import { useUiFlag } from 'hooks/useUiFlag';
+import type { IFeatureToggle } from 'interfaces/featureToggle';
+import { Collaborators } from './Collaborators';
+import StarBorder from '@mui/icons-material/StarBorder';
+import { TooltipResolver } from 'component/common/TooltipResolver/TooltipResolver';
+import { ManageTagsDialog } from './FeatureOverview/ManageTagsDialog/ManageTagsDialog';
+import { FeatureStaleDialog } from 'component/common/FeatureStaleDialog/FeatureStaleDialog';
+import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog';
+import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog';
+
+const NewStyledHeader = styled('div')(({ theme }) => ({
+ backgroundColor: 'none',
+ marginBottom: theme.spacing(2),
+ borderBottom: `1px solid ${theme.palette.divider}`,
+}));
+
+const LowerHeaderRow = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexFlow: 'row wrap',
+ justifyContent: 'space-between',
+ columnGap: theme.spacing(4),
+}));
+
+const HeaderActions = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexFlow: 'row nowrap',
+ alignItems: 'center',
+}));
+
+const IconButtonWithTooltip: FC<
+ PropsWithChildren<{
+ onClick: () => void;
+ label: string;
+ }>
+> = ({ children, label, onClick }) => {
+ return (
+ e.preventDefault()}
+ >
+
+ {children}
+
+
+ );
+};
+
+const StyledHeader = styled('div')(({ theme }) => ({
+ backgroundColor: theme.palette.background.paper,
+ borderRadius: theme.shape.borderRadiusLarge,
+ marginBottom: theme.spacing(2),
+}));
+
+const StyledInnerContainer = styled('div')(({ theme }) => ({
+ padding: theme.spacing(2, 4, 2, 2),
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ [theme.breakpoints.down(500)]: {
+ flexDirection: 'column',
+ },
+}));
+
+const StyledFlagInfoContainer = styled('div')({
+ display: 'flex',
+ 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 StyledFeatureViewHeader = styled('h1')(({ theme }) => ({
+ fontSize: theme.fontSizes.mainHeader,
+ fontWeight: 'normal',
+ display: 'flex',
+ alignItems: 'center',
+ wordBreak: 'break-all',
+}));
+
+const StyledToolbarContainer = styled('div')({
+ flexShrink: 0,
+ display: 'flex',
+});
+
+const StyledSeparator = styled('div')(({ theme }) => ({
+ width: '100%',
+ backgroundColor: theme.palette.divider,
+ height: '1px',
+}));
+
+const StyledTabRow = styled('div')(({ theme }) => ({
+ display: 'flex',
+ flexFlow: 'row nowrap',
+ gap: theme.spacing(4),
+ paddingInline: theme.spacing(4),
+ justifyContent: 'space-between',
+}));
+
+const StyledTabButton = styled(Tab)(({ theme }) => ({
+ textTransform: 'none',
+ width: 'auto',
+ fontSize: theme.fontSizes.bodySize,
+ padding: '0 !important',
+ [theme.breakpoints.up('md')]: {
+ minWidth: 160,
+ },
+}));
+
+export const StyledLink = styled(Link)(() => ({
+ maxWidth: '100%',
+ textDecoration: 'none',
+ '&:hover, &:focus': {
+ textDecoration: 'underline',
+ },
+}));
+
+const useLegacyVariants = (environments: IFeatureToggle['environments']) => {
+ const enableLegacyVariants = useUiFlag('enableLegacyVariants');
+ const existingLegacyVariantsExist = environments.some(
+ (environment) => environment.variants?.length,
+ );
+ return enableLegacyVariants || existingLegacyVariantsExist;
+};
+
+type Props = {
+ feature: IFeatureToggle;
+};
+
+export const FeatureViewHeader: FC = ({ feature }) => {
+ const projectId = useRequiredPathParam('projectId');
+ const featureId = useRequiredPathParam('featureId');
+ const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
+ const { favorite, unfavorite } = useFavoriteFeaturesApi();
+ const { refetchFeature } = useFeature(projectId, featureId);
+ const { setToastData, setToastApiError } = useToast();
+
+ const [openTagDialog, setOpenTagDialog] = useState(false);
+ const [showDelDialog, setShowDelDialog] = useState(false);
+ const [openStaleDialog, setOpenStaleDialog] = useState(false);
+
+ const [isFeatureNameCopied, setIsFeatureNameCopied] = useState(false);
+ const smallScreen = useMediaQuery(`(max-width:${500}px)`);
+
+ const navigate = useNavigate();
+
+ const { pathname } = useLocation();
+
+ const basePath = `/projects/${projectId}/features/${featureId}`;
+
+ const showLegacyVariants = useLegacyVariants(feature.environments);
+
+ const tabData = [
+ {
+ title: 'Overview',
+ path: `${basePath}`,
+ name: 'overview',
+ },
+ {
+ title: 'Metrics',
+ path: `${basePath}/metrics`,
+ name: 'Metrics',
+ },
+ ...(showLegacyVariants
+ ? [
+ {
+ title: 'Variants',
+ path: `${basePath}/variants`,
+ name: 'Variants',
+ },
+ ]
+ : []),
+ { title: 'Settings', path: `${basePath}/settings`, name: 'Settings' },
+ {
+ title: 'Event log',
+ path: `${basePath}/logs`,
+ name: 'Event log',
+ },
+ ];
+
+ const activeTab =
+ tabData.find((tab) => tab.path === pathname) ?? tabData[0];
+
+ const onFavorite = async () => {
+ try {
+ if (feature.favorite) {
+ await unfavorite(projectId, feature.name);
+ } else {
+ await favorite(projectId, feature.name);
+ }
+ refetchFeature();
+ } catch (error) {
+ setToastApiError('Something went wrong, could not update favorite');
+ }
+ };
+
+ const handleCopyToClipboard = () => {
+ try {
+ copy(feature.name);
+ setIsFeatureNameCopied(true);
+ setTimeout(() => {
+ setIsFeatureNameCopied(false);
+ }, 3000);
+ } catch (error: unknown) {
+ setToastData({
+ type: 'error',
+ text: 'Could not copy feature name',
+ });
+ }
+ };
+
+ return (
+ <>
+ {flagOverviewRedesign ? (
+
+ {feature.name}
+
+
+ {tabData.map((tab) => (
+ navigate(tab.path)}
+ data-testid={`TAB-${tab.title}`}
+ />
+ ))}
+
+
+
+ {feature.favorite ? : }
+
+
+
+ {isFeatureNameCopied ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ setShowDelDialog(true)}
+ >
+
+
+ setOpenStaleDialog(true)}
+ permission={UPDATE_FEATURE}
+ projectId={projectId}
+ tooltipProps={{
+ title: 'Toggle stale state',
+ }}
+ data-loading
+ >
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+
+ {feature.name}
+
+
+
+ {isFeatureNameCopied ? (
+
+ ) : (
+
+ )}
+
+
+
+ }
+ />
+
+ 0}
+ show={
+
+ Has parent:
+
+ {
+ feature.dependencies[0]
+ ?.feature
+ }
+
+
+ }
+ />
+ 0}
+ show={
+
+ Has children:
+
+
+ }
+ />
+
+
+
+
+
+
+
+ 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)}
+ data-testid={`TAB-${tab.title}`}
+ />
+ ))}
+
+
+
+
+ )}
+
+ {feature.children.length > 0 ? (
+ setShowDelDialog(false)}
+ />
+ ) : (
+ {
+ navigate(`/projects/${projectId}`);
+ }}
+ onClose={() => setShowDelDialog(false)}
+ projectId={projectId}
+ featureIds={[featureId]}
+ />
+ )}
+
+ {
+ setOpenStaleDialog(false);
+ refetchFeature();
+ }}
+ featureId={featureId}
+ projectId={projectId}
+ />
+
+ >
+ );
+};