Skip to content

Commit 00aae50

Browse files
committed
feat(utils): add score filter to md report generation (#956)
1 parent 88fd037 commit 00aae50

8 files changed

+267
-20
lines changed

packages/utils/src/lib/reports/__snapshots__/generate-md-report-category-section.unit.test.ts.snap

+18
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ exports[`categoriesDetailsSection > should render complete categories details 1`
4545
"
4646
`;
4747

48+
exports[`categoriesDetailsSection > should render filtered categories details > filtered 1`] = `
49+
"## 🏷 Categories
50+
51+
### Bug Prevention
52+
53+
🟢 Score: **100** ✅
54+
55+
- 🟩 [No let](#no-let-eslint) (_Eslint_) - **0**
56+
"
57+
`;
58+
4859
exports[`categoriesOverviewSection > should render complete categories table 1`] = `
4960
"| 🏷 Category | ⭐ Score | 🛡 Audits |
5061
| :-------------------------------- | :-------: | :-------: |
@@ -54,6 +65,13 @@ exports[`categoriesOverviewSection > should render complete categories table 1`]
5465
"
5566
`;
5667

68+
exports[`categoriesOverviewSection > should render filtered categories table 1`] = `
69+
"| 🏷 Category | ⭐ Score | 🛡 Audits |
70+
| :-------------------------------- | :--------: | :-------: |
71+
| [Bug Prevention](#bug-prevention) | 🟢 **100** | 1 |
72+
"
73+
`;
74+
5775
exports[`categoriesOverviewSection > should render targetScore icon "❌" if score fails 1`] = `
5876
"| 🏷 Category | ⭐ Score | 🛡 Audits |
5977
| :-------------------------------- | :---------: | :-------: |

packages/utils/src/lib/reports/generate-md-report-categoy-section.ts renamed to packages/utils/src/lib/reports/generate-md-report-category-section.ts

+23-14
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ import { slugify } from '../formatting.js';
44
import { HIERARCHY } from '../text-formats/index.js';
55
import { metaDescription } from './formatting.js';
66
import { getSortableAuditByRef, getSortableGroupByRef } from './sorting.js';
7-
import type { ScoredGroup, ScoredReport } from './types.js';
7+
import type { ScoreFilter, ScoredGroup, ScoredReport } from './types.js';
88
import {
99
countCategoryAudits,
1010
formatReportScore,
1111
getPluginNameFromSlug,
12+
scoreFilter,
1213
scoreMarker,
1314
targetScoreIcon,
1415
} from './utils.js';
1516

1617
export function categoriesOverviewSection(
1718
report: Required<Pick<ScoredReport, 'plugins' | 'categories'>>,
19+
options?: ScoreFilter,
1820
): MarkdownDocument {
1921
const { categories, plugins } = report;
2022
return new MarkdownDocument().table(
@@ -23,26 +25,29 @@ export function categoriesOverviewSection(
2325
{ heading: '⭐ Score', alignment: 'center' },
2426
{ heading: '🛡 Audits', alignment: 'center' },
2527
],
26-
categories.map(({ title, refs, score, isBinary }) => [
27-
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
28-
// The heading "ID" is inferred from the heading text in Markdown.
29-
md.link(`#${slugify(title)}`, title),
30-
md`${scoreMarker(score)} ${md.bold(
31-
formatReportScore(score),
32-
)}${binaryIconSuffix(score, isBinary)}`,
33-
countCategoryAudits(refs, plugins).toString(),
34-
]),
28+
categories
29+
.filter(scoreFilter(options))
30+
.map(({ title, refs, score, isBinary }) => [
31+
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
32+
// The heading "ID" is inferred from the heading text in Markdown.
33+
md.link(`#${slugify(title)}`, title),
34+
md`${scoreMarker(score)} ${md.bold(
35+
formatReportScore(score),
36+
)}${binaryIconSuffix(score, isBinary)}`,
37+
countCategoryAudits(refs, plugins).toString(),
38+
]),
3539
);
3640
}
3741

3842
export function categoriesDetailsSection(
3943
report: Required<Pick<ScoredReport, 'plugins' | 'categories'>>,
44+
options?: ScoreFilter,
4045
): MarkdownDocument {
4146
const { categories, plugins } = report;
42-
47+
const isScoreDisplayed = scoreFilter(options);
4348
return new MarkdownDocument()
4449
.heading(HIERARCHY.level_2, '🏷 Categories')
45-
.$foreach(categories, (doc, category) =>
50+
.$foreach(categories.filter(isScoreDisplayed), (doc, category) =>
4651
doc
4752
.heading(HIERARCHY.level_3, category.title)
4853
.paragraph(metaDescription(category))
@@ -63,13 +68,17 @@ export function categoriesDetailsSection(
6368
),
6469
);
6570
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
66-
return categoryGroupItem(group, groupAudits, pluginTitle);
71+
return isScoreDisplayed(group)
72+
? categoryGroupItem(group, groupAudits, pluginTitle)
73+
: '';
6774
}
6875
// Add audit details
6976
else {
7077
const audit = getSortableAuditByRef(ref, plugins);
7178
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
72-
return categoryRef(audit, pluginTitle);
79+
return isScoreDisplayed(audit)
80+
? categoryRef(audit, pluginTitle)
81+
: '';
7382
}
7483
}),
7584
),

packages/utils/src/lib/reports/generate-md-report-category-section.unit.test.ts

+105-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
categoriesOverviewSection,
77
categoryGroupItem,
88
categoryRef,
9-
} from './generate-md-report-categoy-section.js';
9+
} from './generate-md-report-category-section.js';
1010
import type { ScoredGroup, ScoredReport } from './types.js';
1111

