Skip to content

Commit 18d4230

Browse files
committed
feat(plugin-lighthouse): convert criticalrequestchain details to trees and table
1 parent f2102eb commit 18d4230

File tree

5 files changed

+346
-25
lines changed

5 files changed

+346
-25
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`parseCriticalRequestChainToAuditDetails > should convert chains to basic trees 1`] = `
4+
[
5+
{
6+
"root": {
7+
"children": [
8+
{
9+
"children": [
10+
{
11+
"name": "https://fonts.gstatic.com/s/googlesans/v62/4UasrENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RPjIUvbQoi-E.woff2",
12+
"values": {
13+
"duration": "48.083 ms",
14+
"transferSize": "35.89 kB",
15+
},
16+
},
17+
{
18+
"name": "https://fonts.gstatic.com/s/robotomono/v23/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0me8iUI0.woff2",
19+
"values": {
20+
"duration": "63.943 ms",
21+
"transferSize": "22.31 kB",
22+
},
23+
},
24+
],
25+
"name": "https://fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap",
26+
"values": {
27+
"duration": "50.656 ms",
28+
"transferSize": "3.68 kB",
29+
},
30+
},
31+
{
32+
"children": [
33+
{
34+
"name": "https://fonts.gstatic.com/s/materialicons/v143/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2",
35+
"values": {
36+
"duration": "86.765 ms",
37+
"transferSize": "125.78 kB",
38+
},
39+
},
40+
],
41+
"name": "https://fonts.googleapis.com/css2?family=Material+Icons&family=Material+Symbols+Outlined&display=block",
42+
"values": {
43+
"duration": "55.102 ms",
44+
"transferSize": "615 B",
45+
},
46+
},
47+
{
48+
"name": "https://www.gstatic.com/devrel-devsite/prod/ve761bca974e16662f27aa8810df6d144acde5bdbeeca0dfd50e25f86621eaa19/chrome/css/app.css",
49+
"values": {
50+
"duration": "70.050 ms",
51+
"transferSize": "133.58 kB",
52+
},
53+
},
54+
{
55+
"name": "https://www.gstatic.com/devrel-devsite/prod/ve761bca974e16662f27aa8810df6d144acde5bdbeeca0dfd50e25f86621eaa19/chrome/css/dark-theme.css",
56+
"values": {
57+
"duration": "69.755 ms",
58+
"transferSize": "3.98 kB",
59+
},
60+
},
61+
{
62+
"name": "https://developer.chrome.com/extras.css",
63+
"values": {
64+
"duration": "199.327 ms",
65+
"transferSize": "109 B",
66+
},
67+
},
68+
],
69+
"name": "https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains",
70+
"values": {
71+
"duration": "472.304 ms",
72+
"transferSize": "18.66 kB",
73+
},
74+
},
75+
"type": "basic",
76+
},
77+
]
78+
`;
79+
80+
exports[`parseCriticalRequestChainToAuditDetails > should convert longest chain to table 1`] = `
81+
{
82+
"columns": [
83+
{
84+
"align": "right",
85+
"key": "duration",
86+
"label": "Duration",
87+
},
88+
{
89+
"align": "right",
90+
"key": "transferSize",
91+
"label": "Transfer size",
92+
},
93+
{
94+
"align": "right",
95+
"key": "length",
96+
"label": "Length",
97+
},
98+
],
99+
"rows": [
100+
{
101+
"duration": "757.072 ms",
102+
"length": 3,
103+
"transferSize": "125.78 kB",
104+
},
105+
],
106+
"title": "Longest chain",
107+
}
108+
`;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type Details from 'lighthouse/types/lhr/audit-details';
2+
import type {
3+
AuditDetails,
4+
BasicTree,
5+
BasicTreeNode,
6+
Table,
7+
} from '@code-pushup/models';
8+
import { formatBytes, formatDuration } from '@code-pushup/utils';
9+
10+
const DURATION_DECIMALS = 3;
11+
12+
export function parseCriticalRequestChainToAuditDetails(
13+
details: Details.CriticalRequestChain,
14+
): AuditDetails {
15+
const trees = chainsToTrees(details);
16+
const table = longestChainToTable(details);
17+
return { table, trees };
18+
}
19+
20+
function longestChainToTable(details: Details.CriticalRequestChain): Table {
21+
const longestChain = {
22+
duration: formatDuration(details.longestChain.duration, DURATION_DECIMALS),
23+
transferSize: formatBytes(details.longestChain.transferSize),
24+
length: details.longestChain.length,
25+
};
26+
type ColumnKey = keyof typeof longestChain;
27+
28+
return {
29+
title: 'Longest chain',
30+
columns: [
31+
{
32+
key: 'duration' satisfies ColumnKey,
33+
label: 'Duration',
34+
align: 'right',
35+
},
36+
{
37+
key: 'transferSize' satisfies ColumnKey,
38+
label: 'Transfer size',
39+
align: 'right',
40+
},
41+
{
42+
key: 'length' satisfies ColumnKey,
43+
label: 'Length',
44+
align: 'right',
45+
},
46+
],
47+
rows: [longestChain],
48+
};
49+
}
50+
51+
function chainsToTrees(details: Details.CriticalRequestChain): BasicTree[] {
52+
return Object.values(details.chains)
53+
.map(chainToTreeNode)
54+
.map(root => ({ type: 'basic', root }));
55+
}
56+
57+
function chainToTreeNode(
58+
chain: Details.SimpleCriticalRequestNode[string],
59+
): BasicTreeNode {
60+
return {
61+
name: chain.request.url,
62+
values: {
63+
duration: formatDuration(
64+
(chain.request.endTime - chain.request.startTime) * 1000,
65+
DURATION_DECIMALS,
66+
),
67+
transferSize: formatBytes(chain.request.transferSize),
68+
},
69+
...(chain.children && {
70+
children: Object.values(chain.children).map(chainToTreeNode),
71+
}),
72+
};
73+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import type Details from 'lighthouse/types/lhr/audit-details.js';
2+
import { parseCriticalRequestChainToAuditDetails } from './critical-request-chain.type.js';
3+
4+
describe('parseCriticalRequestChainToAuditDetails', () => {
5+
const details: Details.CriticalRequestChain = {
6+
type: 'criticalrequestchain',
7+
chains: {
8+
C49E1A2533B1ECBA8C5DB9292950C3C1: {
9+
request: {
10+
url: 'https://developer.chrome.com/docs/lighthouse/performance/critical-request-chains',
11+
startTime: 28_303.087_316,
12+
endTime: 28_303.559_62,
13+
responseReceivedTime: 28_303.558_19,
14+
transferSize: 19_110,
15+
},
16+
children: {
17+
'52823.2': {
18+
request: {
19+
url: 'https://fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap',
20+
startTime: 28_303.570_333,
21+
endTime: 28_303.620_989,
22+
responseReceivedTime: 28_303.608_522,
23+
transferSize: 3765,
24+
},
25+
children: {
26+
'52823.43': {
27+
request: {
28+
url: 'https://fonts.gstatic.com/s/googlesans/v62/4UasrENHsxJlGDuGo1OIlJfC6l_24rlCK1Yo_Iqcsih3SAyH6cAwhX9RPjIUvbQoi-E.woff2',
29+
startTime: 28_303.757_482,
30+
endTime: 28_303.805_565,
31+
responseReceivedTime: 28_303.787_04,
32+
transferSize: 36_754,
33+
},
34+
},
35+
'52823.128': {
36+
request: {
37+
url: 'https://fonts.gstatic.com/s/robotomono/v23/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0me8iUI0.woff2',
38+
startTime: 28_303.757_727,
39+
endTime: 28_303.821_67,
40+
responseReceivedTime: 28_303.805_798,
41+
transferSize: 22_850,
42+
},
43+
},
44+
},
45+
},
46+
'52823.3': {
47+
request: {
48+
url: 'https://fonts.googleapis.com/css2?family=Material+Icons&family=Material+Symbols+Outlined&display=block',
49+
startTime: 28_303.573_062,
50+
endTime: 28_303.628_164,
51+
responseReceivedTime: 28_303.626_091,
52+
transferSize: 615,
53+
},
54+
children: {
55+
'52823.141': {
56+
request: {
57+
url: 'https://fonts.gstatic.com/s/materialicons/v143/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2',
58+
startTime: 28_303.757_623,
59+
endTime: 28_303.844_388,
60+
responseReceivedTime: 28_303.820_133,
61+
transferSize: 128_797,
62+
},
63+
},
64+
},
65+
},
66+
'52823.4': {
67+
request: {
68+
url: 'https://www.gstatic.com/devrel-devsite/prod/ve761bca974e16662f27aa8810df6d144acde5bdbeeca0dfd50e25f86621eaa19/chrome/css/app.css',
69+
startTime: 28_303.573_196,
70+
endTime: 28_303.643_246,
71+
responseReceivedTime: 28_303.593_019,
72+
transferSize: 136_789,
73+
},
74+
},
75+
'52823.5': {
76+
request: {
77+
url: 'https://www.gstatic.com/devrel-devsite/prod/ve761bca974e16662f27aa8810df6d144acde5bdbeeca0dfd50e25f86621eaa19/chrome/css/dark-theme.css',
78+
startTime: 28_303.573_293_999_998,
79+
endTime: 28_303.643_049,
80+
responseReceivedTime: 28_303.641_657,
81+
transferSize: 4072,
82+
},
83+
},
84+
'52823.6': {
85+
request: {
86+
url: 'https://developer.chrome.com/extras.css',
87+
startTime: 28_303.573_378,
88+
endTime: 28_303.772_705,
89+
responseReceivedTime: 28_303.771_657,
90+
transferSize: 109,
91+
},
92+
},
93+
},
94+
},
95+
},
96+
longestChain: {
97+
duration: 757.072_000_000_625_8,
98+
length: 3,
99+
transferSize: 128_797,
100+
},
101+
};
102+
103+
it('should convert chains to basic trees', () => {
104+
expect(
105+
parseCriticalRequestChainToAuditDetails(details).trees,
106+
).toMatchSnapshot();
107+
});
108+
109+
it('should convert longest chain to table', () => {
110+
expect(
111+
parseCriticalRequestChainToAuditDetails(details).table,
112+
).toMatchSnapshot();
113+
});
114+
});

packages/plugin-lighthouse/src/lib/runner/details/details.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { Result } from 'lighthouse/types/lhr/audit-result';
55
import type { AuditDetails, Table } from '@code-pushup/models';
66
import { ui } from '@code-pushup/utils';
77
import { PLUGIN_SLUG } from '../constants.js';
8+
import { parseCriticalRequestChainToAuditDetails } from './critical-request-chain.type.js';
89
import { parseOpportunityToAuditDetailsTable } from './opportunity.type.js';
910
import { parseTableToAuditDetailsTable } from './table.type.js';
1011

@@ -25,23 +26,25 @@ export function toAuditDetails<T extends FormattedIcu<Details>>(
2526
const opportunity: Table | undefined =
2627
parseOpportunityToAuditDetailsTable(details);
2728
return opportunity ? { table: opportunity } : {};
29+
case 'criticalrequestchain':
30+
return parseCriticalRequestChainToAuditDetails(details);
31+
case 'treemap-data': // TODO: implement
32+
33+
// TODO: add 'list' once array of tables supported in audit details
34+
35+
// TODO: add 'screenshot' and 'filmstrip' once images supported in audit details
2836
}
2937
return {};
3038
}
3139

