1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

feat: new flag header (#9302)

Initial spike to add the new design for the flag page header
This commit is contained in:
Thomas Heartman 2025-02-14 14:33:35 +01:00 committed by GitHub
parent 8dc6fbf149
commit aafacc68cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 263 additions and 132 deletions

View File

@ -1,18 +1,24 @@
import { useState } from 'react'; import { type PropsWithChildren, useState, type FC } from 'react';
import { import {
IconButton, IconButton,
styled, styled,
Tab, Tab,
Tabs, Tabs,
Tooltip, Tooltip,
Typography,
useMediaQuery, useMediaQuery,
} from '@mui/material'; } from '@mui/material';
import Archive from '@mui/icons-material/Archive'; import Archive from '@mui/icons-material/Archive';
import ArchiveOutlined from '@mui/icons-material/ArchiveOutlined';
import FileCopy from '@mui/icons-material/FileCopy'; import FileCopy from '@mui/icons-material/FileCopy';
import FileCopyOutlined from '@mui/icons-material/FileCopyOutlined';
import Label from '@mui/icons-material/Label'; import Label from '@mui/icons-material/Label';
import WatchLater from '@mui/icons-material/WatchLater'; import WatchLater from '@mui/icons-material/WatchLater';
import WatchLaterOutlined from '@mui/icons-material/WatchLaterOutlined';
import LibraryAdd from '@mui/icons-material/LibraryAdd'; import LibraryAdd from '@mui/icons-material/LibraryAdd';
import LibraryAddOutlined from '@mui/icons-material/LibraryAddOutlined';
import Check from '@mui/icons-material/Check'; import Check from '@mui/icons-material/Check';
import Star from '@mui/icons-material/Star';
import { import {
Link, Link,
Route, Route,
@ -49,6 +55,46 @@ import useToast from 'hooks/useToast';
import { useUiFlag } from 'hooks/useUiFlag'; import { useUiFlag } from 'hooks/useUiFlag';
import type { IFeatureToggle } from 'interfaces/featureToggle'; import type { IFeatureToggle } from 'interfaces/featureToggle';
import { Collaborators } from './Collaborators'; 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 (
<TooltipResolver
title={label}
arrow
onClick={(e) => e.preventDefault()}
>
<IconButton aria-label={label} onClick={onClick}>
{children}
</IconButton>
</TooltipResolver>
);
};
const StyledHeader = styled('div')(({ theme }) => ({ const StyledHeader = styled('div')(({ theme }) => ({
backgroundColor: theme.palette.background.paper, backgroundColor: theme.palette.background.paper,
@ -139,6 +185,7 @@ const useLegacyVariants = (environments: IFeatureToggle['environments']) => {
export const FeatureView = () => { export const FeatureView = () => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const featureId = useRequiredPathParam('featureId'); const featureId = useRequiredPathParam('featureId');
const flagOverviewRedesign = useUiFlag('flagOverviewRedesign');
const { favorite, unfavorite } = useFavoriteFeaturesApi(); const { favorite, unfavorite } = useFavoriteFeaturesApi();
const { refetchFeature } = useFeature(projectId, featureId); const { refetchFeature } = useFeature(projectId, featureId);
const { setToastData, setToastApiError } = useToast(); const { setToastData, setToastApiError } = useToast();
@ -231,144 +278,228 @@ export const FeatureView = () => {
return ( return (
<div ref={ref}> <div ref={ref}>
<StyledHeader> {flagOverviewRedesign ? (
<StyledInnerContainer> <NewStyledHeader>
<StyledFlagInfoContainer> <Typography variant='h1'>{feature.name}</Typography>
<FavoriteIconButton <LowerHeaderRow>
onClick={onFavorite} <Tabs
isFavorite={feature?.favorite} value={activeTab.path}
/> indicatorColor='primary'
<div> textColor='primary'
<StyledFlagInfoContainer> >
<StyledFeatureViewHeader data-loading> {tabData.map((tab) => (
{feature.name}{' '} <StyledTabButton
</StyledFeatureViewHeader> key={tab.title}
<Tooltip label={tab.title}
title={ value={tab.path}
isFeatureNameCopied onClick={() => navigate(tab.path)}
? 'Copied!' data-testid={`TAB-${tab.title}`}
: 'Copy name' />
} ))}
arrow </Tabs>
> <HeaderActions>
<IconButton <IconButtonWithTooltip
onClick={handleCopyToClipboard} label='Favorite this feature flag'
style={{ marginLeft: 8 }} onClick={onFavorite}
data-loading
>
{feature?.favorite ? <Star /> : <StarBorder />}
</IconButtonWithTooltip>
<IconButtonWithTooltip
label='Copy flag name'
onClick={handleCopyToClipboard}
data-loading
>
{isFeatureNameCopied ? (
<Check />
) : (
<FileCopyOutlined />
)}
</IconButtonWithTooltip>
<PermissionIconButton
permission={CREATE_FEATURE}
projectId={projectId}
data-loading
component={Link}
to={`/projects/${projectId}/features/${featureId}/copy`}
tooltipProps={{
title: 'Clone',
}}
>
<LibraryAddOutlined />
</PermissionIconButton>
<PermissionIconButton
permission={DELETE_FEATURE}
projectId={projectId}
tooltipProps={{
title: 'Archive feature flag',
}}
data-loading
onClick={() => setShowDelDialog(true)}
>
<ArchiveOutlined />
</PermissionIconButton>
<PermissionIconButton
onClick={() => setOpenStaleDialog(true)}
permission={UPDATE_FEATURE}
projectId={projectId}
tooltipProps={{
title: 'Toggle stale state',
}}
data-loading
>
<WatchLaterOutlined />
</PermissionIconButton>
</HeaderActions>
</LowerHeaderRow>
</NewStyledHeader>
) : (
<StyledHeader>
<StyledInnerContainer>
<StyledFlagInfoContainer>
<FavoriteIconButton
onClick={onFavorite}
isFavorite={feature?.favorite}
/>
<div>
<StyledFlagInfoContainer>
<StyledFeatureViewHeader data-loading>
{feature.name}{' '}
</StyledFeatureViewHeader>
<Tooltip
title={
isFeatureNameCopied
? 'Copied!'
: 'Copy name'
}
arrow
> >
{isFeatureNameCopied ? ( <IconButton
<Check style={{ fontSize: 16 }} /> onClick={handleCopyToClipboard}
) : ( style={{ marginLeft: 8 }}
<FileCopy >
style={{ fontSize: 16 }} {isFeatureNameCopied ? (
<Check
style={{ fontSize: 16 }}
/>
) : (
<FileCopy
style={{ fontSize: 16 }}
/>
)}
</IconButton>
</Tooltip>
<ConditionallyRender
condition={!smallScreen}
show={
<FeatureStatusChip
stale={feature?.stale}
/> />
)} }
</IconButton> />
</Tooltip> </StyledFlagInfoContainer>
<ConditionallyRender <ConditionallyRender
condition={!smallScreen} condition={feature.dependencies.length > 0}
show={ show={
<FeatureStatusChip <StyledDependency>
stale={feature?.stale} <b>Has parent: </b>
/> <StyledLink
to={`/projects/${feature.project}/features/${feature?.dependencies[0]?.feature}`}
>
{
feature?.dependencies[0]
?.feature
}
</StyledLink>
</StyledDependency>
} }
/> />
</StyledFlagInfoContainer> <ConditionallyRender
<ConditionallyRender condition={feature.children.length > 0}
condition={feature.dependencies.length > 0} show={
show={ <StyledDependency>
<StyledDependency> <b>Has children:</b>
<b>Has parent: </b> <ChildrenTooltip
<StyledLink childFeatures={feature.children}
to={`/projects/${feature.project}/features/${feature?.dependencies[0]?.feature}`} project={feature.project}
> />
{feature?.dependencies[0]?.feature} </StyledDependency>
</StyledLink> }
</StyledDependency> />
} </div>
/> </StyledFlagInfoContainer>
<ConditionallyRender
condition={feature.children.length > 0}
show={
<StyledDependency>
<b>Has children:</b>
<ChildrenTooltip
childFeatures={feature.children}
project={feature.project}
/>
</StyledDependency>
}
/>
</div>
</StyledFlagInfoContainer>
<StyledToolbarContainer> <StyledToolbarContainer>
<PermissionIconButton <PermissionIconButton
permission={CREATE_FEATURE} permission={CREATE_FEATURE}
projectId={projectId} projectId={projectId}
data-loading data-loading
component={Link} component={Link}
to={`/projects/${projectId}/features/${featureId}/copy`} to={`/projects/${projectId}/features/${featureId}/copy`}
tooltipProps={{ tooltipProps={{
title: 'Clone', title: 'Clone',
}} }}
>
<LibraryAdd />
</PermissionIconButton>
<PermissionIconButton
permission={DELETE_FEATURE}
projectId={projectId}
tooltipProps={{
title: 'Archive feature flag',
}}
data-loading
onClick={() => setShowDelDialog(true)}
>
<Archive />
</PermissionIconButton>
<PermissionIconButton
onClick={() => setOpenStaleDialog(true)}
permission={UPDATE_FEATURE}
projectId={projectId}
tooltipProps={{
title: 'Toggle stale state',
}}
data-loading
>
<WatchLater />
</PermissionIconButton>
<PermissionIconButton
onClick={() => setOpenTagDialog(true)}
permission={UPDATE_FEATURE}
projectId={projectId}
tooltipProps={{ title: 'Add tag' }}
data-loading
>
<Label />
</PermissionIconButton>
</StyledToolbarContainer>
</StyledInnerContainer>
<StyledSeparator />
<StyledTabRow>
<Tabs
value={activeTab.path}
indicatorColor='primary'
textColor='primary'
> >
<LibraryAdd /> {tabData.map((tab) => (
</PermissionIconButton> <StyledTabButton
<PermissionIconButton key={tab.title}
permission={DELETE_FEATURE} label={tab.title}
projectId={projectId} value={tab.path}
tooltipProps={{ onClick={() => navigate(tab.path)}
title: 'Archive feature flag', data-testid={`TAB-${tab.title}`}
}} />
data-loading ))}
onClick={() => setShowDelDialog(true)} </Tabs>
> <Collaborators
<Archive /> collaborators={feature.collaborators?.users}
</PermissionIconButton> />
<PermissionIconButton </StyledTabRow>
onClick={() => setOpenStaleDialog(true)} </StyledHeader>
permission={UPDATE_FEATURE} )}
projectId={projectId}
tooltipProps={{
title: 'Toggle stale state',
}}
data-loading
>
<WatchLater />
</PermissionIconButton>
<PermissionIconButton
onClick={() => setOpenTagDialog(true)}
permission={UPDATE_FEATURE}
projectId={projectId}
tooltipProps={{ title: 'Add tag' }}
data-loading
>
<Label />
</PermissionIconButton>
</StyledToolbarContainer>
</StyledInnerContainer>
<StyledSeparator />
<StyledTabRow>
<Tabs
value={activeTab.path}
indicatorColor='primary'
textColor='primary'
>
{tabData.map((tab) => (
<StyledTabButton
key={tab.title}
label={tab.title}
value={tab.path}
onClick={() => navigate(tab.path)}
data-testid={`TAB-${tab.title}`}
/>
))}
</Tabs>
<Collaborators
collaborators={feature.collaborators?.users}
/>
</StyledTabRow>
</StyledHeader>
<Routes> <Routes>
<Route path='metrics' element={<FeatureMetrics />} /> <Route path='metrics' element={<FeatureMetrics />} />
<Route path='logs' element={<FeatureLog />} /> <Route path='logs' element={<FeatureLog />} />

View File

@ -52,7 +52,7 @@ process.nextTick(async () => {
releasePlans: false, releasePlans: false,
releasePlanChangeRequests: false, releasePlanChangeRequests: false,
showUserDeviceCount: true, showUserDeviceCount: true,
flagOverviewRedesign: false, flagOverviewRedesign: true,
granularAdminPermissions: true, granularAdminPermissions: true,
deltaApi: true, deltaApi: true,
uniqueSdkTracking: true, uniqueSdkTracking: true,