1212
// === Categories Overview Section
@@ -49,6 +49,48 @@ describe('categoriesOverviewSection', () => {
4949
).toMatchSnapshot();
5050
});
5151

52+
it('should render filtered categories table', () => {
53+
expect(
54+
categoriesOverviewSection(
55+
{
56+
plugins: [
57+
{
58+
slug: 'eslint',
59+
title: 'Eslint',
60+
},
61+
{
62+
slug: 'lighthouse',
63+
title: 'Lighthouse',
64+
},
65+
],
66+
categories: [
67+
{
68+
slug: 'bug-prevention',
69+
title: 'Bug Prevention',
70+
score: 1,
71+
refs: [{ slug: 'no-let', type: 'audit' }],
72+
},
73+
{
74+
slug: 'performance',
75+
title: 'Performance',
76+
score: 0.74,
77+
refs: [{ slug: 'largest-contentful-paint', type: 'audit' }],
78+
},
79+
{
80+
slug: 'typescript',
81+
title: 'Typescript',
82+
score: 0.14,
83+
refs: [{ slug: 'no-any', type: 'audit' }],
84+
},
85+
],
86+
} as Required<Pick<ScoredReport, 'plugins' | 'categories'>>,
87+
{
88+
isScoreListed: score => score === 1,
89+
},
90+
).toString(),
91+
).toMatchSnapshot();
92+
});
93+
5294
it('should render targetScore icon "❌" if score fails', () => {
5395
expect(
5496
categoriesOverviewSection({
@@ -215,6 +257,68 @@ describe('categoriesDetailsSection', () => {
215257
).toMatchSnapshot();
216258
});
217259

260+
it('should render filtered categories details', () => {
261+
expect(
262+
categoriesDetailsSection(
263+
{
264+
plugins: [
265+
{
266+
slug: 'eslint',
267+
title: 'Eslint',
268+
audits: [
269+
{ slug: 'no-let', title: 'No let', score: 1, value: 0 },
270+
{ slug: 'no-any', title: 'No any', score: 0, value: 5 },
271+
],
272+
},
273+
{
274+
slug: 'lighthouse',
275+
title: 'Lighthouse',
276+
audits: [
277+
{
278+
slug: 'largest-contentful-paint',
279+
title: 'Largest Contentful Paint',
280+
score: 0.7,
281+
value: 2905,
282+
},
283+
],
284+
},
285+
],
286+
categories: [
287+
{
288+
slug: 'bug-prevention',
289+
title: 'Bug Prevention',
290+
score: 1,
291+
isBinary: true,
292+
refs: [{ slug: 'no-let', type: 'audit', plugin: 'eslint' }],
293+
},
294+
{
295+
slug: 'performance',
296+
title: 'Performance',
297+
score: 0.74,
298+
refs: [
299+
{
300+
slug: 'largest-contentful-paint',
301+
type: 'audit',
302+
plugin: 'lighthouse',
303+
},
304+
],
305+
},
306+
{
307+
slug: 'typescript',
308+
title: 'Typescript',
309+
score: 0.14,
310+
isBinary: true,
311+
refs: [{ slug: 'no-any', type: 'audit', plugin: 'eslint' }],
312+
},
313+
],
314+
} as Required<Pick<ScoredReport, 'plugins' | 'categories'>>,
315+
{
316+
isScoreListed: score => score === 1,
317+
},
318+
).toString(),
319+
).toMatchSnapshot('filtered');
320+
});
321+
218322
it('should render categories details and add "❌" when isBinary is failing', () => {
219323
expect(
220324
categoriesDetailsSection({

packages/utils/src/lib/reports/generate-md-report.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ import {
1616
import {
1717
categoriesDetailsSection,
1818
categoriesOverviewSection,
19-
} from './generate-md-report-categoy-section.js';
19+
} from './generate-md-report-category-section.js';
2020
import type { MdReportOptions, ScoredReport } from './types.js';
21-
import { formatReportScore, scoreMarker, severityMarker } from './utils.js';
21+
import {
22+
formatReportScore,
23+
scoreFilter,
24+
scoreMarker,
25+
severityMarker,
26+
} from './utils.js';
2227

2328
export function auditDetailsAuditValue({
2429
score,
@@ -30,6 +35,10 @@ export function auditDetailsAuditValue({
3035
)} (score: ${formatReportScore(score)})`;
3136
}
3237

38+
/**
39+
* Check if the report has categories.
40+
* @param report
41+
*/
3342
function hasCategories(
3443
report: ScoredReport,
3544
): report is ScoredReport & Required<Pick<ScoredReport, 'categories'>> {
@@ -44,7 +53,10 @@ export function generateMdReport(
4453
.heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT)
4554
.$concat(
4655
...(hasCategories(report)
47-
? [categoriesOverviewSection(report), categoriesDetailsSection(report)]
56+
? [
57+
categoriesOverviewSection(report, options),
58+
categoriesDetailsSection(report, options),
59+
]
4860
: []),
4961
auditsSection(report, options),
5062
aboutSection(report),
@@ -110,11 +122,14 @@ export function auditsSection(
110122
{ plugins }: Pick<ScoredReport, 'plugins'>,
111123
options?: MdReportOptions,
112124
): MarkdownDocument {
125+
const isScoreDisplayed = scoreFilter(options);
113126
return new MarkdownDocument()
114127
.heading(HIERARCHY.level_2, '🛡️ Audits')
115128
.$foreach(
116129
plugins.flatMap(plugin =>
117-
plugin.audits.map(audit => ({ ...audit, plugin })),
130+
plugin.audits
131+
.filter(isScoreDisplayed)
132+
.map(audit => ({ ...audit, plugin })),
118133
),
119134
(doc, { plugin, ...audit }) => {
120135
const auditTitle = `${audit.title} (${plugin.title})`;

packages/utils/src/lib/reports/generate-md-report.unit.test.ts

+76
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,54 @@ const baseScoredReport = {
4747
],
4848
} as ScoredReport;
4949

50+
const baseScoredReport2 = {
51+
date: '2025.01.01',
52+
duration: 4200,
53+
version: 'v1.0.0',
54+
commit: {
55+
message: 'ci: update action',
56+
author: 'Michael <michael.hladky@push-based.io>',
57+
date: new Date('2025.01.01'),
58+
hash: '535b8e9e557336618a764f3fa45609d224a62837',
59+
},
60+
plugins: [
61+
{
62+
slug: 'lighthouse',
63+
version: '1.0.1',
64+
duration: 15_365,
65+
title: 'Lighthouse',
66+
audits: [
67+
{
68+
slug: 'largest-contentful-paint',
69+
title: 'Largest Contentful Paint',
70+
score: 0.6,
71+
value: 2700,
72+
},
73+
{
74+
slug: 'cumulative-layout-shift',
75+
title: 'Cumulative Layout Shift',
76+
score: 1,
77+
value: 0,
78+
},
79+
],
80+
},
81+
],
82+
categories: [
83+
{
84+
title: 'Speed',
85+
slug: 'speed',
86+
score: 0.93,
87+
refs: [{ slug: 'largest-contentful-paint', plugin: 'lighthouse' }],
88+
},
89+
{
90+
title: 'Visual Stability',
91+
slug: 'visual-stability',
92+
score: 1,
93+
refs: [{ slug: 'cumulative-layout-shift', plugin: 'lighthouse' }],
94+
},
95+
],
96+
} as ScoredReport;
97+
5098
// === Audit Details
5199

52100
describe('auditDetailsAuditValue', () => {
@@ -359,6 +407,22 @@ describe('auditsSection', () => {
359407
).toMatch('🟩 **0** (score: 100)');
360408
});
361409

410+
it('should render filtered result', () => {
411+
const auditSection = auditsSection(
412+
{
413+
plugins: [
414+
{ audits: [{ score: 1, value: 0 }] },
415+
{ audits: [{ score: 0, value: 1 }] },
416+
],
417+
} as ScoredReport,
418+
{
419+
isScoreListed: (score: number) => score === 1,
420+
},
421+
).toString();
422+
expect(auditSection).toMatch('(score: 100)');
423+
expect(auditSection).not.toMatch('(score: 0)');
424+
});
425+
362426
it('should render audit details', () => {
363427
const md = auditsSection({
364428
plugins: [
@@ -580,6 +644,18 @@ describe('generateMdReport', () => {
580644
expect(md).toMatch('Made with ❤ by [Code PushUp]');
581645
});
582646

647+
it('should render sections filtered by isScoreListed of the report', () => {
648+
const md = generateMdReport(baseScoredReport2, {
649+
isScoreListed: (score: number) => score === 1,
650+
});
651+
652+
expect(md).toMatch('Visual Stability');
653+
expect(md).toMatch('Cumulative Layout Shift');
654+
655+
expect(md).not.toMatch('Speed');
656+
expect(md).not.toMatch('Largest Contentful Paint');
657+
});
658+
583659
it('should skip categories section when categories are missing', () => {
584660
const md = generateMdReport({ ...baseScoredReport, categories: undefined });
585661
expect(md).not.toMatch('## 🏷 Categories');

0 commit comments

Comments
 (0)