mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-18 13:48:58 +02:00
feat: rename health to technical debt (#10063)
On insights and project status, we would like to show "technica debt" instead of "health". New value is that of `1/health`, or simplified: `healthy flags / total flags`
This commit is contained in:
parent
8d7a0fdd7f
commit
37548c3436
@ -18,6 +18,7 @@ import { allOption } from 'component/common/ProjectSelect/ProjectSelect';
|
|||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import { WidgetTitle } from './components/WidgetTitle/WidgetTitle.tsx';
|
import { WidgetTitle } from './components/WidgetTitle/WidgetTitle.tsx';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
|
||||||
export interface IChartsProps {
|
export interface IChartsProps {
|
||||||
flagTrends: InstanceInsightsSchema['flagTrends'];
|
flagTrends: InstanceInsightsSchema['flagTrends'];
|
||||||
@ -104,6 +105,7 @@ export const InsightsCharts: FC<IChartsProps> = ({
|
|||||||
const showAllProjects = projects[0] === allOption.id;
|
const showAllProjects = projects[0] === allOption.id;
|
||||||
const isOneProjectSelected = projects.length === 1;
|
const isOneProjectSelected = projects.length === 1;
|
||||||
const { isEnterprise } = useUiConfig();
|
const { isEnterprise } = useUiConfig();
|
||||||
|
const healthToDebtEnabled = useFlag('healthToTechDebt');
|
||||||
|
|
||||||
const lastUserTrend = userTrends[userTrends.length - 1];
|
const lastUserTrend = userTrends[userTrends.length - 1];
|
||||||
const lastFlagTrend = flagTrends[flagTrends.length - 1];
|
const lastFlagTrend = flagTrends[flagTrends.length - 1];
|
||||||
@ -189,9 +191,15 @@ export const InsightsCharts: FC<IChartsProps> = ({
|
|||||||
potentiallyStale={summary.potentiallyStale}
|
potentiallyStale={summary.potentiallyStale}
|
||||||
title={
|
title={
|
||||||
<WidgetTitle
|
<WidgetTitle
|
||||||
title='Health'
|
title={
|
||||||
|
healthToDebtEnabled
|
||||||
|
? 'Technical debt'
|
||||||
|
: 'Health'
|
||||||
|
}
|
||||||
tooltip={
|
tooltip={
|
||||||
'Percentage of flags that are not stale or potentially stale.'
|
healthToDebtEnabled
|
||||||
|
? 'Percentage of stale and potentially stale flags.'
|
||||||
|
: 'Percentage of flags that are not stale or potentially stale.'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import { Badge } from 'component/common/Badge/Badge';
|
|||||||
import type { TooltipState } from 'component/insights/components/LineChart/ChartTooltip/ChartTooltip';
|
import type { TooltipState } from 'component/insights/components/LineChart/ChartTooltip/ChartTooltip';
|
||||||
import { HorizontalDistributionChart } from 'component/insights/components/HorizontalDistributionChart/HorizontalDistributionChart';
|
import { HorizontalDistributionChart } from 'component/insights/components/HorizontalDistributionChart/HorizontalDistributionChart';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
import { getTechnicalDebtColor } from 'utils/getTechnicalDebtColor.ts';
|
||||||
|
|
||||||
const StyledTooltipItemContainer = styled(Paper)(({ theme }) => ({
|
const StyledTooltipItemContainer = styled(Paper)(({ theme }) => ({
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(2),
|
||||||
@ -33,6 +35,13 @@ const getHealthBadgeColor = (health?: number | null) => {
|
|||||||
return 'error';
|
return 'error';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTechnicalDebtBadgeColor = (technicalDebt?: number | null) => {
|
||||||
|
if (technicalDebt === undefined || technicalDebt === null) {
|
||||||
|
return 'info';
|
||||||
|
}
|
||||||
|
return getTechnicalDebtColor(technicalDebt);
|
||||||
|
};
|
||||||
|
|
||||||
const Distribution = ({ stale = 0, potentiallyStale = 0, total = 0 }) => {
|
const Distribution = ({ stale = 0, potentiallyStale = 0, total = 0 }) => {
|
||||||
const healthyFlagCount = total - stale - potentiallyStale;
|
const healthyFlagCount = total - stale - potentiallyStale;
|
||||||
|
|
||||||
@ -99,12 +108,16 @@ const Distribution = ({ stale = 0, potentiallyStale = 0, total = 0 }) => {
|
|||||||
export const HealthTooltip: FC<{ tooltip: TooltipState | null }> = ({
|
export const HealthTooltip: FC<{ tooltip: TooltipState | null }> = ({
|
||||||
tooltip,
|
tooltip,
|
||||||
}) => {
|
}) => {
|
||||||
|
const healthToTechDebtEnabled = useFlag('healthToTechDebt');
|
||||||
|
|
||||||
const data = tooltip?.dataPoints.map((point) => {
|
const data = tooltip?.dataPoints.map((point) => {
|
||||||
return {
|
return {
|
||||||
label: point.label,
|
label: point.label,
|
||||||
title: point.dataset.label,
|
title: point.dataset.label,
|
||||||
color: point.dataset.borderColor,
|
color: point.dataset.borderColor,
|
||||||
value: point.raw as InstanceInsightsSchemaProjectFlagTrendsItem,
|
value: point.raw as InstanceInsightsSchemaProjectFlagTrendsItem & {
|
||||||
|
technicalDebt?: number | null;
|
||||||
|
}, // TODO: get from backend
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,7 +150,9 @@ export const HealthTooltip: FC<{ tooltip: TooltipState | null }> = ({
|
|||||||
color='textSecondary'
|
color='textSecondary'
|
||||||
component='span'
|
component='span'
|
||||||
>
|
>
|
||||||
Project health
|
{healthToTechDebtEnabled
|
||||||
|
? 'Technical debt'
|
||||||
|
: 'Project health'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</StyledItemHeader>
|
</StyledItemHeader>
|
||||||
<StyledItemHeader>
|
<StyledItemHeader>
|
||||||
@ -150,9 +165,21 @@ export const HealthTooltip: FC<{ tooltip: TooltipState | null }> = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
<strong>{point.title}</strong>
|
<strong>{point.title}</strong>
|
||||||
</Typography>
|
</Typography>
|
||||||
<Badge color={getHealthBadgeColor(point.value.health)}>
|
{healthToTechDebtEnabled ? (
|
||||||
{point.value.health}%
|
<Badge
|
||||||
</Badge>
|
color={getTechnicalDebtBadgeColor(
|
||||||
|
point.value.technicalDebt,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{point.value.technicalDebt}%
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
<Badge
|
||||||
|
color={getHealthBadgeColor(point.value.health)}
|
||||||
|
>
|
||||||
|
{point.value.health}%
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</StyledItemHeader>{' '}
|
</StyledItemHeader>{' '}
|
||||||
<Divider
|
<Divider
|
||||||
sx={(theme) => ({ margin: theme.spacing(1.5, 0) })}
|
sx={(theme) => ({ margin: theme.spacing(1.5, 0) })}
|
||||||
|
@ -2,7 +2,10 @@ import 'chartjs-adapter-date-fns';
|
|||||||
import { type FC, useMemo } from 'react';
|
import { type FC, useMemo } from 'react';
|
||||||
import type { InstanceInsightsSchema } from 'openapi';
|
import type { InstanceInsightsSchema } from 'openapi';
|
||||||
import { HealthTooltip } from './HealthChartTooltip/HealthChartTooltip.tsx';
|
import { HealthTooltip } from './HealthChartTooltip/HealthChartTooltip.tsx';
|
||||||
import { useProjectChartData } from 'component/insights/hooks/useProjectChartData';
|
import {
|
||||||
|
calculateTechDebt,
|
||||||
|
useProjectChartData,
|
||||||
|
} from 'component/insights/hooks/useProjectChartData';
|
||||||
import {
|
import {
|
||||||
fillGradientPrimary,
|
fillGradientPrimary,
|
||||||
LineChart,
|
LineChart,
|
||||||
@ -11,6 +14,7 @@ import {
|
|||||||
import { useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
import type { GroupedDataByProject } from 'component/insights/hooks/useGroupedProjectTrends';
|
import type { GroupedDataByProject } from 'component/insights/hooks/useGroupedProjectTrends';
|
||||||
import { usePlaceholderData } from 'component/insights/hooks/usePlaceholderData';
|
import { usePlaceholderData } from 'component/insights/hooks/usePlaceholderData';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
|
||||||
interface IProjectHealthChartProps {
|
interface IProjectHealthChartProps {
|
||||||
projectFlagTrends: GroupedDataByProject<
|
projectFlagTrends: GroupedDataByProject<
|
||||||
@ -42,6 +46,7 @@ export const ProjectHealthChart: FC<IProjectHealthChartProps> = ({
|
|||||||
const projectsData = useProjectChartData(projectFlagTrends);
|
const projectsData = useProjectChartData(projectFlagTrends);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const placeholderData = usePlaceholderData();
|
const placeholderData = usePlaceholderData();
|
||||||
|
const healthToTechDebtEnabled = useFlag('healthToTechDebt');
|
||||||
|
|
||||||
const aggregateHealthData = useMemo(() => {
|
const aggregateHealthData = useMemo(() => {
|
||||||
const labels = Array.from(
|
const labels = Array.from(
|
||||||
@ -80,9 +85,18 @@ export const ProjectHealthChart: FC<IProjectHealthChartProps> = ({
|
|||||||
return {
|
return {
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Health',
|
label: healthToTechDebtEnabled
|
||||||
|
? 'Technical debt'
|
||||||
|
: 'Health',
|
||||||
data: weeks.map((item) => ({
|
data: weeks.map((item) => ({
|
||||||
health: item.total ? calculateHealth(item) : undefined,
|
health: item.total ? calculateHealth(item) : undefined,
|
||||||
|
...(healthToTechDebtEnabled
|
||||||
|
? {
|
||||||
|
technicalDebt: item.total
|
||||||
|
? calculateTechDebt(item)
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
date: item.date,
|
date: item.date,
|
||||||
total: item.total,
|
total: item.total,
|
||||||
stale: item.stale,
|
stale: item.stale,
|
||||||
@ -117,7 +131,12 @@ export const ProjectHealthChart: FC<IProjectHealthChartProps> = ({
|
|||||||
notEnoughData
|
notEnoughData
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
parsing: { yAxisKey: 'health', xAxisKey: 'date' },
|
parsing: {
|
||||||
|
yAxisKey: healthToTechDebtEnabled
|
||||||
|
? 'technicalDebt'
|
||||||
|
: 'health',
|
||||||
|
xAxisKey: 'date',
|
||||||
|
},
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
min: 0,
|
min: 0,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { FC, ReactNode } from 'react';
|
import type { FC, ReactNode } from 'react';
|
||||||
import { Box, Divider, Link, styled } from '@mui/material';
|
import { Box, Divider, Link, styled } from '@mui/material';
|
||||||
import { ReactComponent as InstanceHealthIcon } from 'assets/icons/instance-health.svg';
|
import { ReactComponent as InstanceHealthIcon } from 'assets/icons/instance-health.svg';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
|
||||||
interface IHealthStatsProps {
|
interface IHealthStatsProps {
|
||||||
value?: string | number;
|
value?: string | number;
|
||||||
@ -69,43 +70,59 @@ export const HealthStats: FC<IHealthStatsProps> = ({
|
|||||||
stale,
|
stale,
|
||||||
potentiallyStale,
|
potentiallyStale,
|
||||||
title,
|
title,
|
||||||
}) => (
|
}) => {
|
||||||
<StyledContainer>
|
const healthToDebtEnabled = useFlag('healthToTechDebt');
|
||||||
<StyledHeader>
|
|
||||||
<StyledSection>{title}</StyledSection>
|
// TODO: get the following from backend
|
||||||
<StyledSection>{/* TODO: trend */}</StyledSection>
|
const unhealthy = stale + potentiallyStale;
|
||||||
</StyledHeader>
|
const technicalDebtValue = (
|
||||||
<Divider />
|
(unhealthy / (healthy + unhealthy)) *
|
||||||
<StyledSection>
|
100
|
||||||
<StyledStatsRow>
|
).toFixed(1);
|
||||||
<StyledIcon />
|
|
||||||
Instance health
|
return (
|
||||||
<StyledMainValue>{`${value || 0}%`}</StyledMainValue>
|
<StyledContainer>
|
||||||
</StyledStatsRow>
|
<StyledHeader>
|
||||||
</StyledSection>
|
<StyledSection>{title}</StyledSection>
|
||||||
<Divider />
|
</StyledHeader>
|
||||||
<FlagsSection>
|
<Divider />
|
||||||
<StyledStatsRow>
|
<StyledSection>
|
||||||
Healthy flags
|
<StyledStatsRow>
|
||||||
<StyledValue>{healthy || 0}</StyledValue>
|
<StyledIcon />
|
||||||
</StyledStatsRow>
|
{healthToDebtEnabled ? 'Technical debt' : 'Instance health'}
|
||||||
<StyledStatsRow>
|
{healthToDebtEnabled ? (
|
||||||
Stale flags
|
<StyledMainValue>{`${technicalDebtValue}%`}</StyledMainValue>
|
||||||
<StyledValue>{stale || 0}</StyledValue>
|
) : (
|
||||||
</StyledStatsRow>
|
<StyledMainValue>{`${value || 0}%`}</StyledMainValue>
|
||||||
<StyledStatsRow>
|
)}
|
||||||
Potentially stale flags
|
</StyledStatsRow>
|
||||||
<StyledValue>{potentiallyStale || 0}</StyledValue>
|
</StyledSection>
|
||||||
</StyledStatsRow>
|
<Divider />
|
||||||
<ExplanationRow>
|
<FlagsSection>
|
||||||
<Link
|
<StyledStatsRow>
|
||||||
href='https://docs.getunleash.io/reference/insights#health'
|
Healthy flags
|
||||||
target='_blank'
|
<StyledValue>{healthy || 0}</StyledValue>
|
||||||
rel='noreferrer'
|
</StyledStatsRow>
|
||||||
>
|
<StyledStatsRow>
|
||||||
What affects instance health?
|
Stale flags
|
||||||
</Link>
|
<StyledValue>{stale || 0}</StyledValue>
|
||||||
</ExplanationRow>
|
</StyledStatsRow>
|
||||||
</FlagsSection>
|
<StyledStatsRow>
|
||||||
</StyledContainer>
|
Potentially stale flags
|
||||||
);
|
<StyledValue>{potentiallyStale || 0}</StyledValue>
|
||||||
|
</StyledStatsRow>
|
||||||
|
<ExplanationRow>
|
||||||
|
<Link
|
||||||
|
href='https://docs.getunleash.io/reference/insights#health'
|
||||||
|
target='_blank'
|
||||||
|
rel='noreferrer'
|
||||||
|
>
|
||||||
|
{healthToDebtEnabled
|
||||||
|
? 'What affects technical debt?'
|
||||||
|
: 'What affects instance health?'}
|
||||||
|
</Link>
|
||||||
|
</ExplanationRow>
|
||||||
|
</FlagsSection>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -4,13 +4,29 @@ import { useProjectColor } from './useProjectColor.js';
|
|||||||
import { useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
import type { GroupedDataByProject } from './useGroupedProjectTrends.js';
|
import type { GroupedDataByProject } from './useGroupedProjectTrends.js';
|
||||||
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
import useProjects from 'hooks/api/getters/useProjects/useProjects';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
|
||||||
type ProjectFlagTrends = InstanceInsightsSchema['projectFlagTrends'];
|
type ProjectFlagTrends = InstanceInsightsSchema['projectFlagTrends'];
|
||||||
|
|
||||||
|
export const calculateTechDebt = (item: {
|
||||||
|
total: number;
|
||||||
|
stale: number;
|
||||||
|
potentiallyStale: number;
|
||||||
|
}) => {
|
||||||
|
if (!item.total) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (((item.stale + item.potentiallyStale) / item.total) * 100).toFixed(
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const useProjectChartData = (
|
export const useProjectChartData = (
|
||||||
projectFlagTrends: GroupedDataByProject<ProjectFlagTrends>,
|
projectFlagTrends: GroupedDataByProject<ProjectFlagTrends>,
|
||||||
) => {
|
) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const healthToTechDebtEnabled = useFlag('healthToTechDebt');
|
||||||
const getProjectColor = useProjectColor();
|
const getProjectColor = useProjectColor();
|
||||||
const { projects } = useProjects();
|
const { projects } = useProjects();
|
||||||
const projectNames = new Map(
|
const projectNames = new Map(
|
||||||
@ -23,7 +39,17 @@ export const useProjectChartData = (
|
|||||||
const color = getProjectColor(project);
|
const color = getProjectColor(project);
|
||||||
return {
|
return {
|
||||||
label: projectNames.get(project) || project,
|
label: projectNames.get(project) || project,
|
||||||
data: trends,
|
data: trends.map((item) => ({
|
||||||
|
...item,
|
||||||
|
|
||||||
|
...(healthToTechDebtEnabled
|
||||||
|
? {
|
||||||
|
technicalDebt: item.total
|
||||||
|
? calculateTechDebt(item)
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
})),
|
||||||
borderColor: color,
|
borderColor: color,
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
fill: false,
|
fill: false,
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
StyledWidgetContent,
|
StyledWidgetContent,
|
||||||
StyledWidgetStats,
|
StyledWidgetStats,
|
||||||
} from '../InsightsCharts.styles';
|
} from '../InsightsCharts.styles';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
|
||||||
export const PerformanceInsights: FC = () => {
|
export const PerformanceInsights: FC = () => {
|
||||||
const statePrefix = 'performance-';
|
const statePrefix = 'performance-';
|
||||||
@ -47,6 +48,8 @@ export const PerformanceInsights: FC = () => {
|
|||||||
state[`${statePrefix}to`]?.values[0],
|
state[`${statePrefix}to`]?.values[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const healthToTechDebtEnabled = useFlag('healthToTechDebt');
|
||||||
|
|
||||||
const projects = state[`${statePrefix}project`]?.values ?? [allOption.id];
|
const projects = state[`${statePrefix}project`]?.values ?? [allOption.id];
|
||||||
|
|
||||||
const showAllProjects = projects[0] === allOption.id;
|
const showAllProjects = projects[0] === allOption.id;
|
||||||
@ -131,12 +134,21 @@ export const PerformanceInsights: FC = () => {
|
|||||||
stale={summary.stale}
|
stale={summary.stale}
|
||||||
potentiallyStale={summary.potentiallyStale}
|
potentiallyStale={summary.potentiallyStale}
|
||||||
title={
|
title={
|
||||||
<WidgetTitle
|
healthToTechDebtEnabled ? (
|
||||||
title='Health'
|
<WidgetTitle
|
||||||
tooltip={
|
title='Technical debt'
|
||||||
'Percentage of flags that are not stale or potentially stale.'
|
tooltip={
|
||||||
}
|
'Percentage of flags that are stale or potentially stale.'
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<WidgetTitle
|
||||||
|
title='Health'
|
||||||
|
tooltip={
|
||||||
|
'Percentage of flags that are not stale or potentially stale.'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</StyledWidgetStats>
|
</StyledWidgetStats>
|
||||||
|
@ -39,10 +39,14 @@ import { ActionBox } from './ActionBox.tsx';
|
|||||||
import useLoading from 'hooks/useLoading';
|
import useLoading from 'hooks/useLoading';
|
||||||
import { NoProjectsContactAdmin } from './NoProjectsContactAdmin.tsx';
|
import { NoProjectsContactAdmin } from './NoProjectsContactAdmin.tsx';
|
||||||
import { AskOwnerToAddYouToTheirProject } from './AskOwnerToAddYouToTheirProject.tsx';
|
import { AskOwnerToAddYouToTheirProject } from './AskOwnerToAddYouToTheirProject.tsx';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
|
||||||
const ActiveProjectDetails: FC<{
|
const ActiveProjectDetails: FC<{
|
||||||
project: PersonalDashboardSchemaProjectsItem;
|
project: PersonalDashboardSchemaProjectsItem;
|
||||||
}> = ({ project }) => {
|
}> = ({ project }) => {
|
||||||
|
const healthToTechDebtEnabled = useFlag('healthToTechDebt');
|
||||||
|
|
||||||
|
const techicalDebt = 100 - project.health; // TODO: health to technical debt from backend
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
@ -63,10 +67,10 @@ const ActiveProjectDetails: FC<{
|
|||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<Typography variant='subtitle2' color='primary'>
|
<Typography variant='subtitle2' color='primary'>
|
||||||
{project.health}%
|
{healthToTechDebtEnabled ? techicalDebt : project.health}%
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant='caption' color='text.secondary'>
|
<Typography variant='caption' color='text.secondary'>
|
||||||
health
|
{healthToTechDebtEnabled ? 'technical debt' : 'health'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import Lightbulb from '@mui/icons-material/LightbulbOutlined';
|
import Lightbulb from '@mui/icons-material/LightbulbOutlined';
|
||||||
import type { PersonalDashboardProjectDetailsSchemaInsights } from 'openapi';
|
import type { PersonalDashboardProjectDetailsSchemaInsights } from 'openapi';
|
||||||
import { ActionBox } from './ActionBox.tsx';
|
import { ActionBox } from './ActionBox.tsx';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
|
||||||
const PercentageScore = styled('span')(({ theme }) => ({
|
const PercentageScore = styled('span')(({ theme }) => ({
|
||||||
fontWeight: theme.typography.fontWeightBold,
|
fontWeight: theme.typography.fontWeightBold,
|
||||||
@ -57,9 +58,11 @@ const ProjectHealthMessage: FC<{
|
|||||||
insights: PersonalDashboardProjectDetailsSchemaInsights;
|
insights: PersonalDashboardProjectDetailsSchemaInsights;
|
||||||
project: string;
|
project: string;
|
||||||
}> = ({ trend, insights, project }) => {
|
}> = ({ trend, insights, project }) => {
|
||||||
|
const healthToTechDebtEnabled = useFlag('healthToTechDebt');
|
||||||
const { avgHealthCurrentWindow, avgHealthPastWindow, health } = insights;
|
const { avgHealthCurrentWindow, avgHealthPastWindow, health } = insights;
|
||||||
const improveMessage =
|
const improveMessage = healthToTechDebtEnabled
|
||||||
'Remember to archive your stale feature flags to keep the project health growing.';
|
? 'Remember to archive your stale feature flags to keep the technical debt low.'
|
||||||
|
: 'Remember to archive your stale feature flags to keep the project health growing.';
|
||||||
const keepDoingMessage =
|
const keepDoingMessage =
|
||||||
'This indicates that you are doing a good job of archiving your feature flags.';
|
'This indicates that you are doing a good job of archiving your feature flags.';
|
||||||
|
|
||||||
|
@ -373,6 +373,7 @@ export const Project = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
{/* FIXME: remove /health with `healthToTechDebt` flag - redirect to project status */}
|
||||||
<Route path='health' element={<ProjectHealth />} />
|
<Route path='health' element={<ProjectHealth />} />
|
||||||
<Route
|
<Route
|
||||||
path='access/*'
|
path='access/*'
|
||||||
|
@ -5,6 +5,8 @@ import { useProjectStatus } from 'hooks/api/getters/useProjectStatus/useProjectS
|
|||||||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
|
||||||
import { HealthGridTile } from './ProjectHealthGrid.styles';
|
import { HealthGridTile } from './ProjectHealthGrid.styles';
|
||||||
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
import { getTechnicalDebtColor } from 'utils/getTechnicalDebtColor.ts';
|
||||||
|
|
||||||
const ChartRadius = 40;
|
const ChartRadius = 40;
|
||||||
const ChartStrokeWidth = 13;
|
const ChartStrokeWidth = 13;
|
||||||
@ -80,6 +82,29 @@ const UnhealthyFlagBox = ({ flagCount }: { flagCount: number }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useHealthColor = (healthRating: number) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
if (healthRating <= 24) {
|
||||||
|
return theme.palette.error.main;
|
||||||
|
}
|
||||||
|
if (healthRating <= 74) {
|
||||||
|
return theme.palette.warning.border;
|
||||||
|
}
|
||||||
|
return theme.palette.success.border;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useTechnicalDebtColor = (techicalDebt: number) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
switch (getTechnicalDebtColor(techicalDebt)) {
|
||||||
|
case 'error':
|
||||||
|
return theme.palette.error.main;
|
||||||
|
case 'warning':
|
||||||
|
return theme.palette.warning.border;
|
||||||
|
default:
|
||||||
|
return theme.palette.success.border;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const Wrapper = styled(HealthGridTile)(({ theme }) => ({
|
const Wrapper = styled(HealthGridTile)(({ theme }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@ -92,22 +117,21 @@ export const ProjectHealth = () => {
|
|||||||
const {
|
const {
|
||||||
data: { health, staleFlags },
|
data: { health, staleFlags },
|
||||||
} = useProjectStatus(projectId);
|
} = useProjectStatus(projectId);
|
||||||
const healthRating = health.current;
|
|
||||||
const { isOss } = useUiConfig();
|
const { isOss } = useUiConfig();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const circumference = 2 * Math.PI * ChartRadius; //
|
const healthToDebtEnabled = useFlag('healthToTechDebt');
|
||||||
|
const circumference = 2 * Math.PI * ChartRadius;
|
||||||
|
const healthRating = health.current;
|
||||||
|
const technicalDebt = 100 - healthRating; // TODO: get from backend
|
||||||
|
|
||||||
const gapLength = 0.3;
|
const gapLength = 0.3;
|
||||||
const filledLength = 1 - gapLength;
|
const filledLength = 1 - gapLength;
|
||||||
const offset = 0.75 - gapLength / 2;
|
const offset = 0.75 - gapLength / 2;
|
||||||
const healthLength = (healthRating / 100) * circumference * 0.7;
|
const healthLength = (healthRating / 100) * circumference * 0.7;
|
||||||
|
const technicalDebtLength = (technicalDebt / 100) * circumference * 0.7;
|
||||||
|
|
||||||
const healthColor =
|
const healthColor = useHealthColor(healthRating);
|
||||||
healthRating >= 0 && healthRating <= 24
|
const technicalDebtColor = useTechnicalDebtColor(technicalDebt);
|
||||||
? theme.palette.error.main
|
|
||||||
: healthRating >= 25 && healthRating <= 74
|
|
||||||
? theme.palette.warning.border
|
|
||||||
: theme.palette.success.border;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
@ -129,9 +153,17 @@ export const ProjectHealth = () => {
|
|||||||
cy='50'
|
cy='50'
|
||||||
r={ChartRadius}
|
r={ChartRadius}
|
||||||
fill='none'
|
fill='none'
|
||||||
stroke={healthColor}
|
stroke={
|
||||||
|
healthToDebtEnabled
|
||||||
|
? technicalDebtColor
|
||||||
|
: healthColor
|
||||||
|
}
|
||||||
strokeWidth={ChartStrokeWidth}
|
strokeWidth={ChartStrokeWidth}
|
||||||
strokeDasharray={`${healthLength} ${circumference - healthLength}`}
|
strokeDasharray={
|
||||||
|
healthToDebtEnabled
|
||||||
|
? `${technicalDebtLength} ${circumference - technicalDebtLength}`
|
||||||
|
: `${healthLength} ${circumference - healthLength}`
|
||||||
|
}
|
||||||
strokeDashoffset={offset * circumference}
|
strokeDashoffset={offset * circumference}
|
||||||
/>
|
/>
|
||||||
<text
|
<text
|
||||||
@ -142,17 +174,30 @@ export const ProjectHealth = () => {
|
|||||||
fill={theme.palette.text.primary}
|
fill={theme.palette.text.primary}
|
||||||
fontSize={theme.typography.h1.fontSize}
|
fontSize={theme.typography.h1.fontSize}
|
||||||
>
|
>
|
||||||
{healthRating}%
|
{healthToDebtEnabled ? technicalDebt : healthRating}
|
||||||
|
%
|
||||||
</text>
|
</text>
|
||||||
</StyledSVG>
|
</StyledSVG>
|
||||||
</SVGWrapper>
|
</SVGWrapper>
|
||||||
<TextContainer>
|
<TextContainer>
|
||||||
<Typography>
|
<Typography>
|
||||||
Your current project health rating is {healthRating}%
|
{healthToDebtEnabled ? (
|
||||||
|
<>
|
||||||
|
Your current technical debt rating is{' '}
|
||||||
|
{technicalDebt}%.
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
Your current project health rating is{' '}
|
||||||
|
{healthRating}%.
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
{!isOss() && (
|
{!isOss() && (
|
||||||
<Link to={`/insights?project=IS%3A${projectId}`}>
|
<Link to={`/insights?project=IS%3A${projectId}`}>
|
||||||
View health over time
|
{healthToDebtEnabled
|
||||||
|
? 'View technical debt over time'
|
||||||
|
: 'View health over time'}
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</TextContainer>
|
</TextContainer>
|
||||||
|
@ -9,6 +9,7 @@ import { ProjectHealthGrid } from './ProjectHealthGrid.tsx';
|
|||||||
import { useFeedback } from 'component/feedbackNew/useFeedback';
|
import { useFeedback } from 'component/feedbackNew/useFeedback';
|
||||||
import FeedbackIcon from '@mui/icons-material/ChatOutlined';
|
import FeedbackIcon from '@mui/icons-material/ChatOutlined';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { useFlag } from '@unleash/proxy-client-react';
|
||||||
|
|
||||||
const ModalContentContainer = styled('section')(({ theme }) => ({
|
const ModalContentContainer = styled('section')(({ theme }) => ({
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
@ -140,6 +141,7 @@ export const ProjectStatusModal = ({ open, onClose, onFollowLink }: Props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
const { isOss } = useUiConfig();
|
const { isOss } = useUiConfig();
|
||||||
|
const healthToDebtEnabled = useFlag('healthToTechDebt');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicSidebarModal
|
<DynamicSidebarModal
|
||||||
@ -159,7 +161,9 @@ export const ProjectStatusModal = ({ open, onClose, onFollowLink }: Props) => {
|
|||||||
</HeaderRow>
|
</HeaderRow>
|
||||||
<WidgetContainer>
|
<WidgetContainer>
|
||||||
<Row>
|
<Row>
|
||||||
<RowHeader>Health</RowHeader>
|
<RowHeader>
|
||||||
|
{healthToDebtEnabled ? 'Technical debt' : 'Health'}
|
||||||
|
</RowHeader>
|
||||||
<ProjectHealthGrid />
|
<ProjectHealthGrid />
|
||||||
</Row>
|
</Row>
|
||||||
{!isOss() && (
|
{!isOss() && (
|
||||||
|
13
frontend/src/utils/getTechnicalDebtColor.ts
Normal file
13
frontend/src/utils/getTechnicalDebtColor.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Consistent values for boundries between healthy, warning and error colors
|
||||||
|
* @param technicalDebt {Number} 0-100
|
||||||
|
*/
|
||||||
|
export const getTechnicalDebtColor = (technicalDebt: number) => {
|
||||||
|
if (technicalDebt >= 50) {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
|
if (technicalDebt >= 25) {
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
|
return 'success';
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user