1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-06-27 01:19:00 +02:00

fix: use technicalDebt property from backend (#10111)

Frontend should load `technicaDebt` from backend instead of
re-calculating it.
This commit is contained in:
Tymoteusz Czech 2025-06-12 16:50:29 +02:00 committed by GitHub
parent d7da5fe6a4
commit 4e48d90ed8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 92 additions and 31 deletions

View File

@ -115,9 +115,7 @@ export const HealthTooltip: FC<{ tooltip: TooltipState | null }> = ({
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
}; };
}); });

View File

@ -5,6 +5,7 @@ import { useFlag } from '@unleash/proxy-client-react';
interface IHealthStatsProps { interface IHealthStatsProps {
value?: string | number; value?: string | number;
technicalDebt?: string | number;
healthy: number; healthy: number;
stale: number; stale: number;
potentiallyStale: number; potentiallyStale: number;
@ -66,6 +67,7 @@ const StyledMainValue = styled(StyledValue)(({ theme }) => ({
export const HealthStats: FC<IHealthStatsProps> = ({ export const HealthStats: FC<IHealthStatsProps> = ({
value, value,
technicalDebt,
healthy, healthy,
stale, stale,
potentiallyStale, potentiallyStale,
@ -73,13 +75,6 @@ export const HealthStats: FC<IHealthStatsProps> = ({
}) => { }) => {
const healthToDebtEnabled = useFlag('healthToTechDebt'); const healthToDebtEnabled = useFlag('healthToTechDebt');
// TODO: get the following from backend
const unhealthy = stale + potentiallyStale;
const technicalDebtValue = (
(unhealthy / (healthy + unhealthy)) *
100
).toFixed(1);
return ( return (
<StyledContainer> <StyledContainer>
<StyledHeader> <StyledHeader>
@ -91,7 +86,7 @@ export const HealthStats: FC<IHealthStatsProps> = ({
<StyledIcon /> <StyledIcon />
{healthToDebtEnabled ? 'Technical debt' : 'Instance health'} {healthToDebtEnabled ? 'Technical debt' : 'Instance health'}
{healthToDebtEnabled ? ( {healthToDebtEnabled ? (
<StyledMainValue>{`${technicalDebtValue}%`}</StyledMainValue> <StyledMainValue>{`${technicalDebt}%`}</StyledMainValue>
) : ( ) : (
<StyledMainValue>{`${value || 0}%`}</StyledMainValue> <StyledMainValue>{`${value || 0}%`}</StyledMainValue>
)} )}

View File

@ -64,6 +64,7 @@ describe('useFilteredFlagTrends', () => {
averageUsers: 2, averageUsers: 2,
averageHealth: '79', averageHealth: '79',
medianTimeToProduction: 3, medianTimeToProduction: 3,
technicalDebt: '21',
}); });
}); });
@ -92,6 +93,7 @@ describe('useFilteredFlagTrends', () => {
potentiallyStale: 0, potentiallyStale: 0,
averageUsers: 0, averageUsers: 0,
averageHealth: '100', averageHealth: '100',
technicalDebt: '0',
medianTimeToProduction: 4, medianTimeToProduction: 4,
}); });
}); });
@ -134,6 +136,7 @@ describe('useFilteredFlagTrends', () => {
averageUsers: 1.5, averageUsers: 1.5,
averageHealth: '100', averageHealth: '100',
medianTimeToProduction: 3.5, medianTimeToProduction: 3.5,
technicalDebt: '0',
}); });
}); });
@ -163,6 +166,7 @@ describe('useFilteredFlagTrends', () => {
averageUsers: 0, averageUsers: 0,
averageHealth: '100', averageHealth: '100',
medianTimeToProduction: undefined, medianTimeToProduction: undefined,
technicalDebt: '0',
}); });
}); });
@ -216,6 +220,7 @@ describe('useFilteredFlagTrends', () => {
averageUsers: 0, averageUsers: 0,
averageHealth: '100', averageHealth: '100',
medianTimeToProduction: 5, medianTimeToProduction: 5,
technicalDebt: '0',
}); });
}); });
}); });

View File

@ -71,6 +71,12 @@ export const useFilteredFlagsSummary = (
averageHealth: sum.total averageHealth: sum.total
? ((sum.active / (sum.total || 1)) * 100).toFixed(0) ? ((sum.active / (sum.total || 1)) * 100).toFixed(0)
: '100', : '100',
technicalDebt: sum.total
? (
((sum.stale + sum.potentiallyStale) / sum.total) *
100
).toFixed(0)
: '0',
medianTimeToProduction, medianTimeToProduction,
}; };
}, [filteredProjectFlagTrends]); }, [filteredProjectFlagTrends]);

