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,
title: point.dataset.label,
color: point.dataset.borderColor,
value: point.raw as InstanceInsightsSchemaProjectFlagTrendsItem & {
technicalDebt?: number | null;
}, // TODO: get from backend
value: point.raw as InstanceInsightsSchemaProjectFlagTrendsItem,
};
});

View File

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

View File

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

View File

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

View File

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

View File

@ -46,7 +46,7 @@ const ActiveProjectDetails: FC<{
}> = ({ project }) => {
const healthToTechDebtEnabled = useFlag('healthToTechDebt');
const techicalDebt = 100 - project.health; // TODO: health to technical debt from backend
const techicalDebt = project.technicalDebt;
return (
<Box sx={{ display: 'flex', gap: 2 }}>
<Box sx={{ display: 'flex', flexDirection: 'column' }}>

View File

@ -115,23 +115,25 @@ const Wrapper = styled(HealthGridTile)(({ theme }) => ({
export const ProjectHealth = () => {
const projectId = useRequiredPathParam('projectId');
const {
data: { health, staleFlags },
data: { health, technicalDebt, staleFlags },
} = useProjectStatus(projectId);
const { isOss } = useUiConfig();
const theme = useTheme();
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 filledLength = 1 - gapLength;
const offset = 0.75 - gapLength / 2;
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 technicalDebtColor = useTechnicalDebtColor(technicalDebt);
const technicalDebtColor = useTechnicalDebtColor(
technicalDebt.current || 0,
);
return (
<Wrapper>
@ -174,7 +176,9 @@ export const ProjectHealth = () => {
fill={theme.palette.text.primary}
fontSize={theme.typography.h1.fontSize}
>
{healthToDebtEnabled ? technicalDebt : healthRating}
{healthToDebtEnabled
? technicalDebt.current || 0
: healthRating}
%
</text>
</StyledSVG>

View File

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

View File

@ -27,6 +27,7 @@ export const productivityReportViewModel = ({
userName,
userEmail,
...metrics,
technicalDebt: Math.max(0, 100 - metrics.health).toString(),
unleashUrl,
healthColor() {
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.subject).toBe('Unleash - productivity report');
expect(content.html.includes('Productivity Report')).toBe(true);
expect(content.html.includes('localhost/insights')).toBe(true);
expect(content.html.includes('localhost/profile')).toBe(true);
expect(content.html.includes('#68a611')).toBe(true);
expect(content.html.includes('10% more than previous month')).toBe(true);
expect(content.text.includes('localhost/insights')).toBe(true);
expect(content.text.includes('localhost/profile')).toBe(true);
expect(content.text.includes('localhost/profile')).toBe(true);
expect(content.html).toContain('Productivity Report');
expect(content.html).toContain('localhost/insights');
expect(content.html).toContain('localhost/profile');
expect(content.html).toContain('#68a611');
expect(content.html).toContain('1%');
expect(content.html).toContain('10% more than previous month');
expect(content.text).toContain('localhost/insights');
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 () => {

View File

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