mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	fix: health-technical debt trends in emails (#10308)
This commit is contained in:
		
							parent
							
								
									cb4beb71ac
								
							
						
					
					
						commit
						212c538a75
					
				| @ -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( | ||||
|                 "<span style='color: #68a611'>▲</span> 10% more than previous month", | ||||
|             expect(viewModel.technicalDebtTrendMessage()).toMatchInlineSnapshot( | ||||
|                 `"<span style='color: #68a611'>▼</span> 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( | ||||
|                 "<span style='color: #d93644'>▼</span> 10% less than previous month", | ||||
|             expect(viewModel.technicalDebtTrendMessage()).toMatchInlineSnapshot( | ||||
|                 `"<span style='color: #d93644'>▲</span> 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( | ||||
|                 "<span style='color: #68a611'>▲</span> 2 more than previous month", | ||||
|             expect(viewModel.flagsCreatedTrendMessage()).toMatchInlineSnapshot( | ||||
|                 `"<span style='color: #68a611'>▲</span> 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( | ||||
|                 "<span style='color: #d93644'>▼</span> 3 less than previous month", | ||||
|             expect( | ||||
|                 viewModel.productionUpdatedTrendMessage(), | ||||
|             ).toMatchInlineSnapshot( | ||||
|                 `"<span style='color: #d93644'>▼</span> 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"`, | ||||
|             ); | ||||
|         }); | ||||
| 
 | ||||
|  | ||||
| @ -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 `<span style='color: ${GREEN}'>▲</span> ${currentValue - previousValue}${unit} more than previous month`; | ||||
|         } | ||||
|         if (previousValue > currentValue) { | ||||
|             return `<span style='color: ${RED}'>▼</span> ${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 `<span style='color: ${RED}'>${ARROW_UP}</span> ${technicalDebt - previousTechnicalDebt}% more than previous month`; | ||||
|             } | ||||
|             if (previousTechnicalDebt > technicalDebt) { | ||||
|                 return `<span style='color: ${GREEN}'>${ARROW_DOWN}</span> ${previousTechnicalDebt - technicalDebt}% less than previous month`; | ||||
|             } | ||||
|             return 'Same as last month'; | ||||
|         }, | ||||
|         flagsCreatedTrendMessage() { | ||||
|             if (!previousMonth) { | ||||
|                 return null; | ||||
|             } | ||||
| 
 | ||||
|             if (flagsCreated > previousMonth.flagsCreated) { | ||||
|                 return `<span style='color: ${GREEN}'>${ARROW_UP}</span> ${flagsCreated - previousMonth.flagsCreated} more than previous month`; | ||||
|             } | ||||
|             if (previousMonth.flagsCreated > flagsCreated) { | ||||
|                 return `<span style='color: ${RED}'>${ARROW_DOWN}</span> ${previousMonth.flagsCreated - flagsCreated} less than previous month`; | ||||
|             } | ||||
|             return 'Same as last month'; | ||||
|         }, | ||||
|         productionUpdatedTrendMessage() { | ||||
|             if (!previousMonth) { | ||||
|                 return null; | ||||
|             } | ||||
| 
 | ||||
|             if (productionUpdates > previousMonth.productionUpdates) { | ||||
|                 return `<span style='color: ${GREEN}'>${ARROW_UP}</span> ${productionUpdates - previousMonth.productionUpdates} more than previous month`; | ||||
|             } | ||||
|             if (previousMonth.productionUpdates > productionUpdates) { | ||||
|                 return `<span style='color: ${RED}'>${ARROW_DOWN}</span> ${previousMonth.productionUpdates - productionUpdates} less than previous month`; | ||||
|             } | ||||
|             return 'Same as last month'; | ||||
|         }, | ||||
|     }; | ||||
| }; | ||||
|  | ||||
| @ -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'); | ||||
|  | ||||
| @ -27,9 +27,9 @@ | ||||
|                 <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}};">{{technicalDebt}}%</span><br> | ||||
|                         <span style="color: {{technicalDebtColor}};">{{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> | ||||
|                         <span style="font-size: 12px; color: #6E6E70; font-weight: 400; line-height: 14px">{{{technicalDebtTrendMessage}}}</span> | ||||
|                         <div style="font-size: 12px; margin-top: 16px; font-weight: 400; line-height: 14px;">{{actionText}}</div> | ||||
|                     </div> | ||||
|                 </div> | ||||
| @ -75,8 +75,8 @@ | ||||
|                     style="color: #1a4049;margin: 0 10px;text-decoration: none;">getunleash.io</a> | | ||||
|                 <a href="https://github.com/unleash" | ||||
|                     style="color: #1a4049;margin: 0 10px;text-decoration: none;">GitHub</a> | | ||||
|                 <a href="https://twitter.com/unleash" | ||||
|                     style="color: #1a4049;margin: 0 10px;text-decoration: none;">Twitter</a> | ||||
|                 <a href="https://www.linkedin.com/company/getunleash/" | ||||
|                     style="color: #1a4049;margin: 0 10px;text-decoration: none;">LinkedIn</a> | ||||
|             </div> | ||||
|             <div class="unsubscribe" style="font-size: 12px;color: #888;margin-top: 10px;"> | ||||
|                 This email was sent to {{userEmail}}. You’ve received this as you are a user of Unleash.<br> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user