3240
// @TODO implement all details
3341
export const unsupportedDetailTypes = new Set([
3442
'debugdata',
35-
'treemap-data',
3643
'screenshot',
3744
'filmstrip',
38-
'criticalrequestchain',
3945
]);
4046

41-
export function logUnsupportedDetails(
42-
lhrAudits: Result[],
43-
{ displayCount = 3 }: { displayCount?: number } = {},
44-
) {
47+
export function logUnsupportedDetails(lhrAudits: Result[]) {
4548
const slugsWithDetailParsingErrors = [
4649
...new Set(
4750
lhrAudits
@@ -52,14 +55,12 @@ export function logUnsupportedDetails(
5255
),
5356
];
5457
if (slugsWithDetailParsingErrors.length > 0) {
55-
const postFix = (count: number) =>
56-
count > displayCount ? ` and ${count - displayCount} more.` : '';
5758
ui().logger.debug(
5859
`${yellow('⚠')} Plugin ${bold(
5960
PLUGIN_SLUG,
6061
)} skipped parsing of unsupported audit details: ${bold(
61-
slugsWithDetailParsingErrors.slice(0, displayCount).join(', '),
62-
)}${postFix(slugsWithDetailParsingErrors.length)}`,
62+
slugsWithDetailParsingErrors.join(', '),
63+
)}.`,
6364
);
6465
}
6566
}

0 commit comments

Comments
 (0)