1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-10-28 19:06:12 +01:00

feat: Fetch backend api data insights (#6622)

This commit is contained in:
Mateusz Kwasniewski 2024-03-20 10:54:21 +01:00 committed by GitHub
parent ae921aed69
commit f0e5d075a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 140 additions and 50 deletions

View File

@ -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 () => {
setupOssApi();
render(
<Routes>
<Route path={'/projects/:projectId'} element={<ChangeRequests />} />
<Route
path={'/projects/:projectId'}
element={<ChangeRequests changeRequests={changeRequests} />}
/>
</Routes>,
{
route: '/projects/default',
@ -40,7 +52,10 @@ test('Show change requests info', async () => {
setupEnterpriseApi();
render(
<Routes>
<Route path={'/projects/:projectId'} element={<ChangeRequests />} />
<Route
path={'/projects/:projectId'}
element={<ChangeRequests changeRequests={changeRequests} />}
/>
</Routes>,
{
route: '/projects/default',

View File

@ -4,6 +4,8 @@ import { Link } from 'react-router-dom';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
import type { ProjectInsightsSchemaChangeRequests } from '../../../../../openapi';
import type { FC } from 'react';
const Container = styled(Box)(({ theme }) => ({
display: 'flex',
@ -81,15 +83,15 @@ const BigNumber = styled(Typography)(({ theme }) => ({
color: theme.palette.text.primary,
}));
export const ChangeRequests = () => {
export const ChangeRequests: FC<{
changeRequests: ProjectInsightsSchemaChangeRequests;
}> = ({ changeRequests }) => {
const projectId = useRequiredPathParam('projectId');
const { isOss, isPro } = useUiConfig();
const toBeApplied = 12;
const toBeReviewed = 3;
const total = 32;
const applied = 28;
const rejected = 4;
const { total, applied, rejected, reviewRequired, scheduled, approved } =
changeRequests;
const toBeApplied = scheduled + approved;
if (isOss() || isPro()) {
return (
@ -109,7 +111,7 @@ export const ChangeRequests = () => {
<KeyboardArrowRight />
</ChangeRequestNavigation>
<BoxesContainer>
<BoxesContainer data-loading>
<OpenBox>
<ChangeRequestNavigation
to={`/projects/${projectId}/change-requests`}
@ -123,7 +125,7 @@ export const ChangeRequests = () => {
</ApplyBox>
<ReviewBox>
<span>To be reviewed</span>
<MediumNumber>{toBeReviewed}</MediumNumber>
<MediumNumber>{reviewRequired}</MediumNumber>
</ReviewBox>
</OpenBox>
<NumberBox>

View File

@ -24,7 +24,14 @@ test('Show outdated SDKs and apps using them', async () => {
});
render(
<Routes>
<Route path={'/projects/:projectId'} element={<FlagTypesUsed />} />
<Route
path={'/projects/:projectId'}
element={
<FlagTypesUsed
featureTypeCounts={[{ type: 'release', count: 57 }]}
/>
}
/>
</Routes>,
{
route: '/projects/default',

View File

@ -1,10 +1,9 @@
import { useMemo } from 'react';
import { type FC, useMemo } from 'react';
import { styled, type SvgIconTypeMap, Typography } from '@mui/material';
import { getFeatureTypeIcons } from 'utils/getFeatureTypeIcons';
import type { OverridableComponent } from '@mui/material/OverridableComponent';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useProjectOverview from 'hooks/api/getters/useProjectOverview/useProjectOverview';
import type { FeatureTypeCountSchema } from '../../../../../openapi';
export const StyledProjectInfoWidgetContainer = styled('div')(({ theme }) => ({
margin: '0',
@ -64,12 +63,9 @@ const FlagTypesRow = ({ type, Icon, count }: IFlagTypeRowProps) => {
);
};
export const FlagTypesUsed = () => {
const projectId = useRequiredPathParam('projectId');
const { project } = useProjectOverview(projectId);
const { featureTypeCounts } = project;
export const FlagTypesUsed: FC<{
featureTypeCounts: FeatureTypeCountSchema[];
}> = ({ featureTypeCounts }) => {
const featureTypeStats = useMemo(() => {
const release =
featureTypeCounts.find(

View File

@ -2,6 +2,8 @@ import { ProjectHealthChart } from './ProjectHealthChart';
import { Alert, Box, styled, Typography, useTheme } from '@mui/material';
import { Link } from 'react-router-dom';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import type { ProjectInsightsSchemaHealth } from '../../../../../openapi';
import type { FC } from 'react';
const Dot = styled('span', {
shouldForwardProp: (prop) => prop !== 'color',
@ -36,13 +38,12 @@ const StatusWithDot = styled(Box)(({ theme }) => ({
gap: theme.spacing(1),
}));
export const ProjectHealth = () => {
export const ProjectHealth: FC<{ health: ProjectInsightsSchemaHealth }> = ({
health,
}) => {
const theme = useTheme();
const projectId = useRequiredPathParam('projectId');
const active = 15;
const stale = 10;
const potentiallyStale = 3;
const health = 93;
const { staleCount, potentiallyStaleCount, activeCount, rating } = health;
return (
<Container>
@ -52,6 +53,7 @@ export const ProjectHealth = () => {
flags
</Alert>
<Box
data-loading
sx={(theme) => ({
display: 'flex',
gap: theme.spacing(4),
@ -59,10 +61,10 @@ export const ProjectHealth = () => {
})}
>
<ProjectHealthChart
active={active}
stale={stale}
potentiallyStale={potentiallyStale}
health={health}
active={activeCount}
stale={staleCount}
potentiallyStale={potentiallyStaleCount}
health={rating}
/>
<FlagCounts>
<Box>
@ -70,7 +72,7 @@ export const ProjectHealth = () => {
<Dot color={theme.palette.success.border} />
<Box sx={{ fontWeight: 'bold' }}>Active</Box>
</StatusWithDot>
<FlagsCount>{active} feature flags</FlagsCount>
<FlagsCount>{activeCount} feature flags</FlagsCount>
</Box>
<Box>
<StatusWithDot>
@ -81,7 +83,7 @@ export const ProjectHealth = () => {
<Link to='/feature-toggle-type'>(configure)</Link>
</StatusWithDot>
<FlagsCount>
{potentiallyStale} feature flags
{potentiallyStaleCount} feature flags
</FlagsCount>
</Box>
<Box>
@ -92,7 +94,7 @@ export const ProjectHealth = () => {
(view flags)
</Link>
</StatusWithDot>
<FlagsCount>{stale} feature flags</FlagsCount>
<FlagsCount>{staleCount} feature flags</FlagsCount>
</Box>
</FlagCounts>
</Box>

View File

@ -4,6 +4,9 @@ import { LeadTimeForChanges } from './LeadTimeForChanges/LeadTimeForChanges';
import { ProjectHealth } from './ProjectHealth/ProjectHealth';
import { FlagTypesUsed } from './FlagTypesUsed/FlagTypesUsed';
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 }) => ({
backgroundColor: theme.palette.background.paper,
@ -33,37 +36,31 @@ const NarrowContainer = styled(Container)(() => ({
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 = () => {
const projectId = useRequiredPathParam('projectId');
const { data, loading } = useProjectInsights(projectId);
const ref = useLoading(loading);
return (
<Grid>
<Grid ref={ref}>
<FullWidthContainer>
<ProjectInsightsStats {...statsData} />
<ProjectInsightsStats stats={data.stats} />
</FullWidthContainer>
<MediumWideContainer>
<ProjectHealth />
<ProjectHealth health={data.health} />
</MediumWideContainer>
<WideContainer>
<LeadTimeForChanges />
</WideContainer>
<NarrowContainer>
<FlagTypesUsed />
<FlagTypesUsed featureTypeCounts={data.featureTypeCounts} />
</NarrowContainer>
<NarrowContainer>Project members</NarrowContainer>
<WideContainer>
<ChangeRequests />
{data.changeRequests && (
<ChangeRequests changeRequests={data.changeRequests} />
)}
</WideContainer>
</Grid>
);

View File

@ -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 };
};