Skip to content

Commit 460c891

Browse files
committed
feat(core): include trees when uploading audits to portal
1 parent ecd7bd1 commit 460c891

File tree

6 files changed

+192
-10
lines changed

6 files changed

+192
-10
lines changed

package-lock.json

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"node": ">=22.14"
2323
},
2424
"dependencies": {
25-
"@code-pushup/portal-client": "^0.9.0",
25+
"@code-pushup/portal-client": "^0.13.0",
2626
"@isaacs/cliui": "^8.0.2",
2727
"@nx/devkit": "19.8.13",
2828
"@poppinss/cliui": "6.4.1",

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"ansis": "^3.3.0"
4545
},
4646
"peerDependencies": {
47-
"@code-pushup/portal-client": "^0.9.0"
47+
"@code-pushup/portal-client": "^0.13.0"
4848
},
4949
"peerDependenciesMeta": {
5050
"@code-pushup/portal-client": {

packages/core/src/lib/implementation/report-to-gql.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,27 @@ import type {
1111
TableAlignment as PortalTableAlignment,
1212
AuditReportTableCell as PortalTableCell,
1313
AuditReportTableColumn as PortalTableColumn,
14+
AuditReportTree as PortalTree,
15+
AuditReportTreeNode as PortalTreeNode,
16+
TreeType as PortalTreeType,
1417
SaveReportMutationVariables,
1518
} from '@code-pushup/portal-client';
1619
import type {
1720
AuditReport,
21+
BasicTree,
22+
BasicTreeNode,
1823
CategoryConfig,
1924
CategoryRef,
25+
CoverageTree,
26+
CoverageTreeNode,
2027
Group,
2128
Issue,
2229
IssueSeverity,
2330
PluginReport,
2431
Report,
2532
Table,
2633
TableAlignment,
34+
Tree,
2735
} from '@code-pushup/models';
2836

2937
export function reportToGQL(
@@ -75,7 +83,7 @@ function auditToGQL(audit: AuditReport): PortalAudit {
7583
displayValue: formattedValue,
7684
details,
7785
} = audit;
78-
const { issues, table } = details ?? {};
86+
const { issues, table, trees } = details ?? {};
7987
return {
8088
slug,
8189
title,
@@ -88,6 +96,7 @@ function auditToGQL(audit: AuditReport): PortalAudit {
8896
details: {
8997
...(issues && { issues: issues.map(issueToGQL) }),
9098
...(table && { tables: [tableToGQL(table)] }),
99+
...(trees && { trees: trees.map(treeToGQL) }),
91100
},
92101
}),
93102
};
@@ -134,6 +143,57 @@ export function tableToGQL(table: Table): PortalTable {
134143
};
135144
}
136145

146+
export function treeToGQL(tree: Tree): PortalTree {
147+
if (tree.type === 'coverage') {
148+
return coverageTreeToGQL(tree);
149+
}
150+
return basicTreeToGQL(tree);
151+
}
152+
153+
function basicTreeToGQL(tree: BasicTree): PortalTree {
154+
return {
155+
type: safeEnum<PortalTreeType>('Basic'),
156+
...(tree.title && { title: tree.title }),
157+
root: basicTreeNodeToGQL(tree.root),
158+
};
159+
}
160+
161+
function basicTreeNodeToGQL(node: BasicTreeNode): PortalTreeNode {
162+
return {
163+
name: node.name,
164+
...(node.values && {
165+
values: Object.entries(node.values).map(([key, value]) => ({
166+
key,
167+
value: value.toString(),
168+
})),
169+
}),
170+
...(node.children?.length && {
171+
children: node.children.map(basicTreeNodeToGQL),
172+
}),
173+
};
174+
}
175+
176+
function coverageTreeToGQL(tree: CoverageTree): PortalTree {
177+
return {
178+
type: safeEnum<PortalTreeType>('Coverage'),
179+
...(tree.title && { title: tree.title }),
180+
root: coverageTreeNodeToGQL(tree.root),
181+
};
182+
}
183+
184+
function coverageTreeNodeToGQL(node: CoverageTreeNode): PortalTreeNode {
185+
return {
186+
name: node.name,
187+
coverage: node.values.coverage,
188+
...(node.values.missing?.length && {
189+
missing: node.values.missing,
190+
}),
191+
...(node.children?.length && {
192+
children: node.children.map(coverageTreeNodeToGQL),
193+
}),
194+
};
195+
}
196+
137197
function categoryToGQL(category: CategoryConfig): PortalCategory {
138198
return {
139199
slug: category.slug,
@@ -188,7 +248,8 @@ function safeEnum<
188248
| PortalCategoryRefType
189249
| PortalIssueSeverity
190250
| PortalIssueSourceType
191-
| PortalTableAlignment,
251+
| PortalTableAlignment
252+
| PortalTreeType,
192253
>(value: `${T}`): T {
193254
return value as T;
194255
}

packages/core/src/lib/implementation/report-to-gql.unit.test.ts

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe } from 'vitest';
2-
import { issueToGQL, tableToGQL } from './report-to-gql.js';
2+
import { type AuditReportTree, TreeType } from '@code-pushup/portal-client';
3+
import { issueToGQL, tableToGQL, treeToGQL } from './report-to-gql.js';
34

45
describe('issueToGQL', () => {
56
it('transforms issue to GraphQL input type', () => {
@@ -81,3 +82,122 @@ describe('tableToGQL', () => {
8182
});
8283
});
8384
});
85+
86+
describe('treeToGQL', () => {
87+
it('should transform basic tree', () => {
88+
expect(
89+
treeToGQL({
90+
title: 'Critical request chain',
91+
root: {
92+
name: 'https://example.com',
93+
children: [
94+
{
95+
name: 'https://example.com/styles/base.css',
96+
values: { size: '2 kB', duration: 20 },
97+
},
98+
{
99+
name: 'https://example.com/styles/theme.css',
100+
values: { size: '10 kB', duration: 100 },
101+
},
102+
],
103+
},
104+
}),
105+
).toStrictEqual<AuditReportTree>({
106+
type: TreeType.Basic,
107+
title: 'Critical request chain',
108+
root: {
109+
name: 'https://example.com',
110+
children: [
111+
{
112+
name: 'https://example.com/styles/base.css',
113+
values: [
114+
{ key: 'size', value: '2 kB' },
115+
{ key: 'duration', value: '20' },
116+
],
117+
},
118+
{
119+
name: 'https://example.com/styles/theme.css',
120+
values: [
121+
{ key: 'size', value: '10 kB' },
122+
{ key: 'duration', value: '100' },
123+
],
124+
},
125+
],
126+
},
127+
});
128+
});
129+
130+
it('should transform coverage tree', () => {
131+
expect(
132+
treeToGQL({
133+
type: 'coverage',
134+
title: 'Function coverage',
135+
root: {
136+
name: '.',
137+
values: { coverage: 0.7 },
138+
children: [
139+
{
140+
name: 'src',
141+
values: { coverage: 0.7 },
142+
children: [
143+
{
144+
name: 'App.tsx',
145+
values: {
146+
coverage: 0.8,
147+
missing: [
148+
{
149+
startLine: 42,
150+
endLine: 50,
151+
name: 'login',
152+
kind: 'function',
153+
},
154+
],
155+
},
156+
},
157+
{
158+
name: 'index.ts',
159+
values: {
160+
coverage: 0,
161+
missing: [{ startLine: 1, endLine: 10 }],
162+
},
163+
},
164+
],
165+
},
166+
],
167+
},
168+
}),
169+
).toStrictEqual<AuditReportTree>({
170+
type: TreeType.Coverage,
171+
title: 'Function coverage',
172+
root: {
173+
name: '.',
174+
coverage: 0.7,
175+
children: [
176+
{
177+
name: 'src',
178+
coverage: 0.7,
179+
children: [
180+
{
181+
name: 'App.tsx',
182+
coverage: 0.8,
183+
missing: [
184+
{
185+
startLine: 42,
186+
endLine: 50,
187+
name: 'login',
188+
kind: 'function',
189+
},
190+
],
191+
},
192+
{
193+
name: 'index.ts',
194+
coverage: 0,
195+
missing: [{ startLine: 1, endLine: 10 }],
196+
},
197+
],
198+
},
199+
],
200+
},
201+
});
202+
});
203+
});

packages/models/src/lib/tree.unit.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('treeSchema', () => {
2626
expect(() =>
2727
treeSchema.parse({
2828
type: 'coverage',
29-
title: 'Critical request chain',
29+
title: 'Function coverage',
3030
root: {
3131
name: '.',
3232
values: { coverage: 0.7 },

0 commit comments

Comments
 (0)