Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

report: map metrics to audits #11732

Merged
merged 6 commits into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -832,36 +832,89 @@ Object {
"performance": Object {
"auditRefs": Array [
Object {
"acronym": "FCP",
"group": "metrics",
"id": "first-contentful-paint",
"relevantAudits": Array [
"server-response-time",
"render-blocking-resources",
"redirects",
"critical-request-chains",
"uses-text-compression",
"uses-rel-preconnect",
"uses-rel-preload",
"font-display",
"unminified-javascript",
"unminified-css",
"unused-css-rules",
],
"weight": 15,
},
Object {
"acronym": "SI",
"group": "metrics",
"id": "speed-index",
"weight": 15,
},
Object {
"acronym": "LCP",
"group": "metrics",
"id": "largest-contentful-paint",
"relevantAudits": Array [
"server-response-time",
"render-blocking-resources",
"redirects",
"critical-request-chains",
"uses-text-compression",
"uses-rel-preconnect",
"uses-rel-preload",
"font-display",
"unminified-javascript",
"unminified-css",
"unused-css-rules",
"largest-contentful-paint-element",
"preload-lcp-image",
"unused-javascript",
"efficient-animated-content",
"total-byte-weight",
],
"weight": 25,
},
Object {
"acronym": "TTI",
"group": "metrics",
"id": "interactive",
"weight": 15,
},
Object {
"acronym": "TBT",
"group": "metrics",
"id": "total-blocking-time",
"relevantAudits": Array [
"long-tasks",
"third-party-summary",
"third-party-facades",
"bootup-time",
"mainthread-work-breakdown",
"dom-size",
"duplicated-javascript",
"legacy-javascript",
],
"weight": 25,
},
Object {
"acronym": "CLS",
"group": "metrics",
"id": "cumulative-layout-shift",
"relevantAudits": Array [
"layout-shift-elements",
"non-composited-animations",
"unsized-images",
],
"weight": 5,
},
Object {
"acronym": "FCI",
"id": "first-cpu-idle",
"weight": 0,
},
Expand All @@ -870,6 +923,7 @@ Object {
"weight": 0,
},
Object {
"acronym": "FMP",
"id": "first-meaningful-paint",
"weight": 0,
},
Expand Down
20 changes: 11 additions & 9 deletions lighthouse-core/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

const constants = require('./constants.js');
const i18n = require('../lib/i18n/i18n.js');
const m2a = require('./metrics-to-audits.js');

const UIStrings = {
/** Title of the Performance category of audits. Equivalent to 'Web performance', this term is inclusive of all web page speed and loading optimization topics. Also used as a label of a score gauge; try to limit to 20 characters. */
Expand Down Expand Up @@ -420,16 +421,17 @@ const defaultConfig = {
'performance': {
title: str_(UIStrings.performanceCategoryTitle),
auditRefs: [
{id: 'first-contentful-paint', weight: 15, group: 'metrics'},
{id: 'speed-index', weight: 15, group: 'metrics'},
{id: 'largest-contentful-paint', weight: 25, group: 'metrics'},
{id: 'interactive', weight: 15, group: 'metrics'},
{id: 'total-blocking-time', weight: 25, group: 'metrics'},
{id: 'cumulative-layout-shift', weight: 5, group: 'metrics'},
// intentionally left out of metrics group so they won't be displayed
{id: 'first-cpu-idle', weight: 0},
{id: 'first-contentful-paint', weight: 15, group: 'metrics', acronym: 'FCP', relevantAudits: m2a.fcpRelevantAudits},
{id: 'speed-index', weight: 15, group: 'metrics', acronym: 'SI'},
{id: 'largest-contentful-paint', weight: 25, group: 'metrics', acronym: 'LCP', relevantAudits: m2a.lcpRelevantAudits},
{id: 'interactive', weight: 15, group: 'metrics', acronym: 'TTI'},
{id: 'total-blocking-time', weight: 25, group: 'metrics', acronym: 'TBT', relevantAudits: m2a.tbtRelevantAudits},
{id: 'cumulative-layout-shift', weight: 5, group: 'metrics', acronym: 'CLS', relevantAudits: m2a.clsRelevantAudits},

// These are our "invisible" metrics. Not displayed, but still in the LHR
{id: 'first-cpu-idle', weight: 0, acronym: 'FCI'},
{id: 'max-potential-fid', weight: 0},
{id: 'first-meaningful-paint', weight: 0},
{id: 'first-meaningful-paint', weight: 0, acronym: 'FMP'},
{id: 'estimated-input-latency', weight: 0},

{id: 'render-blocking-resources', weight: 0, group: 'load-opportunities'},
Expand Down
55 changes: 55 additions & 0 deletions lighthouse-core/config/metrics-to-audits.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @license Copyright 2021 The Lighthouse Authors. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
'use strict';

// go/lh-audit-metric-mapping
const fcpRelevantAudits = [
'server-response-time',
'render-blocking-resources',
'redirects',
'critical-request-chains',
'uses-text-compression',
'uses-rel-preconnect',
'uses-rel-preload',
'font-display',
'unminified-javascript',
'unminified-css',
'unused-css-rules',
];

const lcpRelevantAudits = [
...fcpRelevantAudits,
'largest-contentful-paint-element',
'preload-lcp-image',
'unused-javascript',
'efficient-animated-content',
'total-byte-weight',
];

const tbtRelevantAudits = [
'long-tasks',
'third-party-summary',
'third-party-facades',
'bootup-time',
'mainthread-work-breakdown',
'dom-size',
'duplicated-javascript',
'legacy-javascript',
];

const clsRelevantAudits = [
'layout-shift-elements',
'non-composited-animations',
'unsized-images',
// 'preload-fonts', // actually in BP, rather than perf
];

module.exports = {
fcpRelevantAudits,
lcpRelevantAudits,
tbtRelevantAudits,
clsRelevantAudits,
};
4 changes: 4 additions & 0 deletions lighthouse-core/lib/i18n/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const MESSAGE_I18N_ID_REGEX = / | [^\s]+$/;
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
}
// Deal with buggy regex caching. https://github.com/andyearnshaw/Intl.js/issues/308
if (IntlPolyfill.__disableRegExpRestore) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

big LOL

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

admittedly i should have googled before i build node 12 with small-icu. but yes. annoying.

IntlPolyfill.__disableRegExpRestore()
}
})();

const UIStrings = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,6 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
if (fci) v5andv6metrics.push(fci);
if (fmp) v5andv6metrics.push(fmp);

/** @type {Record<string, string>} */
const acronymMapping = {
'cumulative-layout-shift': 'CLS',
'first-contentful-paint': 'FCP',
'first-cpu-idle': 'FCI',
'first-meaningful-paint': 'FMP',
'interactive': 'TTI',
'largest-contentful-paint': 'LCP',
'speed-index': 'SI',
'total-blocking-time': 'TBT',
};

/**
* Clamp figure to 2 decimal places
* @param {number} val
Expand All @@ -147,7 +135,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
} else {
value = 'null';
}
return [acronymMapping[audit.id] || audit.id, value];
return [audit.acronym || audit.id, value];
});
const paramPairs = [...metricPairs];

Expand Down Expand Up @@ -225,6 +213,13 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
.filter(audit => audit.group === 'load-opportunities' && !Util.showAsPassed(audit.result))
.sort((auditA, auditB) => this._getWastedMs(auditB) - this._getWastedMs(auditA));


const filterableMetrics = metricAudits.filter(a => !!a.relevantAudits);
// TODO: only add if there are opportunities & diagnostics rendered.
if (filterableMetrics.length) {
this.renderMetricAuditFilter(filterableMetrics, element);
}

if (opportunityAudits.length) {
// Scale the sparklines relative to savings, minimum 2s to not overstate small savings
const minimumScale = 2000;
Expand Down Expand Up @@ -299,6 +294,74 @@ class PerformanceCategoryRenderer extends CategoryRenderer {

return element;
}

/**
* Render the control to filter the audits by metric. The filtering is done at runtime by CSS only
* @param {LH.ReportResult.AuditRef[]} filterableMetrics
* @param {HTMLDivElement} categoryEl
*/
renderMetricAuditFilter(filterableMetrics, categoryEl) {
// thx https://codepen.io/surjithctly/pen/weEJvX
const metricFilterEl = this.dom.createElement('div', 'lh-metricfilter');
const textEl = this.dom.createChildOf(metricFilterEl, 'span', 'lh-metricfilter__text');
textEl.textContent = 'Show audits relevant to: ';
const labelSelectors = [];
const auditSelectors = [];

const filterChoices = /** @type {LH.ReportResult.AuditRef[]} */ ([
({acronym: 'All'}),
...filterableMetrics,
]);
for (const metric of filterChoices) {
// The radio elements are appended into `categoryEl` to allow the sweet ~ selectors to work
const elemId = `metric-${metric.acronym}`;
const radioEl = this.dom.createChildOf(categoryEl, 'input', 'lh-metricfilter__radio', {
type: 'radio',
name: 'metricsfilter',
id: elemId,
hidden: 'true',
});
const labelEl = this.dom.createChildOf(metricFilterEl, 'label', 'lh-metricfilter__label', {
for: elemId,
title: metric.result && metric.result.title,
});
labelEl.textContent = metric.acronym || metric.id;
if (metric.acronym === 'All') {
radioEl.checked = true;
}
// Dynamically write some CSS, for the CSS-only filtering
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all this dynamic CSS seems much harder to follow than the equivalent JS would be. What reasons did you rule out JS?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the CSS's declarative state communicates all the changes pretty clearly.
I'll give the JS equivalent a try in a second PR, for comparison.

labelSelectors.push(`.lh-metricfilter__radio#${elemId}:checked ~ .lh-metricfilter > .lh-metricfilter__label[for="${elemId}"]`); // eslint-disable-line max-len
if (metric.relevantAudits) {
/* Generate some CSS selectors like this:
#metric-CLS:checked ~ .lh-audit-group > #layout-shift-elements,
#metric-CLS:checked ~ .lh-audit-group > #non-composited-animations,
#metric-CLS:checked ~ .lh-audit-group > #unsized-images
*/
auditSelectors.push(metric.relevantAudits.map(auditId => `#${elemId}:checked ~ .lh-audit-group > #${auditId}`).join(',\n')); // eslint-disable-line max-len
}
}

const styleEl = this.dom.createChildOf(metricFilterEl, 'style');
// eslint-disable-next-line max-len
styleEl.textContent = `
${labelSelectors.join(',\n')} {
background: var(--color-blue-A700);
color: var(--color-white);
}
/* If selecting non-All, hide all audits (and also the group header/description… */
.lh-metricfilter__radio:checked:not(#metric-All) ~ .lh-audit-group .lh-audit,
.lh-metricfilter__radio:checked:not(#metric-All) ~ .lh-audit-group .lh-audit-group__description,
.lh-metricfilter__radio:checked:not(#metric-All) ~ .lh-audit-group .lh-audit-group__itemcount {
display: none;
}
/* …And then display:block the relevant ones */
${auditSelectors.join(',\n')} {
display: block;
}
/*# sourceURL=metricfilter.css */
`;
categoryEl.append(metricFilterEl);
}
}

if (typeof module !== 'undefined' && module.exports) {
Expand Down
37 changes: 37 additions & 0 deletions lighthouse-core/report/html/report-styles.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions lighthouse-core/test/config/default-config-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,19 @@ describe('Default Config', () => {
`${auditResult.id} has an undefined overallSavingsMs`);
});
});

it('relevantAudits map to existing perf audit', () => {
const metricsWithRelevantAudits = defaultConfig.categories.performance.auditRefs.filter(a =>
a.relevantAudits);
const allPerfAuditIds = defaultConfig.categories.performance.auditRefs.map(a => a.id);

for (const metric of metricsWithRelevantAudits) {
assert.ok(Array.isArray(metric.relevantAudits) && metric.relevantAudits.length);

for (const auditid of metric.relevantAudits) {
const errMsg = `(${auditid}) is relevant audit for (${metric.id}), but no audit found.`;
assert.ok(allPerfAuditIds.includes(auditid), errMsg);
}
}
});
});
Loading