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:
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 () => {
|
||||
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',
|
||||
|
@ -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>
|
||||
|
@ -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',
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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