View File

@ -130,6 +130,7 @@ export const PerformanceInsights: FC = () => {
<StyledWidgetStats width={350} padding={0}> <StyledWidgetStats width={350} padding={0}>
<HealthStats <HealthStats
value={summary.averageHealth} value={summary.averageHealth}
technicalDebt={summary.technicalDebt}
healthy={summary.active} healthy={summary.active}
stale={summary.stale} stale={summary.stale}
potentiallyStale={summary.potentiallyStale} potentiallyStale={summary.potentiallyStale}

View File

@ -46,7 +46,7 @@ const ActiveProjectDetails: FC<{
}> = ({ project }) => { }> = ({ project }) => {
const healthToTechDebtEnabled = useFlag('healthToTechDebt'); const healthToTechDebtEnabled = useFlag('healthToTechDebt');
const techicalDebt = 100 - project.health; // TODO: health to technical debt from backend const techicalDebt = project.technicalDebt;
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' }}>

View File

@ -115,23 +115,25 @@ const Wrapper = styled(HealthGridTile)(({ theme }) => ({
export const ProjectHealth = () => { export const ProjectHealth = () => {
const projectId = useRequiredPathParam('projectId'); const projectId = useRequiredPathParam('projectId');
const { const {
data: { health, staleFlags }, data: { health, technicalDebt, staleFlags },
} = useProjectStatus(projectId); } = useProjectStatus(projectId);
const { isOss } = useUiConfig(); const { isOss } = useUiConfig();
const theme = useTheme(); const theme = useTheme();
const healthToDebtEnabled = useFlag('healthToTechDebt'); const healthToDebtEnabled = useFlag('healthToTechDebt');
const circumference = 2 * Math.PI * ChartRadius; const circumference = 2 * Math.PI * ChartRadius;
const healthRating = health.current; 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 technicalDebtLength =
((technicalDebt.current || 0) / 100) * circumference * 0.7;
const healthColor = useHealthColor(healthRating); const healthColor = useHealthColor(healthRating);
const technicalDebtColor = useTechnicalDebtColor(technicalDebt); const technicalDebtColor = useTechnicalDebtColor(
technicalDebt.current || 0,
);
return ( return (
<Wrapper> <Wrapper>
@ -174,7 +176,9 @@ export const ProjectHealth = () => {
fill={theme.palette.text.primary} fill={theme.palette.text.primary}
fontSize={theme.typography.h1.fontSize} fontSize={theme.typography.h1.fontSize}
> >
{healthToDebtEnabled ? technicalDebt : healthRating} {healthToDebtEnabled
? technicalDebt.current || 0
: healthRating}
% %
</text> </text>
</StyledSVG> </StyledSVG>

View File

@ -20,6 +20,18 @@ const mockMetrics = {
}; };
describe('productivityReportViewModel', () => { describe('productivityReportViewModel', () => {
it('should show technical debt', () => {
const viewModel = productivityReportViewModel({
...mockData,
metrics: {
...mockMetrics,
health: 85,
},
});
expect(viewModel.technicalDebt).toBe('15');
});
describe('healthColor', () => { describe('healthColor', () => {
it('returns RED for health between 0 and 24', () => { it('returns RED for health between 0 and 24', () => {
const metrics: ProductivityReportMetrics = { const metrics: ProductivityReportMetrics = {
@ -65,7 +77,7 @@ describe('productivityReportViewModel', () => {
}); });
describe('healthTrendMessage', () => { describe('healthTrendMessage', () => {
it('returns correct trend message when health increased', () => { it('returns correct trend message when technica debt decreased', () => {
const metrics: ProductivityReportMetrics = { const metrics: ProductivityReportMetrics = {
...mockMetrics, ...mockMetrics,
health: 80, health: 80,
@ -82,7 +94,7 @@ describe('productivityReportViewModel', () => {
); );
}); });
it('returns correct trend message when health decreased', () => { it('returns correct trend message when technical debt increased', () => {
const metrics: ProductivityReportMetrics = { const metrics: ProductivityReportMetrics = {
...mockMetrics, ...mockMetrics,
health: 60, health: 60,
@ -99,7 +111,7 @@ describe('productivityReportViewModel', () => {
); );
}); });
it('returns correct message when health is the same', () => { it('returns correct message when technical debt is the same', () => {
const metrics: ProductivityReportMetrics = { const metrics: ProductivityReportMetrics = {
...mockMetrics, ...mockMetrics,
health: 70, health: 70,

View File

@ -27,6 +27,7 @@ export const productivityReportViewModel = ({
userName, userName,
userEmail, userEmail,
...metrics, ...metrics,
technicalDebt: Math.max(0, 100 - metrics.health).toString(),
unleashUrl, unleashUrl,
healthColor() { healthColor() {
const healthRating = this.health; const healthRating = this.health;

View File

@ -141,14 +141,53 @@ test('Can send productivity report email', async () => {
); );
expect(content.from).toBe('noreply@getunleash.ai'); expect(content.from).toBe('noreply@getunleash.ai');
expect(content.subject).toBe('Unleash - productivity report'); expect(content.subject).toBe('Unleash - productivity report');
expect(content.html.includes('Productivity Report')).toBe(true); expect(content.html).toContain('Productivity Report');
expect(content.html.includes('localhost/insights')).toBe(true); expect(content.html).toContain('localhost/insights');
expect(content.html.includes('localhost/profile')).toBe(true); expect(content.html).toContain('localhost/profile');
expect(content.html.includes('#68a611')).toBe(true); expect(content.html).toContain('#68a611');
expect(content.html.includes('10% more than previous month')).toBe(true); expect(content.html).toContain('1%');
expect(content.text.includes('localhost/insights')).toBe(true); expect(content.html).toContain('10% more than previous month');
expect(content.text.includes('localhost/profile')).toBe(true); expect(content.text).toContain('localhost/insights');
expect(content.text.includes('localhost/profile')).toBe(true); expect(content.text).toContain('localhost/profile');
expect(content.text).toContain('localhost/profile');
expect(content.text).toContain('Your instance technical debt: 1%');
});
test('Sets correct color for technical debt', async () => {
const emailService = new EmailService({
server: {
unleashUrl: 'http://localhost',
},
email: {
host: 'test',
port: 587,
secure: false,
smtpuser: '',
smtppass: '',
sender: 'noreply@getunleash.ai',
},
getLogger: noLoggerProvider,
} as unknown as IUnleashConfig);
const content = await emailService.sendProductivityReportEmail(
'user@user.com',
'customerId',
{
flagsCreated: 1,
productionUpdates: 2,
health: 20,
previousMonth: {
health: 50,
flagsCreated: 1,
productionUpdates: 3,
},
},
);
expect(content.html).not.toContain('#68a611');
expect(content.html).toContain('#d93644');
expect(content.html).toContain(
'Remember to archive stale flags to reduce technical debt and keep your project healthy',
);
}); });
test('Should add optional headers to productivity email', async () => { test('Should add optional headers to productivity email', async () => {

View File

@ -27,8 +27,8 @@
<div class="shaded" <div class="shaded"
style="margin: 0;padding: 24px 8px 24px 8px; background: #f0f0f5;border-width: 3px;border-color: #ffffff;border-style: solid;"> style="margin: 0;padding: 24px 8px 24px 8px; background: #f0f0f5;border-width: 3px;border-color: #ffffff;border-style: solid;">
<div style="padding-top: 12px;"> <div style="padding-top: 12px;">
<span style="color: {{healthColor}};">{{health}}%</span><br> <span style="color: {{healthColor}};">{{technicalDebt}}%</span><br>
<span style="font-size: 16px; color: #1A4049; font-weight: 700">your instance health</span><br> <span style="font-size: 16px; color: #1A4049; font-weight: 700">your instance technical debt</span><br>
<span style="font-size: 12px; color: #6E6E70; font-weight: 400; line-height: 14px">{{{healthTrendMessage}}}</span> <span style="font-size: 12px; color: #6E6E70; font-weight: 400; line-height: 14px">{{{healthTrendMessage}}}</span>
<div style="font-size: 12px; margin-top: 16px; font-weight: 400; line-height: 14px;">{{actionText}}</div> <div style="font-size: 12px; margin-top: 16px; font-weight: 400; line-height: 14px;">{{actionText}}</div>
</div> </div>

View File

@ -5,7 +5,7 @@ Hi {{userName}},
We are excited to share the latest insights for your instance. As always if you We are excited to share the latest insights for your instance. As always if you
have any questions or concerns let us know - we are here for you. have any questions or concerns let us know - we are here for you.
Your instance health: {{health}} Your instance technical debt: {{technicalDebt}}%
Flags created last month: {{flagsCreated}} Flags created last month: {{flagsCreated}}