diff --git a/src/lib/features/productivity-report/productivity-report-view-model.test.ts b/src/lib/features/productivity-report/productivity-report-view-model.test.ts index 8d6c8ab60f..f7f9297a3a 100644 --- a/src/lib/features/productivity-report/productivity-report-view-model.test.ts +++ b/src/lib/features/productivity-report/productivity-report-view-model.test.ts @@ -33,7 +33,7 @@ describe('productivityReportViewModel', () => { }); describe('healthColor', () => { - it('returns RED for health between 0 and 24', () => { + it('returns RED for technical debt between 75 and 100', () => { const metrics: ProductivityReportMetrics = { ...mockMetrics, health: 20, @@ -44,10 +44,10 @@ describe('productivityReportViewModel', () => { metrics, }); - expect(viewModel.healthColor()).toBe('#d93644'); + expect(viewModel.technicalDebtColor()).toBe('#d93644'); }); - it('returns ORANGE for health between 25 and 74', () => { + it('returns ORANGE for technical debt between 25 and 74', () => { const metrics: ProductivityReportMetrics = { ...mockMetrics, health: 50, @@ -58,10 +58,10 @@ describe('productivityReportViewModel', () => { metrics, }); - expect(viewModel.healthColor()).toBe('#d76500'); + expect(viewModel.technicalDebtColor()).toBe('#d76500'); }); - it('returns GREEN for health 75 or above', () => { + it('returns GREEN for technical debt below 25', () => { const metrics: ProductivityReportMetrics = { ...mockMetrics, health: 80, @@ -72,25 +72,24 @@ describe('productivityReportViewModel', () => { metrics, }); - expect(viewModel.healthColor()).toBe('#68a611'); + expect(viewModel.technicalDebtColor()).toBe('#68a611'); }); }); - describe('healthTrendMessage', () => { + describe('technicalDebtTrendMessage', () => { it('returns correct trend message when technica debt decreased', () => { const metrics: ProductivityReportMetrics = { ...mockMetrics, health: 80, previousMonth: { ...mockMetrics.previousMonth, health: 70 }, }; - const viewModel = productivityReportViewModel({ ...mockData, metrics, }); - expect(viewModel.healthTrendMessage()).toBe( - "▲ 10% more than previous month", + expect(viewModel.technicalDebtTrendMessage()).toMatchInlineSnapshot( + `"▼ 10% less than previous month"`, ); }); @@ -100,14 +99,13 @@ describe('productivityReportViewModel', () => { health: 60, previousMonth: { ...mockMetrics.previousMonth, health: 70 }, }; - const viewModel = productivityReportViewModel({ ...mockData, metrics, }); - expect(viewModel.healthTrendMessage()).toBe( - "▼ 10% less than previous month", + expect(viewModel.technicalDebtTrendMessage()).toMatchInlineSnapshot( + `"▲ 10% more than previous month"`, ); }); @@ -117,13 +115,14 @@ describe('productivityReportViewModel', () => { health: 70, previousMonth: { ...mockMetrics.previousMonth, health: 70 }, }; - const viewModel = productivityReportViewModel({ ...mockData, metrics, }); - expect(viewModel.healthTrendMessage()).toBe('Same as last month'); + expect(viewModel.technicalDebtTrendMessage()).toMatchInlineSnapshot( + `"Same as last month"`, + ); }); }); @@ -137,18 +136,15 @@ describe('productivityReportViewModel', () => { flagsCreated: 8, }, }; - const viewModel = productivityReportViewModel({ ...mockData, metrics, }); - - expect(viewModel.flagsCreatedTrendMessage()).toBe( - "▲ 2 more than previous month", + expect(viewModel.flagsCreatedTrendMessage()).toMatchInlineSnapshot( + `"▲ 2 more than previous month"`, ); }); }); - describe('productionUpdatedTrendMessage', () => { it('returns correct trend message for productionUpdates decrease', () => { const metrics: ProductivityReportMetrics = { @@ -159,18 +155,17 @@ describe('productivityReportViewModel', () => { productionUpdates: 8, }, }; - const viewModel = productivityReportViewModel({ ...mockData, metrics, }); - - expect(viewModel.productionUpdatedTrendMessage()).toBe( - "▼ 3 less than previous month", + expect( + viewModel.productionUpdatedTrendMessage(), + ).toMatchInlineSnapshot( + `"▼ 3 less than previous month"`, ); }); }); - describe('Missing previous month data', () => { it('returns no trends messages', () => { const metrics: ProductivityReportMetrics = { @@ -179,13 +174,12 @@ describe('productivityReportViewModel', () => { productionUpdates: 5, previousMonth: null, }; - const viewModel = productivityReportViewModel({ ...mockData, metrics, }); - expect(viewModel.healthTrendMessage()).toBe(null); + expect(viewModel.technicalDebtTrendMessage()).toBe(null); expect(viewModel.flagsCreatedTrendMessage()).toBe(null); expect(viewModel.productionUpdatedTrendMessage()).toBe(null); }); @@ -225,8 +219,8 @@ describe('productivityReportViewModel', () => { metrics, }); - expect(viewModel.actionText()).toBe( - 'Remember to archive stale flags to reduce technical debt and keep your project healthy', + expect(viewModel.actionText()).toMatchInlineSnapshot( + `"Remember to archive stale flags to reduce technical debt and keep your project healthy"`, ); }); @@ -245,8 +239,8 @@ describe('productivityReportViewModel', () => { metrics, }); - expect(viewModel.actionText()).toBe( - 'Remember to archive stale flags to reduce technical debt and keep your project healthy', + expect(viewModel.actionText()).toMatchInlineSnapshot( + `"Remember to archive stale flags to reduce technical debt and keep your project healthy"`, ); }); diff --git a/src/lib/features/productivity-report/productivity-report-view-model.ts b/src/lib/features/productivity-report/productivity-report-view-model.ts index 5b87f3985a..52ce1c7203 100644 --- a/src/lib/features/productivity-report/productivity-report-view-model.ts +++ b/src/lib/features/productivity-report/productivity-report-view-model.ts @@ -13,6 +13,9 @@ const RED = '#d93644'; const GREEN = '#68a611'; const ORANGE = '#d76500'; +const ARROW_UP = '▲'; +const ARROW_DOWN = '▼'; + export const productivityReportViewModel = ({ unleashUrl, userEmail, @@ -23,69 +26,82 @@ export const productivityReportViewModel = ({ userEmail: string; userName: string; metrics: ProductivityReportMetrics; -}) => ({ - userName, - userEmail, - ...metrics, - technicalDebt: Math.max(0, 100 - metrics.health).toString(), - unleashUrl, - healthColor() { - const healthRating = this.health; - const healthColor = - healthRating >= 0 && healthRating <= 24 - ? RED - : healthRating >= 25 && healthRating <= 74 - ? ORANGE - : GREEN; - return healthColor; - }, - actionText(): string | null { - const improveMessage = - 'Remember to archive stale flags to reduce technical debt and keep your project healthy'; - const previousHealth = this.previousMonth?.health || 0; - if (this.health <= 74) { - return improveMessage; - } - if (this.health < previousHealth) { - return improveMessage; - } - return null; - }, - healthTrendMessage() { - return this.previousMonthText( - '%', - this.health, - this.previousMonth?.health, - ); - }, - flagsCreatedTrendMessage() { - return this.previousMonthText( - '', - this.flagsCreated, - this.previousMonth?.flagsCreated, - ); - }, - productionUpdatedTrendMessage() { - return this.previousMonthText( - '', - this.productionUpdates, - this.previousMonth?.productionUpdates, - ); - }, - previousMonthText( - unit: '' | '%', - currentValue: number, - previousValue?: number, - ) { - if (previousValue == null) { +}) => { + const { health, previousMonth, flagsCreated, productionUpdates } = metrics; + const technicalDebt = Math.max(0, 100 - health) || 0; + + return { + userName, + userEmail, + flagsCreated, + productionUpdates, + previousMonth, + health, + technicalDebt: technicalDebt.toString(), + unleashUrl, + technicalDebtColor() { + if (technicalDebt < 25) { + return GREEN; + } + if (technicalDebt < 75) { + return ORANGE; + } + return RED; + }, + actionText(): string | null { + const improveMessage = + 'Remember to archive stale flags to reduce technical debt and keep your project healthy'; + const previousHealth = previousMonth?.health || 0; + if (health <= 74) { + return improveMessage; + } + if (health < previousHealth) { + return improveMessage; + } return null; - } - if (currentValue > previousValue) { - return `▲ ${currentValue - previousValue}${unit} more than previous month`; - } - if (previousValue > currentValue) { - return `▼ ${previousValue - currentValue}${unit} less than previous month`; - } - return `Same as last month`; - }, -}); + }, + technicalDebtTrendMessage() { + if (!previousMonth || Number.isNaN(previousMonth.health)) { + return null; + } + const previousTechnicalDebt = Math.max( + 0, + 100 - previousMonth.health, + ); + + if (technicalDebt > previousTechnicalDebt) { + return `${ARROW_UP} ${technicalDebt - previousTechnicalDebt}% more than previous month`; + } + if (previousTechnicalDebt > technicalDebt) { + return `${ARROW_DOWN} ${previousTechnicalDebt - technicalDebt}% less than previous month`; + } + return 'Same as last month'; + }, + flagsCreatedTrendMessage() { + if (!previousMonth) { + return null; + } + + if (flagsCreated > previousMonth.flagsCreated) { + return `${ARROW_UP} ${flagsCreated - previousMonth.flagsCreated} more than previous month`; + } + if (previousMonth.flagsCreated > flagsCreated) { + return `${ARROW_DOWN} ${previousMonth.flagsCreated - flagsCreated} less than previous month`; + } + return 'Same as last month'; + }, + productionUpdatedTrendMessage() { + if (!previousMonth) { + return null; + } + + if (productionUpdates > previousMonth.productionUpdates) { + return `${ARROW_UP} ${productionUpdates - previousMonth.productionUpdates} more than previous month`; + } + if (previousMonth.productionUpdates > productionUpdates) { + return `${ARROW_DOWN} ${previousMonth.productionUpdates - productionUpdates} less than previous month`; + } + return 'Same as last month'; + }, + }; +}; diff --git a/src/lib/services/email-service.test.ts b/src/lib/services/email-service.test.ts index e09aef1cab..d3c02e04db 100644 --- a/src/lib/services/email-service.test.ts +++ b/src/lib/services/email-service.test.ts @@ -146,7 +146,7 @@ test('Can send productivity report email', async () => { 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.html).toContain('10% less than previous month'); expect(content.text).toContain('localhost/insights'); expect(content.text).toContain('localhost/profile'); expect(content.text).toContain('localhost/profile'); diff --git a/src/mailtemplates/productivity-report/productivity-report.html.mustache b/src/mailtemplates/productivity-report/productivity-report.html.mustache index 806961f060..78b0a43938 100644 --- a/src/mailtemplates/productivity-report/productivity-report.html.mustache +++ b/src/mailtemplates/productivity-report/productivity-report.html.mustache @@ -27,9 +27,9 @@