mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: new flag info box (#9308)
- updated spacing of elements - modified header and "flag type" - added "collaborators" - refactored tags Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
		
							parent
							
								
									b15502ec5e
								
							
						
					
					
						commit
						2ede2a6578
					
				| @ -90,7 +90,7 @@ export const AddDependencyDialogue = ({ | |||||||
|     return ( |     return ( | ||||||
|         <Dialogue |         <Dialogue | ||||||
|             open={showDependencyDialogue} |             open={showDependencyDialogue} | ||||||
|             title='Add parent feature dependency' |             title='Add parent flag dependency' | ||||||
|             onClose={onClose} |             onClose={onClose} | ||||||
|             onClick={manageDependency} |             onClick={manageDependency} | ||||||
|             primaryButtonText={ |             primaryButtonText={ | ||||||
|  | |||||||
| @ -17,7 +17,6 @@ import { useLastViewedFlags } from 'hooks/useLastViewedFlags'; | |||||||
| import { useUiFlag } from 'hooks/useUiFlag'; | import { useUiFlag } from 'hooks/useUiFlag'; | ||||||
| import OldFeatureOverviewMetaData from './FeatureOverviewMetaData/OldFeatureOverviewMetaData'; | import OldFeatureOverviewMetaData from './FeatureOverviewMetaData/OldFeatureOverviewMetaData'; | ||||||
| import { OldFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel'; | import { OldFeatureOverviewSidePanel } from 'component/feature/FeatureView/FeatureOverview/FeatureOverviewSidePanel/OldFeatureOverviewSidePanel'; | ||||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; |  | ||||||
| import { NewFeatureOverviewEnvironment } from './NewFeatureOverviewEnvironment/NewFeatureOverviewEnvironment'; | import { NewFeatureOverviewEnvironment } from './NewFeatureOverviewEnvironment/NewFeatureOverviewEnvironment'; | ||||||
| 
 | 
 | ||||||
| const StyledContainer = styled('div')(({ theme }) => ({ | const StyledContainer = styled('div')(({ theme }) => ({ | ||||||
| @ -51,39 +50,37 @@ const FeatureOverview = () => { | |||||||
|         setLastViewed({ featureId, projectId }); |         setLastViewed({ featureId, projectId }); | ||||||
|     }, [featureId]); |     }, [featureId]); | ||||||
|     const [environmentId, setEnvironmentId] = useState(''); |     const [environmentId, setEnvironmentId] = useState(''); | ||||||
| 
 |  | ||||||
|     const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); |     const flagOverviewRedesign = useUiFlag('flagOverviewRedesign'); | ||||||
|     const FeatureOverviewMetaData = flagOverviewRedesign |  | ||||||
|         ? NewFeatureOverviewMetaData |  | ||||||
|         : OldFeatureOverviewMetaData; |  | ||||||
|     const FeatureOverviewSidePanel = flagOverviewRedesign ? ( |  | ||||||
|         <NewFeatureOverviewSidePanel |  | ||||||
|             environmentId={environmentId} |  | ||||||
|             setEnvironmentId={setEnvironmentId} |  | ||||||
|         /> |  | ||||||
|     ) : ( |  | ||||||
|         <OldFeatureOverviewSidePanel |  | ||||||
|             hiddenEnvironments={hiddenEnvironments} |  | ||||||
|             setHiddenEnvironments={setHiddenEnvironments} |  | ||||||
|         /> |  | ||||||
|     ); |  | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <StyledContainer> |         <StyledContainer> | ||||||
|             <div> |             <div> | ||||||
|                 <FeatureOverviewMetaData /> |                 {flagOverviewRedesign ? ( | ||||||
|                 {FeatureOverviewSidePanel} |                     <> | ||||||
|  |                         <NewFeatureOverviewMetaData /> | ||||||
|  |                         <NewFeatureOverviewSidePanel | ||||||
|  |                             environmentId={environmentId} | ||||||
|  |                             setEnvironmentId={setEnvironmentId} | ||||||
|  |                         /> | ||||||
|  |                     </> | ||||||
|  |                 ) : ( | ||||||
|  |                     <> | ||||||
|  |                         <OldFeatureOverviewMetaData /> | ||||||
|  |                         <OldFeatureOverviewSidePanel | ||||||
|  |                             hiddenEnvironments={hiddenEnvironments} | ||||||
|  |                             setHiddenEnvironments={setHiddenEnvironments} | ||||||
|  |                         /> | ||||||
|  |                     </> | ||||||
|  |                 )} | ||||||
|             </div> |             </div> | ||||||
|             <StyledMainContent> |             <StyledMainContent> | ||||||
|                 <ConditionallyRender |                 {flagOverviewRedesign ? ( | ||||||
|                     condition={flagOverviewRedesign} |  | ||||||
|                     show={ |  | ||||||
|                     <NewFeatureOverviewEnvironment |                     <NewFeatureOverviewEnvironment | ||||||
|                         environmentId={environmentId} |                         environmentId={environmentId} | ||||||
|                     /> |                     /> | ||||||
|                     } |                 ) : ( | ||||||
|                     elseShow={<FeatureOverviewEnvironments />} |                     <FeatureOverviewEnvironments /> | ||||||
|                 /> |                 )} | ||||||
|             </StyledMainContent> |             </StyledMainContent> | ||||||
|             <Routes> |             <Routes> | ||||||
|                 <Route |                 <Route | ||||||
|  | |||||||
| @ -0,0 +1,35 @@ | |||||||
|  | import type { FC } from 'react'; | ||||||
|  | import AddIcon from '@mui/icons-material/Add'; | ||||||
|  | import { styled } from '@mui/material'; | ||||||
|  | import PermissionButton from 'component/common/PermissionButton/PermissionButton'; | ||||||
|  | import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; | ||||||
|  | 
 | ||||||
|  | const StyledAddTagButton = styled(PermissionButton)(({ theme }) => ({ | ||||||
|  |     lineHeight: theme.typography.body1.lineHeight, | ||||||
|  |     borderRadius: theme.shape.borderRadiusExtraLarge, | ||||||
|  |     background: theme.palette.secondary.light, | ||||||
|  |     padding: theme.spacing(0.5, 1), | ||||||
|  |     height: theme.spacing(3.5), | ||||||
|  | })); | ||||||
|  | 
 | ||||||
|  | const StyledAddIcon = styled(AddIcon)(({ theme }) => ({ | ||||||
|  |     fontSize: theme.typography.body2.fontSize, | ||||||
|  | })); | ||||||
|  | 
 | ||||||
|  | type AddTagButtonProps = { | ||||||
|  |     project: string; | ||||||
|  |     onClick: () => void; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const AddTagButton: FC<AddTagButtonProps> = ({ project, onClick }) => ( | ||||||
|  |     <StyledAddTagButton | ||||||
|  |         size='small' | ||||||
|  |         permission={UPDATE_FEATURE} | ||||||
|  |         projectId={project} | ||||||
|  |         variant='text' | ||||||
|  |         onClick={onClick} | ||||||
|  |         startIcon={<StyledAddIcon />} | ||||||
|  |     > | ||||||
|  |         Add tag | ||||||
|  |     </StyledAddTagButton> | ||||||
|  | ); | ||||||
| @ -0,0 +1,34 @@ | |||||||
|  | import { styled } from '@mui/material'; | ||||||
|  | import { | ||||||
|  |     AvatarComponent, | ||||||
|  |     AvatarGroup, | ||||||
|  | } from 'component/common/AvatarGroup/AvatarGroup'; | ||||||
|  | import type { Collaborator } from 'interfaces/featureToggle'; | ||||||
|  | import type { FC } from 'react'; | ||||||
|  | 
 | ||||||
|  | const StyledAvatarComponent = styled(AvatarComponent)(({ theme }) => ({ | ||||||
|  |     width: theme.spacing(2.5), | ||||||
|  |     height: theme.spacing(2.5), | ||||||
|  | })); | ||||||
|  | 
 | ||||||
|  | const StyledAvatarGroup = styled(AvatarGroup)({ | ||||||
|  |     flexWrap: 'nowrap', | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | type CollaboratorsProps = { | ||||||
|  |     collaborators: Collaborator[] | undefined; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const Collaborators: FC<CollaboratorsProps> = ({ collaborators }) => { | ||||||
|  |     if (!collaborators || collaborators.length === 0) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <StyledAvatarGroup | ||||||
|  |             users={collaborators} | ||||||
|  |             avatarLimit={9} | ||||||
|  |             AvatarComponent={StyledAvatarComponent} | ||||||
|  |         /> | ||||||
|  |     ); | ||||||
|  | }; | ||||||
| @ -1,4 +1,3 @@ | |||||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; |  | ||||||
| import { AddDependencyDialogue } from 'component/feature/Dependencies/AddDependencyDialogue'; | import { AddDependencyDialogue } from 'component/feature/Dependencies/AddDependencyDialogue'; | ||||||
| import type { IFeatureToggle } from 'interfaces/featureToggle'; | import type { IFeatureToggle } from 'interfaces/featureToggle'; | ||||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||||
| @ -26,11 +25,8 @@ import { | |||||||
| } from './FeatureOverviewMetaData'; | } from './FeatureOverviewMetaData'; | ||||||
| 
 | 
 | ||||||
| const StyledPermissionButton = styled(PermissionButton)(({ theme }) => ({ | const StyledPermissionButton = styled(PermissionButton)(({ theme }) => ({ | ||||||
|     '&&&': { |  | ||||||
|     fontSize: theme.fontSizes.smallBody, |     fontSize: theme.fontSizes.smallBody, | ||||||
|         lineHeight: 1, |     lineHeight: theme.typography.body1.lineHeight, | ||||||
|         margin: 0, |  | ||||||
|     }, |  | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const useDeleteDependency = (project: string, featureId: string) => { | const useDeleteDependency = (project: string, featureId: string) => { | ||||||
| @ -112,13 +108,12 @@ export const DependencyRow = ({ feature }: IDependencyRowProps) => { | |||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|             <ConditionallyRender |             {canAddParentDependency ? ( | ||||||
|                 condition={canAddParentDependency} |  | ||||||
|                 show={ |  | ||||||
|                 <StyledMetaDataItem> |                 <StyledMetaDataItem> | ||||||
|                     <StyledMetaDataItemLabel> |                     <StyledMetaDataItemLabel> | ||||||
|                         Dependency: |                         Dependency: | ||||||
|                     </StyledMetaDataItemLabel> |                     </StyledMetaDataItemLabel> | ||||||
|  |                     <div> | ||||||
|                         <StyledPermissionButton |                         <StyledPermissionButton | ||||||
|                             size='small' |                             size='small' | ||||||
|                             permission={UPDATE_FEATURE_DEPENDENCY} |                             permission={UPDATE_FEATURE_DEPENDENCY} | ||||||
| @ -128,14 +123,12 @@ export const DependencyRow = ({ feature }: IDependencyRowProps) => { | |||||||
|                                 setShowDependencyDialogue(true); |                                 setShowDependencyDialogue(true); | ||||||
|                             }} |                             }} | ||||||
|                         > |                         > | ||||||
|                             Add parent feature |                             Add parent flag | ||||||
|                         </StyledPermissionButton> |                         </StyledPermissionButton> | ||||||
|  |                     </div> | ||||||
|                 </StyledMetaDataItem> |                 </StyledMetaDataItem> | ||||||
|                 } |             ) : null} | ||||||
|             /> |             {hasParentDependency ? ( | ||||||
|             <ConditionallyRender |  | ||||||
|                 condition={hasParentDependency} |  | ||||||
|                 show={ |  | ||||||
|                 <StyledMetaDataItem> |                 <StyledMetaDataItem> | ||||||
|                     <StyledMetaDataItemLabel> |                     <StyledMetaDataItemLabel> | ||||||
|                         Dependency: |                         Dependency: | ||||||
| @ -146,44 +139,26 @@ export const DependencyRow = ({ feature }: IDependencyRowProps) => { | |||||||
|                         > |                         > | ||||||
|                             {feature.dependencies[0]?.feature} |                             {feature.dependencies[0]?.feature} | ||||||
|                         </StyledLink> |                         </StyledLink> | ||||||
|                             <ConditionallyRender |                         {checkAccess(UPDATE_FEATURE_DEPENDENCY, environment) ? ( | ||||||
|                                 condition={checkAccess( |  | ||||||
|                                     UPDATE_FEATURE_DEPENDENCY, |  | ||||||
|                                     environment, |  | ||||||
|                                 )} |  | ||||||
|                                 show={ |  | ||||||
|                             <DependencyActions |                             <DependencyActions | ||||||
|                                 feature={feature.name} |                                 feature={feature.name} | ||||||
|                                         onEdit={() => |                                 onEdit={() => setShowDependencyDialogue(true)} | ||||||
|                                             setShowDependencyDialogue(true) |  | ||||||
|                                         } |  | ||||||
|                                 onDelete={deleteDependency} |                                 onDelete={deleteDependency} | ||||||
|                             /> |                             /> | ||||||
|                                 } |                         ) : null} | ||||||
|                             /> |  | ||||||
|                     </StyledMetaDataItemValue> |                     </StyledMetaDataItemValue> | ||||||
|                 </StyledMetaDataItem> |                 </StyledMetaDataItem> | ||||||
|                 } |             ) : null} | ||||||
|             /> |             {hasParentDependency && !feature.dependencies[0]?.enabled ? ( | ||||||
|             <ConditionallyRender |  | ||||||
|                 condition={ |  | ||||||
|                     hasParentDependency && !feature.dependencies[0]?.enabled |  | ||||||
|                 } |  | ||||||
|                 show={ |  | ||||||
|                 <StyledMetaDataItem> |                 <StyledMetaDataItem> | ||||||
|                     <StyledMetaDataItemLabel> |                     <StyledMetaDataItemLabel> | ||||||
|                         Dependency value: |                         Dependency value: | ||||||
|                     </StyledMetaDataItemLabel> |                     </StyledMetaDataItemLabel> | ||||||
|                     <span>disabled</span> |                     <span>disabled</span> | ||||||
|                 </StyledMetaDataItem> |                 </StyledMetaDataItem> | ||||||
|                 } |             ) : null} | ||||||
|             /> |             {hasParentDependency && | ||||||
|             <ConditionallyRender |             Boolean(feature.dependencies[0]?.variants?.length) ? ( | ||||||
|                 condition={ |  | ||||||
|                     hasParentDependency && |  | ||||||
|                     Boolean(feature.dependencies[0]?.variants?.length) |  | ||||||
|                 } |  | ||||||
|                 show={ |  | ||||||
|                 <StyledMetaDataItem> |                 <StyledMetaDataItem> | ||||||
|                     <StyledMetaDataItemLabel> |                     <StyledMetaDataItemLabel> | ||||||
|                         Dependency value: |                         Dependency value: | ||||||
| @ -192,26 +167,17 @@ export const DependencyRow = ({ feature }: IDependencyRowProps) => { | |||||||
|                         variants={feature.dependencies[0]?.variants || []} |                         variants={feature.dependencies[0]?.variants || []} | ||||||
|                     /> |                     /> | ||||||
|                 </StyledMetaDataItem> |                 </StyledMetaDataItem> | ||||||
|                 } |             ) : null} | ||||||
|             /> |             {hasChildren ? ( | ||||||
|             <ConditionallyRender |  | ||||||
|                 condition={hasChildren} |  | ||||||
|                 show={ |  | ||||||
|                 <StyledMetaDataItem> |                 <StyledMetaDataItem> | ||||||
|                         <StyledMetaDataItemLabel> |                     <StyledMetaDataItemLabel>Children:</StyledMetaDataItemLabel> | ||||||
|                             Children: |  | ||||||
|                         </StyledMetaDataItemLabel> |  | ||||||
|                     <ChildrenTooltip |                     <ChildrenTooltip | ||||||
|                         childFeatures={feature.children} |                         childFeatures={feature.children} | ||||||
|                         project={feature.project} |                         project={feature.project} | ||||||
|                     /> |                     /> | ||||||
|                 </StyledMetaDataItem> |                 </StyledMetaDataItem> | ||||||
|                 } |             ) : null} | ||||||
|             /> |             {feature.project ? ( | ||||||
| 
 |  | ||||||
|             <ConditionallyRender |  | ||||||
|                 condition={Boolean(feature.project)} |  | ||||||
|                 show={ |  | ||||||
|                 <AddDependencyDialogue |                 <AddDependencyDialogue | ||||||
|                     project={feature.project} |                     project={feature.project} | ||||||
|                     featureId={feature.name} |                     featureId={feature.name} | ||||||
| @ -219,8 +185,7 @@ export const DependencyRow = ({ feature }: IDependencyRowProps) => { | |||||||
|                     onClose={() => setShowDependencyDialogue(false)} |                     onClose={() => setShowDependencyDialogue(false)} | ||||||
|                     showDependencyDialogue={showDependencyDialogue} |                     showDependencyDialogue={showDependencyDialogue} | ||||||
|                 /> |                 /> | ||||||
|                 } |             ) : null} | ||||||
|             /> |  | ||||||
|         </> |         </> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -97,11 +97,11 @@ test('show dependency dialogue', async () => { | |||||||
|         }, |         }, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     const addParentButton = await screen.findByText('Add parent feature'); |     const addParentButton = await screen.findByText('Add parent flag'); | ||||||
| 
 | 
 | ||||||
|     addParentButton.click(); |     addParentButton.click(); | ||||||
| 
 | 
 | ||||||
|     await screen.findByText('Add parent feature dependency'); |     await screen.findByText('Add parent flag dependency'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('show dependency dialogue for OSS with dependencies', async () => { | test('show dependency dialogue for OSS with dependencies', async () => { | ||||||
| @ -127,11 +127,11 @@ test('show dependency dialogue for OSS with dependencies', async () => { | |||||||
|         }, |         }, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     const addParentButton = await screen.findByText('Add parent feature'); |     const addParentButton = await screen.findByText('Add parent flag'); | ||||||
| 
 | 
 | ||||||
|     addParentButton.click(); |     addParentButton.click(); | ||||||
| 
 | 
 | ||||||
|     await screen.findByText('Add parent feature dependency'); |     await screen.findByText('Add parent flag dependency'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('show child', async () => { | test('show child', async () => { | ||||||
| @ -291,7 +291,7 @@ test('edit dependency', async () => { | |||||||
|     const editButton = await screen.findByText('Edit'); |     const editButton = await screen.findByText('Edit'); | ||||||
|     fireEvent.click(editButton); |     fireEvent.click(editButton); | ||||||
| 
 | 
 | ||||||
|     await screen.findByText('Add parent feature dependency'); |     await screen.findByText('Add parent flag dependency'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('show variant dependencies', async () => { | test('show variant dependencies', async () => { | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| import { capitalize, styled } from '@mui/material'; | import { styled } from '@mui/material'; | ||||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||||
| import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; | import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; | ||||||
| import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons'; |  | ||||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; |  | ||||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||||
| import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog'; | import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog'; | ||||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||||
| @ -14,8 +12,9 @@ import { useLocationSettings } from 'hooks/useLocationSettings'; | |||||||
| import { useShowDependentFeatures } from './useShowDependentFeatures'; | import { useShowDependentFeatures } from './useShowDependentFeatures'; | ||||||
| import { FeatureLifecycle } from '../FeatureLifecycle/FeatureLifecycle'; | import { FeatureLifecycle } from '../FeatureLifecycle/FeatureLifecycle'; | ||||||
| import { MarkCompletedDialogue } from '../FeatureLifecycle/MarkCompletedDialogue'; | import { MarkCompletedDialogue } from '../FeatureLifecycle/MarkCompletedDialogue'; | ||||||
| import { UserAvatar } from 'component/common/UserAvatar/UserAvatar'; |  | ||||||
| import { TagRow } from './TagRow'; | import { TagRow } from './TagRow'; | ||||||
|  | import { capitalizeFirst } from 'utils/capitalizeFirst'; | ||||||
|  | import { Collaborators } from './Collaborators'; | ||||||
| 
 | 
 | ||||||
| const StyledMetaDataContainer = styled('div')(({ theme }) => ({ | const StyledMetaDataContainer = styled('div')(({ theme }) => ({ | ||||||
|     padding: theme.spacing(3), |     padding: theme.spacing(3), | ||||||
| @ -30,22 +29,10 @@ const StyledMetaDataContainer = styled('div')(({ theme }) => ({ | |||||||
|     }, |     }, | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const StyledMetaDataHeader = styled('div')(({ theme }) => ({ | const StyledTitle = styled('h2')(({ theme }) => ({ | ||||||
|     display: 'flex', |     fontSize: theme.typography.body1.fontSize, | ||||||
|     alignItems: 'center', |     fontWeight: theme.typography.fontWeightBold, | ||||||
|     gap: theme.spacing(2), |     marginBottom: theme.spacing(0.5), | ||||||
|     '& > svg': { |  | ||||||
|         height: theme.spacing(5), |  | ||||||
|         width: theme.spacing(5), |  | ||||||
|         padding: theme.spacing(0.5), |  | ||||||
|         backgroundColor: theme.palette.background.alternative, |  | ||||||
|         fill: theme.palette.primary.contrastText, |  | ||||||
|         borderRadius: theme.shape.borderRadiusMedium, |  | ||||||
|     }, |  | ||||||
|     '& > h2': { |  | ||||||
|         fontSize: theme.fontSizes.mainHeader, |  | ||||||
|         fontWeight: 'normal', |  | ||||||
|     }, |  | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const StyledBody = styled('div')({ | const StyledBody = styled('div')({ | ||||||
| @ -57,7 +44,7 @@ export const StyledMetaDataItem = styled('div')(({ theme }) => ({ | |||||||
|     display: 'flex', |     display: 'flex', | ||||||
|     alignItems: 'center', |     alignItems: 'center', | ||||||
|     justifyContent: 'space-between', |     justifyContent: 'space-between', | ||||||
|     minHeight: theme.spacing(4.25), |     minHeight: theme.spacing(4.5), | ||||||
|     fontSize: theme.fontSizes.smallBody, |     fontSize: theme.fontSizes.smallBody, | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| @ -76,11 +63,6 @@ export const StyledMetaDataItemValue = styled('div')(({ theme }) => ({ | |||||||
|     gap: theme.spacing(1), |     gap: theme.spacing(1), | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const StyledUserAvatar = styled(UserAvatar)(({ theme }) => ({ |  | ||||||
|     height: theme.spacing(3.5), |  | ||||||
|     width: theme.spacing(3.5), |  | ||||||
| })); |  | ||||||
| 
 |  | ||||||
| const FeatureOverviewMetaData = () => { | const FeatureOverviewMetaData = () => { | ||||||
|     const projectId = useRequiredPathParam('projectId'); |     const projectId = useRequiredPathParam('projectId'); | ||||||
|     const featureId = useRequiredPathParam('featureId'); |     const featureId = useRequiredPathParam('featureId'); | ||||||
| @ -97,37 +79,29 @@ const FeatureOverviewMetaData = () => { | |||||||
| 
 | 
 | ||||||
|     const showDependentFeatures = useShowDependentFeatures(project); |     const showDependentFeatures = useShowDependentFeatures(project); | ||||||
| 
 | 
 | ||||||
|     const FlagTypeIcon = getFeatureTypeIcons(type); |  | ||||||
| 
 |  | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|             <StyledMetaDataContainer> |             <StyledMetaDataContainer> | ||||||
|                 <StyledMetaDataHeader data-loading> |                 <div> | ||||||
|                     <FlagTypeIcon /> |                     <StyledTitle>Flag details</StyledTitle> | ||||||
|                     <h2>{capitalize(type || '')} flag</h2> |                     {description ? ( | ||||||
|                 </StyledMetaDataHeader> |  | ||||||
|                 <ConditionallyRender |  | ||||||
|                     condition={Boolean(description)} |  | ||||||
|                     show={ |  | ||||||
|                         <StyledMetaDataItem data-loading> |                         <StyledMetaDataItem data-loading> | ||||||
|                             <StyledMetaDataItemText> |                             <StyledMetaDataItemText> | ||||||
|                                 {description} |                                 {description} | ||||||
|                             </StyledMetaDataItemText> |                             </StyledMetaDataItemText> | ||||||
|                         </StyledMetaDataItem> |                         </StyledMetaDataItem> | ||||||
|                     } |                     ) : null} | ||||||
|                 /> |                 </div> | ||||||
|                 <StyledBody> |                 <StyledBody> | ||||||
|                     <StyledMetaDataItem> |                     <StyledMetaDataItem> | ||||||
|                         <StyledMetaDataItemLabel> |                         <StyledMetaDataItemLabel> | ||||||
|                             Project: |                             Flag type: | ||||||
|                         </StyledMetaDataItemLabel> |                         </StyledMetaDataItemLabel> | ||||||
|                         <StyledMetaDataItemText data-loading> |                         <StyledMetaDataItemText data-loading> | ||||||
|                             {project} |                             {capitalizeFirst(type || ' ')} flag | ||||||
|                         </StyledMetaDataItemText> |                         </StyledMetaDataItemText> | ||||||
|                     </StyledMetaDataItem> |                     </StyledMetaDataItem> | ||||||
|                     <ConditionallyRender |                     {feature.lifecycle ? ( | ||||||
|                         condition={Boolean(feature.lifecycle)} |  | ||||||
|                         show={ |  | ||||||
|                         <StyledMetaDataItem data-loading> |                         <StyledMetaDataItem data-loading> | ||||||
|                             <StyledMetaDataItemLabel> |                             <StyledMetaDataItemLabel> | ||||||
|                                 Lifecycle: |                                 Lifecycle: | ||||||
| @ -141,11 +115,10 @@ const FeatureOverviewMetaData = () => { | |||||||
|                                 onUncomplete={refetchFeature} |                                 onUncomplete={refetchFeature} | ||||||
|                             /> |                             /> | ||||||
|                         </StyledMetaDataItem> |                         </StyledMetaDataItem> | ||||||
|                         } |                     ) : null} | ||||||
|                     /> |  | ||||||
|                     <StyledMetaDataItem> |                     <StyledMetaDataItem> | ||||||
|                         <StyledMetaDataItemLabel> |                         <StyledMetaDataItemLabel> | ||||||
|                             Created at: |                             Created: | ||||||
|                         </StyledMetaDataItemLabel> |                         </StyledMetaDataItemLabel> | ||||||
|                         <StyledMetaDataItemText data-loading> |                         <StyledMetaDataItemText data-loading> | ||||||
|                             {formatDateYMD( |                             {formatDateYMD( | ||||||
| @ -154,9 +127,7 @@ const FeatureOverviewMetaData = () => { | |||||||
|                             )} |                             )} | ||||||
|                         </StyledMetaDataItemText> |                         </StyledMetaDataItemText> | ||||||
|                     </StyledMetaDataItem> |                     </StyledMetaDataItem> | ||||||
|                     <ConditionallyRender |                     {feature.createdBy ? ( | ||||||
|                         condition={Boolean(feature.createdBy)} |  | ||||||
|                         show={() => ( |  | ||||||
|                         <StyledMetaDataItem> |                         <StyledMetaDataItem> | ||||||
|                             <StyledMetaDataItemLabel> |                             <StyledMetaDataItemLabel> | ||||||
|                                 Created by: |                                 Created by: | ||||||
| @ -165,31 +136,36 @@ const FeatureOverviewMetaData = () => { | |||||||
|                                 <StyledMetaDataItemText data-loading> |                                 <StyledMetaDataItemText data-loading> | ||||||
|                                     {feature.createdBy?.name} |                                     {feature.createdBy?.name} | ||||||
|                                 </StyledMetaDataItemText> |                                 </StyledMetaDataItemText> | ||||||
|                                     <StyledUserAvatar |                             </StyledMetaDataItemValue> | ||||||
|                                         user={feature.createdBy} |                         </StyledMetaDataItem> | ||||||
|  |                     ) : null} | ||||||
|  |                     {feature.collaborators?.users && | ||||||
|  |                     feature.collaborators?.users.length > 0 ? ( | ||||||
|  |                         <StyledMetaDataItem> | ||||||
|  |                             <StyledMetaDataItemLabel> | ||||||
|  |                                 Collaborators: | ||||||
|  |                             </StyledMetaDataItemLabel> | ||||||
|  |                             <StyledMetaDataItemValue> | ||||||
|  |                                 <Collaborators | ||||||
|  |                                     collaborators={feature.collaborators?.users} | ||||||
|                                 /> |                                 /> | ||||||
|                             </StyledMetaDataItemValue> |                             </StyledMetaDataItemValue> | ||||||
|                         </StyledMetaDataItem> |                         </StyledMetaDataItem> | ||||||
|                         )} |                     ) : null} | ||||||
|                     /> |                     {showDependentFeatures ? ( | ||||||
|                     <ConditionallyRender |                         <DependencyRow feature={feature} /> | ||||||
|                         condition={showDependentFeatures} |                     ) : null} | ||||||
|                         show={<DependencyRow feature={feature} />} |  | ||||||
|                     /> |  | ||||||
|                     <TagRow feature={feature} /> |                     <TagRow feature={feature} /> | ||||||
|                 </StyledBody> |                 </StyledBody> | ||||||
|             </StyledMetaDataContainer> |             </StyledMetaDataContainer> | ||||||
|             <ConditionallyRender |             {feature.children.length > 0 ? ( | ||||||
|                 condition={feature.children.length > 0} |  | ||||||
|                 show={ |  | ||||||
|                 <FeatureArchiveNotAllowedDialog |                 <FeatureArchiveNotAllowedDialog | ||||||
|                     features={feature.children} |                     features={feature.children} | ||||||
|                     project={projectId} |                     project={projectId} | ||||||
|                     isOpen={archiveDialogOpen} |                     isOpen={archiveDialogOpen} | ||||||
|                     onClose={() => setArchiveDialogOpen(false)} |                     onClose={() => setArchiveDialogOpen(false)} | ||||||
|                 /> |                 /> | ||||||
|                 } |             ) : ( | ||||||
|                 elseShow={ |  | ||||||
|                 <FeatureArchiveDialog |                 <FeatureArchiveDialog | ||||||
|                     isOpen={archiveDialogOpen} |                     isOpen={archiveDialogOpen} | ||||||
|                     onConfirm={() => { |                     onConfirm={() => { | ||||||
| @ -199,11 +175,8 @@ const FeatureOverviewMetaData = () => { | |||||||
|                     projectId={projectId} |                     projectId={projectId} | ||||||
|                     featureIds={[featureId]} |                     featureIds={[featureId]} | ||||||
|                 /> |                 /> | ||||||
|                 } |             )} | ||||||
|             /> |             {feature.project ? ( | ||||||
|             <ConditionallyRender |  | ||||||
|                 condition={Boolean(feature.project)} |  | ||||||
|                 show={ |  | ||||||
|                 <MarkCompletedDialogue |                 <MarkCompletedDialogue | ||||||
|                     isOpen={markCompletedDialogueOpen} |                     isOpen={markCompletedDialogueOpen} | ||||||
|                     setIsOpen={setMarkCompletedDialogueOpen} |                     setIsOpen={setMarkCompletedDialogueOpen} | ||||||
| @ -211,8 +184,7 @@ const FeatureOverviewMetaData = () => { | |||||||
|                     featureId={feature.name} |                     featureId={feature.name} | ||||||
|                     onComplete={refetchFeature} |                     onComplete={refetchFeature} | ||||||
|                 /> |                 /> | ||||||
|                 } |             ) : null} | ||||||
|             /> |  | ||||||
|         </> |         </> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -119,7 +119,7 @@ export const OldDependencyRow: FC<{ feature: IFeatureToggle }> = ({ | |||||||
|                                     marginBottom: theme.spacing(0.4), |                                     marginBottom: theme.spacing(0.4), | ||||||
|                                 })} |                                 })} | ||||||
|                             > |                             > | ||||||
|                                 Add parent feature |                                 Add parent flag | ||||||
|                             </PermissionButton> |                             </PermissionButton> | ||||||
|                         </StyledDetail> |                         </StyledDetail> | ||||||
|                     </FlexRow> |                     </FlexRow> | ||||||
|  | |||||||
| @ -2,8 +2,7 @@ import type { IFeatureToggle } from 'interfaces/featureToggle'; | |||||||
| import { useContext, useState } from 'react'; | import { useContext, useState } from 'react'; | ||||||
| import { Chip, styled, Tooltip } from '@mui/material'; | import { Chip, styled, Tooltip } from '@mui/material'; | ||||||
| import useFeatureTags from 'hooks/api/getters/useFeatureTags/useFeatureTags'; | import useFeatureTags from 'hooks/api/getters/useFeatureTags/useFeatureTags'; | ||||||
| import Add from '@mui/icons-material/Add'; | import DeleteTagIcon from '@mui/icons-material/Cancel'; | ||||||
| import ClearIcon from '@mui/icons-material/Clear'; |  | ||||||
| import { ManageTagsDialog } from 'component/feature/FeatureView/FeatureOverview/ManageTagsDialog/ManageTagsDialog'; | import { ManageTagsDialog } from 'component/feature/FeatureView/FeatureOverview/ManageTagsDialog/ManageTagsDialog'; | ||||||
| import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; | import { UPDATE_FEATURE } from 'component/providers/AccessProvider/permissions'; | ||||||
| import AccessContext from 'contexts/AccessContext'; | import AccessContext from 'contexts/AccessContext'; | ||||||
| @ -12,57 +11,42 @@ import type { ITag } from 'interfaces/tags'; | |||||||
| import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; | import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; | ||||||
| import useToast from 'hooks/useToast'; | import useToast from 'hooks/useToast'; | ||||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | import { formatUnknownError } from 'utils/formatUnknownError'; | ||||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | import { StyledMetaDataItem } from './FeatureOverviewMetaData'; | ||||||
| import { | import { AddTagButton } from './AddTagButton'; | ||||||
|     StyledMetaDataItem, |  | ||||||
|     StyledMetaDataItemLabel, |  | ||||||
| } from './FeatureOverviewMetaData'; |  | ||||||
| import PermissionButton from 'component/common/PermissionButton/PermissionButton'; |  | ||||||
| 
 | 
 | ||||||
| const StyledPermissionButton = styled(PermissionButton)(({ theme }) => ({ | const StyledLabel = styled('span')(({ theme }) => ({ | ||||||
|     '&&&': { |     marginTop: theme.spacing(1), | ||||||
|         fontSize: theme.fontSizes.smallBody, |     color: theme.palette.text.secondary, | ||||||
|         lineHeight: 1, |     marginRight: theme.spacing(1), | ||||||
|         margin: 0, |  | ||||||
|     }, |  | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const StyledTagRow = styled('div')(({ theme }) => ({ | const StyledTagRow = styled('div')(({ theme }) => ({ | ||||||
|     display: 'flex', |     display: 'flex', | ||||||
|     alignItems: 'start', |     justifyContent: 'space-between', | ||||||
|     minHeight: theme.spacing(4.25), |     flexWrap: 'wrap', | ||||||
|     lineHeight: theme.spacing(4.25), |     minHeight: theme.spacing(4.5), | ||||||
|     fontSize: theme.fontSizes.smallBody, |     fontSize: theme.fontSizes.smallBody, | ||||||
|     justifyContent: 'start', |  | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const StyledTagContainer = styled('div')(({ theme }) => ({ | const StyledTagContainer = styled('div')(({ theme }) => ({ | ||||||
|     display: 'flex', |     display: 'flex', | ||||||
|     flex: 1, |  | ||||||
|     overflow: 'hidden', |     overflow: 'hidden', | ||||||
|     gap: theme.spacing(1), |     gap: theme.spacing(1), | ||||||
|     flexWrap: 'wrap', |     flexWrap: 'wrap', | ||||||
|     marginTop: theme.spacing(0.75), |     marginTop: theme.spacing(0.75), | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const StyledChip = styled(Chip)(({ theme }) => ({ | const StyledTag = styled(Chip)(({ theme }) => ({ | ||||||
|     fontSize: theme.fontSizes.smallerBody, |  | ||||||
|     overflowWrap: 'anywhere', |     overflowWrap: 'anywhere', | ||||||
|  |     lineHeight: theme.typography.body1.lineHeight, | ||||||
|     backgroundColor: theme.palette.neutral.light, |     backgroundColor: theme.palette.neutral.light, | ||||||
|     color: theme.palette.neutral.dark, |     color: theme.palette.text.primary, | ||||||
|     '&&& > svg': { |     padding: theme.spacing(0.25), | ||||||
|         color: theme.palette.neutral.dark, |     height: theme.spacing(3.5), | ||||||
|         fontSize: theme.fontSizes.smallBody, |  | ||||||
|     }, |  | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const StyledAddedTag = styled(StyledChip)(({ theme }) => ({ | const StyledEllipsis = styled('span')(({ theme }) => ({ | ||||||
|     backgroundColor: theme.palette.secondary.light, |     color: theme.palette.text.secondary, | ||||||
|     color: theme.palette.secondary.dark, |  | ||||||
|     '&&& > svg': { |  | ||||||
|         color: theme.palette.secondary.dark, |  | ||||||
|         fontSize: theme.fontSizes.smallBody, |  | ||||||
|     }, |  | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| interface IFeatureOverviewSidePanelTagsProps { | interface IFeatureOverviewSidePanelTagsProps { | ||||||
| @ -81,6 +65,10 @@ export const TagRow = ({ feature }: IFeatureOverviewSidePanelTagsProps) => { | |||||||
|     const { hasAccess } = useContext(AccessContext); |     const { hasAccess } = useContext(AccessContext); | ||||||
|     const canUpdateTags = hasAccess(UPDATE_FEATURE, feature.project); |     const canUpdateTags = hasAccess(UPDATE_FEATURE, feature.project); | ||||||
| 
 | 
 | ||||||
|  |     const handleAdd = () => { | ||||||
|  |         setManageTagsOpen(true); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     const handleRemove = async () => { |     const handleRemove = async () => { | ||||||
|         if (!selectedTag) return; |         if (!selectedTag) return; | ||||||
|         try { |         try { | ||||||
| @ -101,78 +89,71 @@ export const TagRow = ({ feature }: IFeatureOverviewSidePanelTagsProps) => { | |||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|             <ConditionallyRender |             {!tags.length ? ( | ||||||
|                 condition={!tags.length} |  | ||||||
|                 show={ |  | ||||||
|                 <StyledMetaDataItem> |                 <StyledMetaDataItem> | ||||||
|                         <StyledMetaDataItemLabel>Tags:</StyledMetaDataItemLabel> |                     <StyledLabel>Tags:</StyledLabel> | ||||||
|                         <StyledPermissionButton |                     <StyledTagContainer> | ||||||
|                             size='small' |                         <AddTagButton | ||||||
|                             permission={UPDATE_FEATURE} |                             project={feature.project} | ||||||
|                             projectId={feature.project} |                             onClick={handleAdd} | ||||||
|                             variant='text' |                         /> | ||||||
|                             onClick={() => { |                     </StyledTagContainer> | ||||||
|                                 setManageTagsOpen(true); |  | ||||||
|                             }} |  | ||||||
|                         > |  | ||||||
|                             Add tag |  | ||||||
|                         </StyledPermissionButton> |  | ||||||
|                 </StyledMetaDataItem> |                 </StyledMetaDataItem> | ||||||
|                 } |             ) : ( | ||||||
|                 elseShow={ |  | ||||||
|                 <StyledTagRow> |                 <StyledTagRow> | ||||||
|                         <StyledMetaDataItemLabel>Tags:</StyledMetaDataItemLabel> |                     <StyledLabel>Tags:</StyledLabel> | ||||||
|                     <StyledTagContainer> |                     <StyledTagContainer> | ||||||
|                         {tags.map((tag) => { |                         {tags.map((tag) => { | ||||||
|                             const tagLabel = `${tag.type}:${tag.value}`; |                             const tagLabel = `${tag.type}:${tag.value}`; | ||||||
|  |                             const isOverflowing = tagLabel.length > 25; | ||||||
|                             return ( |                             return ( | ||||||
|  |                                 <StyledTag | ||||||
|  |                                     label={ | ||||||
|                                         <Tooltip |                                         <Tooltip | ||||||
|                                             key={tagLabel} |                                             key={tagLabel} | ||||||
|                                             title={ |                                             title={ | ||||||
|                                             tagLabel.length > 35 ? tagLabel : '' |                                                 isOverflowing ? tagLabel : '' | ||||||
|                                             } |                                             } | ||||||
|                                             arrow |                                             arrow | ||||||
|                                         > |                                         > | ||||||
|                                         <StyledAddedTag |                                             <span> | ||||||
|                                             label={tagLabel} |                                                 {tagLabel.substring(0, 25)} | ||||||
|  |                                                 {isOverflowing ? ( | ||||||
|  |                                                     <StyledEllipsis> | ||||||
|  |                                                         … | ||||||
|  |                                                     </StyledEllipsis> | ||||||
|  |                                                 ) : ( | ||||||
|  |                                                     '' | ||||||
|  |                                                 )} | ||||||
|  |                                             </span> | ||||||
|  |                                         </Tooltip> | ||||||
|  |                                     } | ||||||
|                                     size='small' |                                     size='small' | ||||||
|                                     deleteIcon={ |                                     deleteIcon={ | ||||||
|                                                 <Tooltip |                                         <Tooltip title='Remove tag' arrow> | ||||||
|                                                     title='Remove tag' |                                             <DeleteTagIcon /> | ||||||
|                                                     arrow |  | ||||||
|                                                 > |  | ||||||
|                                                     <ClearIcon /> |  | ||||||
|                                         </Tooltip> |                                         </Tooltip> | ||||||
|                                     } |                                     } | ||||||
|                                     onDelete={ |                                     onDelete={ | ||||||
|                                         canUpdateTags |                                         canUpdateTags | ||||||
|                                             ? () => { |                                             ? () => { | ||||||
|                                                           setRemoveTagOpen( |                                                   setRemoveTagOpen(true); | ||||||
|                                                               true, |  | ||||||
|                                                           ); |  | ||||||
|                                                   setSelectedTag(tag); |                                                   setSelectedTag(tag); | ||||||
|                                               } |                                               } | ||||||
|                                             : undefined |                                             : undefined | ||||||
|                                     } |                                     } | ||||||
|                                 /> |                                 /> | ||||||
|                                     </Tooltip> |  | ||||||
|                             ); |                             ); | ||||||
|                         })} |                         })} | ||||||
|                             <ConditionallyRender |                         {canUpdateTags ? ( | ||||||
|                                 condition={canUpdateTags} |                             <AddTagButton | ||||||
|                                 show={ |                                 project={feature.project} | ||||||
|                                     <StyledChip |                                 onClick={handleAdd} | ||||||
|                                         icon={<Add />} |  | ||||||
|                                         label='Add tag' |  | ||||||
|                                         size='small' |  | ||||||
|                                         onClick={() => setManageTagsOpen(true)} |  | ||||||
|                                     /> |  | ||||||
|                                 } |  | ||||||
|                             /> |                             /> | ||||||
|  |                         ) : null} | ||||||
|                     </StyledTagContainer> |                     </StyledTagContainer> | ||||||
|                 </StyledTagRow> |                 </StyledTagRow> | ||||||
|                 } |             )} | ||||||
|             /> |  | ||||||
|             <ManageTagsDialog |             <ManageTagsDialog | ||||||
|                 open={manageTagsOpen} |                 open={manageTagsOpen} | ||||||
|                 setOpen={setManageTagsOpen} |                 setOpen={setManageTagsOpen} | ||||||
| @ -184,6 +165,7 @@ export const TagRow = ({ feature }: IFeatureOverviewSidePanelTagsProps) => { | |||||||
|                 onClose={() => { |                 onClose={() => { | ||||||
|                     setRemoveTagOpen(false); |                     setRemoveTagOpen(false); | ||||||
|                     setSelectedTag(undefined); |                     setSelectedTag(undefined); | ||||||
|  |                     refetch(); | ||||||
|                 }} |                 }} | ||||||
|                 onClick={() => { |                 onClick={() => { | ||||||
|                     setRemoveTagOpen(false); |                     setRemoveTagOpen(false); | ||||||
|  | |||||||
| @ -140,7 +140,7 @@ A child feature flag is evaluated only when both the child and its parent featur | |||||||
| - Parent feature is disabled: Useful when the parent acts as a kill switch with inverted enabled/disabled logic. | - Parent feature is disabled: Useful when the parent acts as a kill switch with inverted enabled/disabled logic. | ||||||
| - Parent feature is enabled with variants: Useful for A/B testing scenarios where you need specific variant dependencies. | - Parent feature is enabled with variants: Useful for A/B testing scenarios where you need specific variant dependencies. | ||||||
| 
 | 
 | ||||||
| To add a dependency, you need the `update-feature-dependency` project permission. In the Admin UI, go to the feature flag you want to add a parent to and select **Add parent feature**. | To add a dependency, you need the `update-feature-dependency` project permission. In the Admin UI, go to the feature flag you want to add a parent to and select **Add parent flag**. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user