mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: flag exposure in personal dashboard (#8247)
This commit is contained in:
		
							parent
							
								
									289324fd02
								
							
						
					
					
						commit
						a1a24ea0b1
					
				| @ -43,6 +43,7 @@ export const FeatureLifecycle: FC<{ | ||||
|     return currentStage ? ( | ||||
|         <FeatureLifecycleTooltip | ||||
|             stage={currentStage!} | ||||
|             project={feature.project} | ||||
|             onArchive={onArchive} | ||||
|             onComplete={onComplete} | ||||
|             onUncomplete={onUncompleteHandler} | ||||
|  | ||||
| @ -8,7 +8,6 @@ import { | ||||
|     DELETE_FEATURE, | ||||
|     UPDATE_FEATURE, | ||||
| } from 'component/providers/AccessProvider/permissions'; | ||||
| import { Route, Routes } from 'react-router-dom'; | ||||
| 
 | ||||
| const currentTime = '2024-04-25T08:05:00.000Z'; | ||||
| const twoMinutesAgo = '2024-04-25T08:03:00.000Z'; | ||||
| @ -23,24 +22,17 @@ const renderOpenTooltip = ( | ||||
|     loading = false, | ||||
| ) => { | ||||
|     render( | ||||
|         <Routes> | ||||
|             <Route | ||||
|                 path={'/projects/:projectId'} | ||||
|                 element={ | ||||
|                     <FeatureLifecycleTooltip | ||||
|                         stage={stage} | ||||
|                         onArchive={onArchive} | ||||
|                         onComplete={onComplete} | ||||
|                         onUncomplete={onUncomplete} | ||||
|                         loading={loading} | ||||
|                     > | ||||
|                         <span>child</span> | ||||
|                     </FeatureLifecycleTooltip> | ||||
|                 } | ||||
|             /> | ||||
|         </Routes>, | ||||
|         <FeatureLifecycleTooltip | ||||
|             stage={stage} | ||||
|             onArchive={onArchive} | ||||
|             onComplete={onComplete} | ||||
|             onUncomplete={onUncomplete} | ||||
|             loading={loading} | ||||
|             project={'default'} | ||||
|         > | ||||
|             <span>child</span> | ||||
|         </FeatureLifecycleTooltip>, | ||||
|         { | ||||
|             route: '/projects/default', | ||||
|             permissions: [ | ||||
|                 { permission: DELETE_FEATURE }, | ||||
|                 { permission: UPDATE_FEATURE }, | ||||
|  | ||||
| @ -25,7 +25,6 @@ import { isSafeToArchive } from './isSafeToArchive'; | ||||
| import { useLocationSettings } from 'hooks/useLocationSettings'; | ||||
| import { formatDateYMDHMS } from 'utils/formatDate'; | ||||
| import { formatDistanceToNow, parseISO } from 'date-fns'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| 
 | ||||
| const TimeLabel = styled('span')(({ theme }) => ({ | ||||
|     color: theme.palette.text.secondary, | ||||
| @ -96,7 +95,7 @@ const StageBox = styled(Box, { | ||||
|         ...(active && { | ||||
|             backgroundColor: theme.palette.primary.light, | ||||
|             color: theme.palette.primary.contrastText, | ||||
|             fontWeight: theme.fontWeight.bold, | ||||
|             fontWeight: theme.typography.fontWeightBold, | ||||
|             borderRadius: theme.spacing(0.5), | ||||
|         }), | ||||
|     }, | ||||
| @ -247,17 +246,16 @@ const PreLiveStageDescription: FC<{ children?: React.ReactNode }> = ({ | ||||
| const BoldTitle = styled(Typography)(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(1), | ||||
|     marginBottom: theme.spacing(1), | ||||
|     fontSize: theme.fontSizes.smallBody, | ||||
|     fontWeight: theme.fontWeight.bold, | ||||
|     fontSize: theme.typography.body2.fontSize, | ||||
|     fontWeight: theme.typography.fontWeightBold, | ||||
| })); | ||||
| 
 | ||||
| const LiveStageDescription: FC<{ | ||||
|     onComplete: () => void; | ||||
|     loading: boolean; | ||||
|     children?: React.ReactNode; | ||||
| }> = ({ children, onComplete, loading }) => { | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
| 
 | ||||
|     project: string; | ||||
| }> = ({ children, onComplete, loading, project }) => { | ||||
|     return ( | ||||
|         <> | ||||
|             <BoldTitle>Is this feature complete?</BoldTitle> | ||||
| @ -276,7 +274,7 @@ const LiveStageDescription: FC<{ | ||||
|                 size='small' | ||||
|                 onClick={onComplete} | ||||
|                 disabled={loading} | ||||
|                 projectId={projectId} | ||||
|                 projectId={project} | ||||
|             > | ||||
|                 Mark completed | ||||
|             </PermissionButton> | ||||
| @ -294,9 +292,8 @@ const SafeToArchive: FC<{ | ||||
|     onArchive: () => void; | ||||
|     onUncomplete: () => void; | ||||
|     loading: boolean; | ||||
| }> = ({ onArchive, onUncomplete, loading }) => { | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
| 
 | ||||
|     project: string; | ||||
| }> = ({ onArchive, onUncomplete, loading, project }) => { | ||||
|     return ( | ||||
|         <> | ||||
|             <BoldTitle>Safe to archive</BoldTitle> | ||||
| @ -324,7 +321,7 @@ const SafeToArchive: FC<{ | ||||
|                     size='small' | ||||
|                     onClick={onUncomplete} | ||||
|                     disabled={loading} | ||||
|                     projectId={projectId} | ||||
|                     projectId={project} | ||||
|                 > | ||||
|                     Revert to live | ||||
|                 </PermissionButton> | ||||
| @ -335,7 +332,7 @@ const SafeToArchive: FC<{ | ||||
|                     size='small' | ||||
|                     sx={{ mb: 2 }} | ||||
|                     onClick={onArchive} | ||||
|                     projectId={projectId} | ||||
|                     projectId={project} | ||||
|                 > | ||||
|                     Archive feature | ||||
|                 </PermissionButton> | ||||
| @ -393,7 +390,15 @@ const CompletedStageDescription: FC<{ | ||||
|         lastSeenAt: string; | ||||
|     }>; | ||||
|     children?: React.ReactNode; | ||||
| }> = ({ children, environments, onArchive, onUncomplete, loading }) => { | ||||
|     project: string; | ||||
| }> = ({ | ||||
|     children, | ||||
|     environments, | ||||
|     onArchive, | ||||
|     onUncomplete, | ||||
|     loading, | ||||
|     project, | ||||
| }) => { | ||||
|     return ( | ||||
|         <ConditionallyRender | ||||
|             condition={isSafeToArchive(environments)} | ||||
| @ -402,6 +407,7 @@ const CompletedStageDescription: FC<{ | ||||
|                     onArchive={onArchive} | ||||
|                     onUncomplete={onUncomplete} | ||||
|                     loading={loading} | ||||
|                     project={project} | ||||
|                 /> | ||||
|             } | ||||
|             elseShow={ | ||||
| @ -432,11 +438,20 @@ const FormatElapsedTime: FC<{ | ||||
| export const FeatureLifecycleTooltip: FC<{ | ||||
|     children: React.ReactElement<any, any>; | ||||
|     stage: LifecycleStage; | ||||
|     project: string; | ||||
|     onArchive: () => void; | ||||
|     onComplete: () => void; | ||||
|     onUncomplete: () => void; | ||||
|     loading: boolean; | ||||
| }> = ({ children, stage, onArchive, onComplete, onUncomplete, loading }) => ( | ||||
| }> = ({ | ||||
|     children, | ||||
|     stage, | ||||
|     project, | ||||
|     onArchive, | ||||
|     onComplete, | ||||
|     onUncomplete, | ||||
|     loading, | ||||
| }) => ( | ||||
|     <HtmlTooltip | ||||
|         maxHeight={800} | ||||
|         maxWidth={350} | ||||
| @ -482,6 +497,7 @@ export const FeatureLifecycleTooltip: FC<{ | ||||
|                         <LiveStageDescription | ||||
|                             onComplete={onComplete} | ||||
|                             loading={loading} | ||||
|                             project={project} | ||||
|                         > | ||||
|                             <Environments environments={stage.environments} /> | ||||
|                         </LiveStageDescription> | ||||
| @ -492,6 +508,7 @@ export const FeatureLifecycleTooltip: FC<{ | ||||
|                             onArchive={onArchive} | ||||
|                             onUncomplete={onUncomplete} | ||||
|                             loading={loading} | ||||
|                             project={project} | ||||
|                         > | ||||
|                             <Environments environments={stage.environments} /> | ||||
|                         </CompletedStageDescription> | ||||
|  | ||||
| @ -0,0 +1,72 @@ | ||||
| import { type FC, useState } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
| import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; | ||||
| import type { ILastSeenEnvironments } from 'interfaces/featureToggle'; | ||||
| import { Box } from '@mui/material'; | ||||
| import { FeatureEnvironmentSeen } from '../../FeatureEnvironmentSeen/FeatureEnvironmentSeen'; | ||||
| import { FeatureLifecycle } from './FeatureLifecycle'; | ||||
| import { FeatureArchiveNotAllowedDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveNotAllowedDialog'; | ||||
| import { FeatureArchiveDialog } from 'component/common/FeatureArchiveDialog/FeatureArchiveDialog'; | ||||
| import { MarkCompletedDialogue } from './MarkCompletedDialogue'; | ||||
| 
 | ||||
| export const FlagExposure: FC<{ | ||||
|     project: string; | ||||
|     flagName: string; | ||||
|     onArchive: () => void; | ||||
| }> = ({ project, flagName, onArchive }) => { | ||||
|     const navigate = useNavigate(); | ||||
|     const { feature, refetchFeature } = useFeature(project, flagName); | ||||
|     const lastSeenEnvironments: ILastSeenEnvironments[] = | ||||
|         feature.environments?.map((env) => ({ | ||||
|             name: env.name, | ||||
|             lastSeenAt: env.lastSeenAt, | ||||
|             enabled: env.enabled, | ||||
|             yes: env.yes, | ||||
|             no: env.no, | ||||
|         })); | ||||
|     const [showDelDialog, setShowDelDialog] = useState(false); | ||||
|     const [showMarkCompletedDialogue, setShowMarkCompletedDialogue] = | ||||
|         useState(false); | ||||
| 
 | ||||
|     return ( | ||||
|         <Box sx={{ display: 'flex' }}> | ||||
|             <FeatureEnvironmentSeen | ||||
|                 featureLastSeen={feature.lastSeenAt} | ||||
|                 environments={lastSeenEnvironments} | ||||
|             /> | ||||
|             <FeatureLifecycle | ||||
|                 feature={feature} | ||||
|                 onArchive={() => setShowDelDialog(true)} | ||||
|                 onComplete={() => setShowMarkCompletedDialogue(true)} | ||||
|                 onUncomplete={refetchFeature} | ||||
|             /> | ||||
| 
 | ||||
|             {feature.children.length > 0 ? ( | ||||
|                 <FeatureArchiveNotAllowedDialog | ||||
|                     features={feature.children} | ||||
|                     project={project} | ||||
|                     isOpen={showDelDialog} | ||||
|                     onClose={() => setShowDelDialog(false)} | ||||
|                 /> | ||||
|             ) : ( | ||||
|                 <FeatureArchiveDialog | ||||
|                     isOpen={showDelDialog} | ||||
|                     onConfirm={onArchive} | ||||
|                     onClose={() => setShowDelDialog(false)} | ||||
|                     projectId={project} | ||||
|                     featureIds={[flagName]} | ||||
|                 /> | ||||
|             )} | ||||
| 
 | ||||
|             {feature.project ? ( | ||||
|                 <MarkCompletedDialogue | ||||
|                     isOpen={showMarkCompletedDialogue} | ||||
|                     setIsOpen={setShowMarkCompletedDialogue} | ||||
|                     projectId={feature.project} | ||||
|                     featureId={feature.name} | ||||
|                     onComplete={refetchFeature} | ||||
|                 /> | ||||
|             ) : null} | ||||
|         </Box> | ||||
|     ); | ||||
| }; | ||||
| @ -24,6 +24,7 @@ import { ProjectSetupComplete } from './ProjectSetupComplete'; | ||||
| import { usePersonalDashboard } from 'hooks/api/getters/usePersonalDashboard/usePersonalDashboard'; | ||||
| import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons'; | ||||
| import type { PersonalDashboardSchema } from '../../openapi'; | ||||
| import { FlagExposure } from 'component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FlagExposure'; | ||||
| 
 | ||||
| const ScreenExplanation = styled(Typography)(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(1), | ||||
| @ -177,7 +178,8 @@ export const PersonalDashboard = () => { | ||||
| 
 | ||||
|     const { projects, activeProject, setActiveProject } = useProjects(); | ||||
| 
 | ||||
|     const { personalDashboard } = usePersonalDashboard(); | ||||
|     const { personalDashboard, refetch: refetchDashboard } = | ||||
|         usePersonalDashboard(); | ||||
|     const [activeFlag, setActiveFlag] = useState< | ||||
|         PersonalDashboardSchema['flags'][0] | null | ||||
|     >(null); | ||||
| @ -298,7 +300,20 @@ export const PersonalDashboard = () => { | ||||
|                 <SpacedGridItem item lg={4} md={1}> | ||||
|                     <Typography variant='h3'>My feature flags</Typography> | ||||
|                 </SpacedGridItem> | ||||
|                 <SpacedGridItem item lg={8} md={1} /> | ||||
|                 <SpacedGridItem | ||||
|                     item | ||||
|                     lg={8} | ||||
|                     md={1} | ||||
|                     sx={{ display: 'flex', justifyContent: 'flex-end' }} | ||||
|                 > | ||||
|                     {activeFlag ? ( | ||||
|                         <FlagExposure | ||||
|                             project={activeFlag.project} | ||||
|                             flagName={activeFlag.name} | ||||
|                             onArchive={refetchDashboard} | ||||
|                         /> | ||||
|                     ) : null} | ||||
|                 </SpacedGridItem> | ||||
|                 <SpacedGridItem item lg={4} md={1}> | ||||
|                     {personalDashboard && personalDashboard.flags.length > 0 ? ( | ||||
|                         <List | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user