-
-
Notifications
You must be signed in to change notification settings - Fork 730
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: productivity report trends visualization (#8956)
- Loading branch information
Showing
5 changed files
with
289 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -126,16 +126,23 @@ test('Can send productivity report email', async () => { | |
flagsCreated: 1, | ||
productionUpdates: 2, | ||
health: 99, | ||
previousMonth: { | ||
health: 89, | ||
flagsCreated: 1, | ||
productionUpdates: 3, | ||
}, | ||
}, | ||
); | ||
expect(content.from).toBe('[email protected]'); | ||
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(`#b0d182`)).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); | ||
}); | ||
|
||
test('Should add optional headers to productivity email', async () => { | ||
|
@@ -170,6 +177,7 @@ test('Should add optional headers to productivity email', async () => { | |
flagsCreated: 1, | ||
productionUpdates: 2, | ||
health: 99, | ||
previousMonth: null, | ||
}, | ||
); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
181 changes: 181 additions & 0 deletions
181
src/mailtemplates/productivity-report/productivity-report-view-model.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
import { | ||
productivityReportViewModel, | ||
type ProductivityReportMetrics, | ||
} from './productivity-report-view-model'; | ||
|
||
const mockData = { | ||
unleashUrl: 'http://example.com', | ||
userEmail: '[email protected]', | ||
userName: 'Test User', | ||
}; | ||
const mockMetrics = { | ||
health: 0, | ||
flagsCreated: 0, | ||
productionUpdates: 0, | ||
previousMonth: { | ||
health: 0, | ||
flagsCreated: 0, | ||
productionUpdates: 0, | ||
}, | ||
}; | ||
|
||
describe('productivityReportViewModel', () => { | ||
describe('healthColor', () => { | ||
it('returns RED for health between 0 and 24', () => { | ||
const metrics: ProductivityReportMetrics = { | ||
...mockMetrics, | ||
health: 20, | ||
}; | ||
|
||
const viewModel = productivityReportViewModel({ | ||
...mockData, | ||
metrics, | ||
}); | ||
|
||
expect(viewModel.healthColor()).toBe('#d93644'); | ||
}); | ||
|
||
it('returns ORANGE for health between 25 and 74', () => { | ||
const metrics: ProductivityReportMetrics = { | ||
...mockMetrics, | ||
health: 50, | ||
}; | ||
|
||
const viewModel = productivityReportViewModel({ | ||
...mockData, | ||
metrics, | ||
}); | ||
|
||
expect(viewModel.healthColor()).toBe('#d76500'); | ||
}); | ||
|
||
it('returns GREEN for health 75 or above', () => { | ||
const metrics: ProductivityReportMetrics = { | ||
...mockMetrics, | ||
health: 80, | ||
}; | ||
|
||
const viewModel = productivityReportViewModel({ | ||
...mockData, | ||
metrics, | ||
}); | ||
|
||
expect(viewModel.healthColor()).toBe('#68a611'); | ||
}); | ||
}); | ||
|
||
describe('healthTrendMessage', () => { | ||
it('returns correct trend message when health increased', () => { | ||
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", | ||
); | ||
}); | ||
|
||
it('returns correct trend message when health decreased', () => { | ||
const metrics: ProductivityReportMetrics = { | ||
...mockMetrics, | ||
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", | ||
); | ||
}); | ||
|
||
it('returns correct message when health is the same', () => { | ||
const metrics: ProductivityReportMetrics = { | ||
...mockMetrics, | ||
health: 70, | ||
previousMonth: { ...mockMetrics.previousMonth, health: 70 }, | ||
}; | ||
|
||
const viewModel = productivityReportViewModel({ | ||
...mockData, | ||
metrics, | ||
}); | ||
|
||
expect(viewModel.healthTrendMessage()).toBe('Same as last month'); | ||
}); | ||
}); | ||
|
||
describe('flagsCreatedTrendMessage', () => { | ||
it('returns correct trend message for flagsCreated increase', () => { | ||
const metrics: ProductivityReportMetrics = { | ||
...mockMetrics, | ||
flagsCreated: 10, | ||
previousMonth: { | ||
...mockMetrics.previousMonth, | ||
flagsCreated: 8, | ||
}, | ||
}; | ||
|
||
const viewModel = productivityReportViewModel({ | ||
...mockData, | ||
metrics, | ||
}); | ||
|
||
expect(viewModel.flagsCreatedTrendMessage()).toBe( | ||
"<span style='color: #68a611'>▲</span> 2 more than previous month", | ||
); | ||
}); | ||
}); | ||
|
||
describe('productionUpdatedTrendMessage', () => { | ||
it('returns correct trend message for productionUpdates decrease', () => { | ||
const metrics: ProductivityReportMetrics = { | ||
...mockMetrics, | ||
productionUpdates: 5, | ||
previousMonth: { | ||
...mockMetrics.previousMonth, | ||
productionUpdates: 8, | ||
}, | ||
}; | ||
|
||
const viewModel = productivityReportViewModel({ | ||
...mockData, | ||
metrics, | ||
}); | ||
|
||
expect(viewModel.productionUpdatedTrendMessage()).toBe( | ||
"<span style='color: #d93644'>▼</span> 3 less than previous month", | ||
); | ||
}); | ||
}); | ||
|
||
describe('Missing previous month data', () => { | ||
it('returns no trends messages', () => { | ||
const metrics: ProductivityReportMetrics = { | ||
health: 100, | ||
flagsCreated: 10, | ||
productionUpdates: 5, | ||
previousMonth: null, | ||
}; | ||
|
||
const viewModel = productivityReportViewModel({ | ||
...mockData, | ||
metrics, | ||
}); | ||
|
||
expect(viewModel.healthTrendMessage()).toBe(null); | ||
expect(viewModel.flagsCreatedTrendMessage()).toBe(null); | ||
expect(viewModel.productionUpdatedTrendMessage()).toBe(null); | ||
}); | ||
}); | ||
}); |
78 changes: 78 additions & 0 deletions
78
src/mailtemplates/productivity-report/productivity-report-view-model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
export type ProductivityReportMetrics = { | ||
health: number; | ||
flagsCreated: number; | ||
productionUpdates: number; | ||
previousMonth: { | ||
health: number; | ||
flagsCreated: number; | ||
productionUpdates: number; | ||
} | null; | ||
}; | ||
|
||
const RED = '#d93644'; | ||
const GREEN = '#68a611'; | ||
const ORANGE = '#d76500'; | ||
|
||
export const productivityReportViewModel = ({ | ||
unleashUrl, | ||
userEmail, | ||
userName, | ||
metrics, | ||
}: { | ||
unleashUrl: string; | ||
userEmail: string; | ||
userName: string; | ||
metrics: ProductivityReportMetrics; | ||
}) => ({ | ||
userName, | ||
userEmail, | ||
...metrics, | ||
unleashUrl, | ||
healthColor() { | ||
const healthRating = this.health; | ||
const healthColor = | ||
healthRating >= 0 && healthRating <= 24 | ||
? RED | ||
: healthRating >= 25 && healthRating <= 74 | ||
? ORANGE | ||
: GREEN; | ||
return healthColor; | ||
}, | ||
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) { | ||
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`; | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters