Skip to content

Commit ab462d4

Browse files
committed
feat(utils): generate ascii tree in full markdown report's audit details
1 parent c1c6965 commit ab462d4

12 files changed

+568
-31
lines changed

packages/utils/src/lib/reports/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { HIERARCHY } from '../text-formats/index.js';
2+
13
// https://stackoverflow.com/questions/4651012/why-is-the-default-terminal-width-80-characters/4651037#4651037
24
export const TERMINAL_WIDTH = 80;
35

@@ -20,3 +22,5 @@ export const REPORT_RAW_OVERVIEW_TABLE_HEADERS = [
2022
'Score',
2123
'Audits',
2224
];
25+
26+
export const AUDIT_DETAILS_HEADING_LEVEL = HIERARCHY.level_4;

packages/utils/src/lib/reports/formatting.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import type {
99
AuditReport,
1010
SourceFileLocation,
1111
Table,
12+
Tree,
1213
} from '@code-pushup/models';
13-
import { HIERARCHY } from '../text-formats/index.js';
14+
import { formatAsciiTree } from '../text-formats/ascii/tree.js';
1415
import {
1516
columnsToStringArray,
1617
getColumnAlignments,
1718
rowToStringArray,
1819
} from '../text-formats/table.js';
20+
import { AUDIT_DETAILS_HEADING_LEVEL } from './constants.js';
1921
import {
2022
getEnvironmentType,
2123
getGitHubBaseUrl,
@@ -24,19 +26,19 @@ import {
2426
import type { MdReportOptions } from './types.js';
2527

2628
export function tableSection(
27-
tableData: Table,
29+
table: Table,
2830
options?: {
2931
level?: HeadingLevel;
3032
},
3133
): MarkdownDocument | null {
32-
if (tableData.rows.length === 0) {
34+
if (table.rows.length === 0) {
3335
return null;
3436
}
35-
const { level = HIERARCHY.level_4 } = options ?? {};
36-
const columns = columnsToStringArray(tableData);
37-
const alignments = getColumnAlignments(tableData);
38-
const rows = rowToStringArray(tableData);
39-
return new MarkdownDocument().heading(level, tableData.title).table(
37+
const { level = AUDIT_DETAILS_HEADING_LEVEL } = options ?? {};
38+
const columns = columnsToStringArray(table);
39+
const alignments = getColumnAlignments(table);
40+
const rows = rowToStringArray(table);
41+
return new MarkdownDocument().heading(level, table.title).table(
4042
columns.map((heading, i) => {
4143
const alignment = alignments[i];
4244
if (alignment) {
@@ -48,6 +50,18 @@ export function tableSection(
4850
);
4951
}
5052

53+
export function treeSection(
54+
tree: Tree,
55+
options?: {
56+
level?: HeadingLevel;
57+
},
58+
): MarkdownDocument {
59+
const { level = AUDIT_DETAILS_HEADING_LEVEL } = options ?? {};
60+
return new MarkdownDocument()
61+
.heading(level, tree.title)
62+
.code(formatAsciiTree(tree));
63+
}
64+
5165
// @TODO extract `Pick<AuditReport, 'docsUrl' | 'description'>` to a reusable schema and type
5266
export function metaDescription(
5367
audit: Pick<AuditReport, 'docsUrl' | 'description'>,

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

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { AuditReport, Issue, Report } from '@code-pushup/models';
33
import { formatDate, formatDuration } from '../formatting.js';
44
import { HIERARCHY } from '../text-formats/index.js';
55
import {
6+
AUDIT_DETAILS_HEADING_LEVEL,
67
FOOTER_PREFIX,
78
README_LINK,
89
REPORT_HEADLINE_TEXT,
@@ -12,6 +13,7 @@ import {
1213
linkToLocalSourceForIde,
1314
metaDescription,
1415
tableSection,
16+
treeSection,
1517
} from './formatting.js';
1618
import {
1719
categoriesDetailsSection,
@@ -73,48 +75,57 @@ export function auditDetailsIssues(
7375
if (issues.length === 0) {
7476
return null;
7577
}
76-
return new MarkdownDocument().heading(HIERARCHY.level_4, 'Issues').table(
77-
[
78-
{ heading: 'Severity', alignment: 'center' },
79-
{ heading: 'Message', alignment: 'left' },
80-
{ heading: 'Source file', alignment: 'left' },
81-
{ heading: 'Line(s)', alignment: 'center' },
82-
],
83-
issues.map(({ severity: level, message, source }: Issue) => {
84-
const severity = md`${severityMarker(level)} ${md.italic(level)}`;
78+
return new MarkdownDocument()
79+
.heading(AUDIT_DETAILS_HEADING_LEVEL, 'Issues')
80+
.table(
81+
[
82+
{ heading: 'Severity', alignment: 'center' },
83+
{ heading: 'Message', alignment: 'left' },
84+
{ heading: 'Source file', alignment: 'left' },
85+
{ heading: 'Line(s)', alignment: 'center' },
86+
],
87+
issues.map(({ severity: level, message, source }: Issue) => {
88+
const severity = md`${severityMarker(level)} ${md.italic(level)}`;
8589

86-
if (!source) {
87-
return [severity, message];
88-
}
89-
const file = linkToLocalSourceForIde(source, options);
90-
if (!source.position) {
91-
return [severity, message, file];
92-
}
93-
const line = formatSourceLine(source.position);
94-
return [severity, message, file, line];
95-
}),
96-
);
90+
if (!source) {
91+
return [severity, message];
92+
}
93+
const file = linkToLocalSourceForIde(source, options);
94+
if (!source.position) {
95+
return [severity, message, file];
96+
}
97+
const line = formatSourceLine(source.position);
98+
return [severity, message, file, line];
99+
}),
100+
);
97101
}
98102

99103
export function auditDetails(
100104
audit: AuditReport,
101105
options?: MdReportOptions,
102106
): MarkdownDocument {
103-
const { table, issues = [] } = audit.details ?? {};
107+
const { table, issues = [], trees = [] } = audit.details ?? {};
104108
const detailsValue = auditDetailsAuditValue(audit);
105109

106110
// undefined details OR empty details (undefined issues OR empty issues AND empty table)
107-
if (issues.length === 0 && !table?.rows.length) {
111+
if (issues.length === 0 && !table?.rows.length && trees.length === 0) {
108112
return new MarkdownDocument().paragraph(detailsValue);
109113
}
110114

111115
const tableSectionContent = table && tableSection(table);
112116
const issuesSectionContent =
113117
issues.length > 0 && auditDetailsIssues(issues, options);
118+
const treesSectionContent =
119+
trees.length > 0 &&
120+
new MarkdownDocument().$concat(...trees.map(tree => treeSection(tree)));
114121

115122
return new MarkdownDocument().details(
116123
detailsValue,
117-
new MarkdownDocument().$concat(tableSectionContent, issuesSectionContent),
124+
new MarkdownDocument().$concat(
125+
tableSectionContent,
126+
treesSectionContent,
127+
issuesSectionContent,
128+
),
118129
);
119130
}
120131

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,56 @@ describe('auditDetails', () => {
339339
expect(md).not.toMatch('#### Issues');
340340
});
341341

342+
it('should display tree section if trees are present', () => {
343+
const md = auditDetails({
344+
slug: 'line-coverage',
345+
title: 'Line coverage',
346+
score: 0.7,
347+
value: 70,
348+
displayValue: '70 %',
349+
details: {
350+
trees: [
351+
{
352+
type: 'coverage',
353+
title: 'Line coverage',
354+
root: {
355+
name: '.',
356+
values: { coverage: 0.7 },
357+
children: [
358+
{
359+
name: 'src',
360+
values: { coverage: 0.7 },
361+
children: [
362+
{
363+
name: 'App.tsx',
364+
values: {
365+
coverage: 0.8,
366+
missing: [{ startLine: 42, endLine: 50 }],
367+
},
368+
},
369+
{
370+
name: 'index.ts',
371+
values: {
372+
coverage: 0,
373+
missing: [{ startLine: 1, endLine: 10 }],
374+
},
375+
},
376+
],
377+
},
378+
],
379+
},
380+
},
381+
],
382+
},
383+
} as AuditReport).toString();
384+
expect(md).toMatch('<details>');
385+
expect(md).toMatch('#### Line coverage');
386+
expect(md).toContain('```');
387+
expect(md).toContain('└── src');
388+
expect(md).toContain('├── App.tsx');
389+
expect(md).not.toMatch('#### Issues');
390+
});
391+
342392
it('should render complete details section', () => {
343393
expect(
344394
auditDetails({
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
https://example.com
2+
├── https://example.com/styles/base.css
3+
└── https://example.com/styles/theme.css
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.
2+
└── src
3+
├── app
4+
│ ├── components
5+
│ │ ├── login
6+
│ │ │ └── login.component.ts
7+
│ │ └── platform
8+
│ │ └── platform.component.ts
9+
│ ├── services
10+
│ │ ├── api-client.service.ts
11+
│ │ └── auth.service.ts
12+
│ ├── app.component.ts
13+
│ ├── app.config.ts
14+
│ └── app.routes.ts
15+
└── main.ts
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
https://example.com
2+
├── https://example.com/styles/base.css 2 kB 20
3+
└── https://example.com/styles/theme.css 10 kB 100
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
. 72.00 %
2+
└── src 72.00 %
3+
├── app 88.00 %
4+
│ ├── components 68.00 %
5+
│ │ ├── login 48.00 %
6+
│ │ │ └── login.component.ts 48.00 %
7+
│ │ └── platform 74.00 %
8+
│ │ └── platform.component.ts 74.00 %
9+
│ ├── services 97.00 %
10+
│ │ ├── api-client.service.ts 99.00 %
11+
│ │ └── auth.service.ts 94.00 %
12+
│ ├── app.component.ts 92.00 %
13+
│ ├── app.config.ts 100.00 %
14+
│ └── app.routes.ts 100.00 %
15+
└── main.ts 0.00 %
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
. 70.00 %
2+
└── src 45.39 %
3+
├── components 97.36 %
4+
│ ├── CreateTodo.jsx 100.00 %
5+
│ ├── TodoFilter.jsx 90.90 % 18
6+
│ └── TodoList.jsx 100.00 %
7+
└── hooks 0.00 %
8+
└── useTodos.js 0.00 % 1-73
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
. 70.00 %
2+
└── src 70.00 %
3+
├── components 80.00 %
4+
│ ├── App.tsx 75.00 % login (42-50), logout (52-55)
5+
│ └── Layout.tsx 100.00 %
6+
├── index.ts 100.00 %
7+
└── utils.ts 0.00 % ErrorBoundary (1-10)

0 commit comments

Comments
 (0)