mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Fetch backend api data insights (#6622)
This commit is contained in:
		
							parent
							
								
									ae921aed69
								
							
						
					
					
						commit
						f0e5d075a7
					
				| @ -22,11 +22,23 @@ const setupOssApi = () => { | |||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | const changeRequests = { | ||||||
|  |     applied: 0, | ||||||
|  |     total: 0, | ||||||
|  |     approved: 0, | ||||||
|  |     scheduled: 0, | ||||||
|  |     reviewRequired: 0, | ||||||
|  |     rejected: 0, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| test('Show enterprise hints', async () => { | test('Show enterprise hints', async () => { | ||||||
|     setupOssApi(); |     setupOssApi(); | ||||||
|     render( |     render( | ||||||
|         <Routes> |         <Routes> | ||||||
|             <Route path={'/projects/:projectId'} element={<ChangeRequests />} /> |             <Route | ||||||
|  |                 path={'/projects/:projectId'} | ||||||
|  |                 element={<ChangeRequests changeRequests={changeRequests} />} | ||||||
|  |             /> | ||||||
|         </Routes>, |         </Routes>, | ||||||
|         { |         { | ||||||
|             route: '/projects/default', |             route: '/projects/default', | ||||||
| @ -40,7 +52,10 @@ test('Show change requests info', async () => { | |||||||
|     setupEnterpriseApi(); |     setupEnterpriseApi(); | ||||||
|     render( |     render( | ||||||
|         <Routes> |         <Routes> | ||||||
|             <Route path={'/projects/:projectId'} element={<ChangeRequests />} /> |             <Route | ||||||
|  |                 path={'/projects/:projectId'} | ||||||
|  |                 element={<ChangeRequests changeRequests={changeRequests} />} | ||||||
|  |             /> | ||||||
|         </Routes>, |         </Routes>, | ||||||
|         { |         { | ||||||
|             route: '/projects/default', |             route: '/projects/default', | ||||||
|  | |||||||
| @ -4,6 +4,8 @@ import { Link } from 'react-router-dom'; | |||||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||||
| import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature'; | import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature'; | ||||||
|  | import type { ProjectInsightsSchemaChangeRequests } from '../../../../../openapi'; | ||||||
|  | import type { FC } from 'react'; | ||||||
| 
 | 
 | ||||||
| const Container = styled(Box)(({ theme }) => ({ | const Container = styled(Box)(({ theme }) => ({ | ||||||
|     display: 'flex', |     display: 'flex', | ||||||
| @ -81,15 +83,15 @@ const BigNumber = styled(Typography)(({ theme }) => ({ | |||||||
|     color: theme.palette.text.primary, |     color: theme.palette.text.primary, | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| export const ChangeRequests = () => { | export const ChangeRequests: FC<{ | ||||||
|  |     changeRequests: ProjectInsightsSchemaChangeRequests; | ||||||
|  | }> = ({ changeRequests }) => { | ||||||
|     const projectId = useRequiredPathParam('projectId'); |     const projectId = useRequiredPathParam('projectId'); | ||||||
|     const { isOss, isPro } = useUiConfig(); |     const { isOss, isPro } = useUiConfig(); | ||||||
| 
 | 
 | ||||||
|     const toBeApplied = 12; |     const { total, applied, rejected, reviewRequired, scheduled, approved } = | ||||||
|     const toBeReviewed = 3; |         changeRequests; | ||||||
|     const total = 32; |     const toBeApplied = scheduled + approved; | ||||||
|     const applied = 28; |  | ||||||
|     const rejected = 4; |  | ||||||
| 
 | 
 | ||||||
|     if (isOss() || isPro()) { |     if (isOss() || isPro()) { | ||||||
|         return ( |         return ( | ||||||
| @ -109,7 +111,7 @@ export const ChangeRequests = () => { | |||||||
|                 <KeyboardArrowRight /> |                 <KeyboardArrowRight /> | ||||||
|             </ChangeRequestNavigation> |             </ChangeRequestNavigation> | ||||||
| 
 | 
 | ||||||
|             <BoxesContainer> |             <BoxesContainer data-loading> | ||||||
|                 <OpenBox> |                 <OpenBox> | ||||||
|                     <ChangeRequestNavigation |                     <ChangeRequestNavigation | ||||||
|                         to={`/projects/${projectId}/change-requests`} |                         to={`/projects/${projectId}/change-requests`} | ||||||
| @ -123,7 +125,7 @@ export const ChangeRequests = () => { | |||||||
|                     </ApplyBox> |                     </ApplyBox> | ||||||
|                     <ReviewBox> |                     <ReviewBox> | ||||||
|                         <span>To be reviewed</span> |                         <span>To be reviewed</span> | ||||||
|                         <MediumNumber>{toBeReviewed}</MediumNumber> |                         <MediumNumber>{reviewRequired}</MediumNumber> | ||||||
|                     </ReviewBox> |                     </ReviewBox> | ||||||
|                 </OpenBox> |                 </OpenBox> | ||||||
|                 <NumberBox> |                 <NumberBox> | ||||||
|  | |||||||
| @ -24,7 +24,14 @@ test('Show outdated SDKs and apps using them', async () => { | |||||||
|     }); |     }); | ||||||
|     render( |     render( | ||||||
|         <Routes> |         <Routes> | ||||||
|             <Route path={'/projects/:projectId'} element={<FlagTypesUsed />} /> |             <Route | ||||||
|  |                 path={'/projects/:projectId'} | ||||||
|  |                 element={ | ||||||
|  |                     <FlagTypesUsed | ||||||
|  |                         featureTypeCounts={[{ type: 'release', count: 57 }]} | ||||||
|  |                     /> | ||||||
|  |                 } | ||||||
|  |             /> | ||||||
|         </Routes>, |         </Routes>, | ||||||
|         { |         { | ||||||
|             route: '/projects/default', |             route: '/projects/default', | ||||||
|  | |||||||
| @ -1,10 +1,9 @@ | |||||||
| import { useMemo } from 'react'; | import { type FC, useMemo } from 'react'; | ||||||
| import { styled, type SvgIconTypeMap, Typography } from '@mui/material'; | import { styled, type SvgIconTypeMap, Typography } from '@mui/material'; | ||||||
| import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons'; | import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons'; | ||||||
| 
 | 
 | ||||||
| import type { OverridableComponent } from '@mui/material/OverridableComponent'; | import type { OverridableComponent } from '@mui/material/OverridableComponent'; | ||||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | import type { FeatureTypeCountSchema } from '../../../../../openapi'; | ||||||
| import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview'; |  | ||||||
| 
 | 
 | ||||||
| export const StyledProjectInfoWidgetContainer = styled('div')(({ theme }) => ({ | export const StyledProjectInfoWidgetContainer = styled('div')(({ theme }) => ({ | ||||||
|     margin: '0', |     margin: '0', | ||||||
| @ -64,12 +63,9 @@ const FlagTypesRow = ({ type, Icon, count }: IFlagTypeRowProps) => { | |||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const FlagTypesUsed = () => { | export const FlagTypesUsed: FC<{ | ||||||
|     const projectId = useRequiredPathParam('projectId'); |     featureTypeCounts: FeatureTypeCountSchema[]; | ||||||
|     const { project } = useProjectOverview(projectId); | }> = ({ featureTypeCounts }) => { | ||||||
| 
 |  | ||||||
|     const { featureTypeCounts } = project; |  | ||||||
| 
 |  | ||||||
|     const featureTypeStats = useMemo(() => { |     const featureTypeStats = useMemo(() => { | ||||||
|         const release = |         const release = | ||||||
|             featureTypeCounts.find( |             featureTypeCounts.find( | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ import { ProjectHealthChart } from './ProjectHealthChart'; | |||||||
| import { Alert, Box, styled, Typography, useTheme } from '@mui/material'; | import { Alert, Box, styled, Typography, useTheme } from '@mui/material'; | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||||
|  | import type { ProjectInsightsSchemaHealth } from '../../../../../openapi'; | ||||||
|  | import type { FC } from 'react'; | ||||||
| 
 | 
 | ||||||
| const Dot = styled('span', { | const Dot = styled('span', { | ||||||
|     shouldForwardProp: (prop) => prop !== 'color', |     shouldForwardProp: (prop) => prop !== 'color', | ||||||
| @ -36,13 +38,12 @@ const StatusWithDot = styled(Box)(({ theme }) => ({ | |||||||
|     gap: theme.spacing(1), |     gap: theme.spacing(1), | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| export const ProjectHealth = () => { | export const ProjectHealth: FC<{ health: ProjectInsightsSchemaHealth }> = ({ | ||||||
|  |     health, | ||||||
|  | }) => { | ||||||
|     const theme = useTheme(); |     const theme = useTheme(); | ||||||
|     const projectId = useRequiredPathParam('projectId'); |     const projectId = useRequiredPathParam('projectId'); | ||||||
|     const active = 15; |     const { staleCount, potentiallyStaleCount, activeCount, rating } = health; | ||||||
|     const stale = 10; |  | ||||||
|     const potentiallyStale = 3; |  | ||||||
|     const health = 93; |  | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <Container> |         <Container> | ||||||
| @ -52,6 +53,7 @@ export const ProjectHealth = () => { | |||||||
|                 flags |                 flags | ||||||
|             </Alert> |             </Alert> | ||||||
|             <Box |             <Box | ||||||
|  |                 data-loading | ||||||
|                 sx={(theme) => ({ |                 sx={(theme) => ({ | ||||||
|                     display: 'flex', |                     display: 'flex', | ||||||
|                     gap: theme.spacing(4), |                     gap: theme.spacing(4), | ||||||
| @ -59,10 +61,10 @@ export const ProjectHealth = () => { | |||||||
|                 })} |                 })} | ||||||
|             > |             > | ||||||
|                 <ProjectHealthChart |                 <ProjectHealthChart | ||||||
|                     active={active} |                     active={activeCount} | ||||||
|                     stale={stale} |                     stale={staleCount} | ||||||
|                     potentiallyStale={potentiallyStale} |                     potentiallyStale={potentiallyStaleCount} | ||||||
|                     health={health} |                     health={rating} | ||||||
|                 /> |                 /> | ||||||
|                 <FlagCounts> |                 <FlagCounts> | ||||||
|                     <Box> |                     <Box> | ||||||
| @ -70,7 +72,7 @@ export const ProjectHealth = () => { | |||||||
|                             <Dot color={theme.palette.success.border} /> |                             <Dot color={theme.palette.success.border} /> | ||||||
|                             <Box sx={{ fontWeight: 'bold' }}>Active</Box> |                             <Box sx={{ fontWeight: 'bold' }}>Active</Box> | ||||||
|                         </StatusWithDot> |                         </StatusWithDot> | ||||||
|                         <FlagsCount>{active} feature flags</FlagsCount> |                         <FlagsCount>{activeCount} feature flags</FlagsCount> | ||||||
|                     </Box> |                     </Box> | ||||||
|                     <Box> |                     <Box> | ||||||
|                         <StatusWithDot> |                         <StatusWithDot> | ||||||
| @ -81,7 +83,7 @@ export const ProjectHealth = () => { | |||||||
|                             <Link to='/feature-toggle-type'>(configure)</Link> |                             <Link to='/feature-toggle-type'>(configure)</Link> | ||||||
|                         </StatusWithDot> |                         </StatusWithDot> | ||||||
|                         <FlagsCount> |                         <FlagsCount> | ||||||
|                             {potentiallyStale} feature flags |                             {potentiallyStaleCount} feature flags | ||||||
|                         </FlagsCount> |                         </FlagsCount> | ||||||
|                     </Box> |                     </Box> | ||||||
|                     <Box> |                     <Box> | ||||||
| @ -92,7 +94,7 @@ export const ProjectHealth = () => { | |||||||
|                                 (view flags) |                                 (view flags) | ||||||
|                             </Link> |                             </Link> | ||||||
|                         </StatusWithDot> |                         </StatusWithDot> | ||||||
|                         <FlagsCount>{stale} feature flags</FlagsCount> |                         <FlagsCount>{staleCount} feature flags</FlagsCount> | ||||||
|                     </Box> |                     </Box> | ||||||
|                 </FlagCounts> |                 </FlagCounts> | ||||||
|             </Box> |             </Box> | ||||||
|  | |||||||
| @ -4,6 +4,9 @@ import { LeadTimeForChanges } from './LeadTimeForChanges/LeadTimeForChanges'; | |||||||
| import { ProjectHealth } from './ProjectHealth/ProjectHealth'; | import { ProjectHealth } from './ProjectHealth/ProjectHealth'; | ||||||
| import { FlagTypesUsed } from './FlagTypesUsed/FlagTypesUsed'; | import { FlagTypesUsed } from './FlagTypesUsed/FlagTypesUsed'; | ||||||
| import { ProjectInsightsStats } from './ProjectInsightsStats/ProjectInsightsStats'; | import { ProjectInsightsStats } from './ProjectInsightsStats/ProjectInsightsStats'; | ||||||
|  | import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||||
|  | import { useProjectInsights } from 'hooks/api/getters/useProjectInsights/useProjectInsights'; | ||||||
|  | import useLoading from 'hooks/useLoading'; | ||||||
| 
 | 
 | ||||||
| const Container = styled(Box)(({ theme }) => ({ | const Container = styled(Box)(({ theme }) => ({ | ||||||
|     backgroundColor: theme.palette.background.paper, |     backgroundColor: theme.palette.background.paper, | ||||||
| @ -33,37 +36,31 @@ const NarrowContainer = styled(Container)(() => ({ | |||||||
|     gridColumn: 'span 2', |     gridColumn: 'span 2', | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
| const statsData = { |  | ||||||
|     stats: { |  | ||||||
|         archivedCurrentWindow: 5, |  | ||||||
|         archivedPastWindow: 3, |  | ||||||
|         avgTimeToProdCurrentWindow: 2.5, |  | ||||||
|         createdCurrentWindow: 7, |  | ||||||
|         createdPastWindow: 4, |  | ||||||
|         projectActivityCurrentWindow: 10, |  | ||||||
|         projectActivityPastWindow: 8, |  | ||||||
|         projectMembersAddedCurrentWindow: 2, |  | ||||||
|     }, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export const ProjectInsights = () => { | export const ProjectInsights = () => { | ||||||
|  |     const projectId = useRequiredPathParam('projectId'); | ||||||
|  |     const { data, loading } = useProjectInsights(projectId); | ||||||
|  | 
 | ||||||
|  |     const ref = useLoading(loading); | ||||||
|  | 
 | ||||||
|     return ( |     return ( | ||||||
|         <Grid> |         <Grid ref={ref}> | ||||||
|             <FullWidthContainer> |             <FullWidthContainer> | ||||||
|                 <ProjectInsightsStats {...statsData} /> |                 <ProjectInsightsStats stats={data.stats} /> | ||||||
|             </FullWidthContainer> |             </FullWidthContainer> | ||||||
|             <MediumWideContainer> |             <MediumWideContainer> | ||||||
|                 <ProjectHealth /> |                 <ProjectHealth health={data.health} /> | ||||||
|             </MediumWideContainer> |             </MediumWideContainer> | ||||||
|             <WideContainer> |             <WideContainer> | ||||||
|                 <LeadTimeForChanges /> |                 <LeadTimeForChanges /> | ||||||
|             </WideContainer> |             </WideContainer> | ||||||
|             <NarrowContainer> |             <NarrowContainer> | ||||||
|                 <FlagTypesUsed /> |                 <FlagTypesUsed featureTypeCounts={data.featureTypeCounts} /> | ||||||
|             </NarrowContainer> |             </NarrowContainer> | ||||||
|             <NarrowContainer>Project members</NarrowContainer> |             <NarrowContainer>Project members</NarrowContainer> | ||||||
|             <WideContainer> |             <WideContainer> | ||||||
|                 <ChangeRequests /> |                 {data.changeRequests && ( | ||||||
|  |                     <ChangeRequests changeRequests={data.changeRequests} /> | ||||||
|  |                 )} | ||||||
|             </WideContainer> |             </WideContainer> | ||||||
|         </Grid> |         </Grid> | ||||||
|     ); |     ); | ||||||
|  | |||||||
| @ -0,0 +1,71 @@ | |||||||
|  | import { fetcher, useApiGetter } from '../useApiGetter/useApiGetter'; | ||||||
|  | import type { ProjectInsightsSchema } from '../../../../openapi'; | ||||||
|  | import { formatApiPath } from 'utils/formatPath'; | ||||||
|  | 
 | ||||||
|  | const path = (projectId: string) => `api/admin/projects/${projectId}/insights`; | ||||||
|  | 
 | ||||||
|  | const placeholderData: ProjectInsightsSchema = { | ||||||
|  |     stats: { | ||||||
|  |         avgTimeToProdCurrentWindow: 0, | ||||||
|  |         createdCurrentWindow: 0, | ||||||
|  |         createdPastWindow: 0, | ||||||
|  |         archivedCurrentWindow: 0, | ||||||
|  |         archivedPastWindow: 0, | ||||||
|  |         projectActivityCurrentWindow: 0, | ||||||
|  |         projectActivityPastWindow: 0, | ||||||
|  |         projectMembersAddedCurrentWindow: 0, | ||||||
|  |     }, | ||||||
|  |     featureTypeCounts: [ | ||||||
|  |         { | ||||||
|  |             type: 'experiment', | ||||||
|  |             count: 0, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             type: 'permission', | ||||||
|  |             count: 0, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             type: 'release', | ||||||
|  |             count: 0, | ||||||
|  |         }, | ||||||
|  |     ], | ||||||
|  |     leadTime: { | ||||||
|  |         projectAverage: 0, | ||||||
|  |         features: [ | ||||||
|  |             { name: 'feature1', timeToProduction: 0 }, | ||||||
|  |             { name: 'feature2', timeToProduction: 0 }, | ||||||
|  |             { name: 'feature3', timeToProduction: 0 }, | ||||||
|  |             { name: 'feature4', timeToProduction: 0 }, | ||||||
|  |             { name: 'feature5', timeToProduction: 2 }, | ||||||
|  |         ], | ||||||
|  |     }, | ||||||
|  |     health: { | ||||||
|  |         rating: 0, | ||||||
|  |         activeCount: 0, | ||||||
|  |         potentiallyStaleCount: 0, | ||||||
|  |         staleCount: 0, | ||||||
|  |     }, | ||||||
|  |     members: { | ||||||
|  |         active: 0, | ||||||
|  |         inactive: 0, | ||||||
|  |         totalPreviousMonth: 0, | ||||||
|  |     }, | ||||||
|  |     changeRequests: { | ||||||
|  |         total: 0, | ||||||
|  |         applied: 0, | ||||||
|  |         approved: 0, | ||||||
|  |         rejected: 0, | ||||||
|  |         reviewRequired: 0, | ||||||
|  |         scheduled: 0, | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const useProjectInsights = (projectId: string) => { | ||||||
|  |     const projectPath = formatApiPath(path(projectId)); | ||||||
|  |     const { data, refetch, loading, error } = | ||||||
|  |         useApiGetter<ProjectInsightsSchema>(projectPath, () => | ||||||
|  |             fetcher(projectPath, 'Outdated SDKs'), | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |     return { data: data || placeholderData, refetch, loading, error }; | ||||||
|  | }; | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user