Skip to content

Commit

Permalink
report: final metrics display, icons, whitespace polish (#5130)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulirish authored May 7, 2018
1 parent d03a05e commit 1750799
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 194 deletions.
4 changes: 2 additions & 2 deletions lighthouse-core/audits/first-meaningful-paint.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class FirstMeaningfulPaint extends Audit {
static get meta() {
return {
name: 'first-meaningful-paint',
description: 'First meaningful paint',
helpText: 'First meaningful paint measures when the primary content of a page is visible. ' +
description: 'First Meaningful Paint',
helpText: 'First Meaningful Paint measures when the primary content of a page is visible. ' +
'[Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint).',
scoreDisplayMode: Audit.SCORING_MODES.NUMERIC,
requiredArtifacts: ['traces'],
Expand Down
3 changes: 0 additions & 3 deletions lighthouse-core/config/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ module.exports = {
groups: {
'metrics': {
title: 'Metrics',
description: 'These metrics encapsulate your web app\'s performance across a number of dimensions.',
},
'load-opportunities': {
title: 'Opportunities',
Expand Down Expand Up @@ -248,7 +247,6 @@ module.exports = {
categories: {
'performance': {
name: 'Performance',
description: 'These encapsulate your web app\'s current performance and opportunities to improve it.',
audits: [
{id: 'first-contentful-paint', weight: 3, group: 'metrics'},
{id: 'first-meaningful-paint', weight: 1, group: 'metrics'},
Expand Down Expand Up @@ -363,7 +361,6 @@ module.exports = {
},
'best-practices': {
name: 'Best Practices',
description: 'We\'ve compiled some recommendations for modernizing your web app and avoiding performance pitfalls.',
audits: [
{id: 'appcache-manifest', weight: 1},
{id: 'no-websql', weight: 1},
Expand Down
37 changes: 16 additions & 21 deletions lighthouse-core/report/html/renderer/category-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ class CategoryRenderer {
this.dom.find('.lh-audit__display-text', auditEl).textContent = displayValue;
}

this.dom.find('.lh-audit__title', auditEl).appendChild(
this.dom.convertMarkdownCodeSnippets(audit.result.description));
const titleEl = this.dom.find('.lh-audit__title', auditEl);
titleEl.appendChild(this.dom.convertMarkdownCodeSnippets(audit.result.description));
this.dom.find('.lh-audit__description', auditEl)
.appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.helpText));

Expand All @@ -64,7 +64,7 @@ class CategoryRenderer {
const tooltip = this.dom.createChildOf(textEl, 'div', 'lh-error-tooltip-content tooltip');
tooltip.textContent = audit.result.debugString || 'Report error: no audit information';
} else if (audit.result.debugString) {
const debugStrEl = auditEl.appendChild(this.dom.createElement('div', 'lh-debug'));
const debugStrEl = this.dom.createChildOf(titleEl, 'div', 'lh-debug');
debugStrEl.textContent = audit.result.debugString;
}
return auditEl;
Expand All @@ -86,7 +86,7 @@ class CategoryRenderer {
* @param {!ReportRenderer.CategoryJSON} category
* @return {!Element}
*/
renderCategoryScore(category) {
renderCategoryHeader(category) {
const tmpl = this.dom.cloneTemplate('#tmpl-lh-category-header', this.templateContext);

const gaugeContainerEl = this.dom.find('.lh-score__gauge', tmpl);
Expand All @@ -95,8 +95,11 @@ class CategoryRenderer {

this.dom.find('.lh-category-header__title', tmpl).appendChild(
this.dom.convertMarkdownCodeSnippets(category.name));
this.dom.find('.lh-category-header__description', tmpl)
.appendChild(this.dom.convertMarkdownLinkSnippets(category.description));
if (category.description) {
const descEl = this.dom.convertMarkdownLinkSnippets(category.description);
this.dom.find('.lh-category-header__description', tmpl).appendChild(descEl);
}


return /** @type {!Element} */ (tmpl.firstElementChild);
}
Expand Down Expand Up @@ -157,9 +160,7 @@ class CategoryRenderer {
* @return {!Element}
*/
_renderFailedAuditsSection(elements) {
const failedElem = this.renderAuditGroup({
title: `Failed audits`,
}, {expandable: false, itemCount: this._getTotalAuditsLength(elements)});
const failedElem = this.dom.createElement('div');
failedElem.classList.add('lh-failed-audits');
elements.forEach(elem => failedElem.appendChild(elem));
return failedElem;
Expand Down Expand Up @@ -253,7 +254,7 @@ class CategoryRenderer {
render(category, groupDefinitions) {
const element = this.dom.createElement('div', 'lh-category');
this.createPermalinkSpan(element, category.id);
element.appendChild(this.renderCategoryScore(category));
element.appendChild(this.renderCategoryHeader(category));

const manualAudits = category.audits.filter(item => item.result.scoreDisplayMode === 'manual');
const nonManualAudits = category.audits.filter(audit => !manualAudits.includes(audit));
Expand Down Expand Up @@ -300,43 +301,37 @@ class CategoryRenderer {
auditsUngrouped.notApplicable.forEach((/** @type {!ReportRenderer.AuditJSON} */ audit, i) =>
notApplicableElements.push(this.renderAudit(audit, i)));

let hasFailedGroups = false;

Object.keys(auditsGroupedByGroup).forEach(groupId => {
const group = groupDefinitions[groupId];
const groups = auditsGroupedByGroup[groupId];

if (groups.failed.length) {
const auditGroupElem = this.renderAuditGroup(group, {expandable: false});
groups.failed.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i)));
auditGroupElem.classList.add('lh-audit-group--unadorned');
auditGroupElem.open = true;
failedElements.push(auditGroupElem);

hasFailedGroups = true;
}

if (groups.passed.length) {
const auditGroupElem = this.renderAuditGroup(group, {expandable: true});
groups.passed.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i)));
auditGroupElem.classList.add('lh-audit-group--unadorned');
passedElements.push(auditGroupElem);
}

if (groups.notApplicable.length) {
const auditGroupElem = this.renderAuditGroup(group, {expandable: true});
groups.notApplicable.forEach((item, i) =>
auditGroupElem.appendChild(this.renderAudit(item, i)));
auditGroupElem.classList.add('lh-audit-group--unadorned');
notApplicableElements.push(auditGroupElem);
}
});

if (failedElements.length) {
// if failed audits are grouped skip the 'X Failed Audits' header
if (hasFailedGroups) {
failedElements.forEach(elem => element.appendChild(elem));
} else {
const failedElem = this._renderFailedAuditsSection(failedElements);
element.appendChild(failedElem);
}
const failedElem = this._renderFailedAuditsSection(failedElements);
element.appendChild(failedElem);
}

if (manualAudits.length) {
Expand Down
15 changes: 2 additions & 13 deletions lighthouse-core/report/html/renderer/details-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,22 +253,11 @@ class DetailsRenderer {

for (const thumbnail of details.items) {
const frameEl = this._dom.createChildOf(filmstripEl, 'div', 'lh-filmstrip__frame');

let timing = Util.formatMilliseconds(thumbnail.timing, 1);
if (thumbnail.timing > 1000) {
timing = Util.formatNumber(thumbnail.timing / 1000) + ' s';
}

const timingEl = this._dom.createChildOf(frameEl, 'div', 'lh-filmstrip__timestamp');
timingEl.textContent = timing;

const base64data = thumbnail.data;
this._dom.createChildOf(frameEl, 'img', 'lh-filmstrip__thumbnail', {
src: `data:image/jpeg;base64,${base64data}`,
alt: `Screenshot at ${timing}`,
src: `data:image/jpeg;base64,${thumbnail.data}`,
alt: `Screenshot`,
});
}

return filmstripEl;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,12 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
element.classList.add(`lh-load-opportunity--${Util.calculateRating(audit.result.score)}`);
element.id = audit.result.name;

const summary = this.dom.find('.lh-load-opportunity__summary', tmpl);
const titleEl = this.dom.find('.lh-load-opportunity__title', tmpl);
titleEl.textContent = audit.result.description;
this.dom.find('.lh-audit__index', element).textContent = `${index + 1}`;

if (audit.result.debugString || audit.result.error) {
const debugStrEl = this.dom.createChildOf(summary, 'div', 'lh-debug');
const debugStrEl = this.dom.createChildOf(titleEl, 'div', 'lh-debug');
debugStrEl.textContent = audit.result.debugString || 'Audit error';
}
if (audit.result.error) return element;
Expand Down Expand Up @@ -86,18 +85,37 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
return element;
}

/**
* Get an audit's wastedMs to sort the opportunity by, and scale the sparkline width
* Opportunties with an error won't have a summary object, so MIN_VALUE is returned to keep any
* erroring opportunities last in sort order.
* @param {!ReportRenderer.AuditJSON} audit
* @return {number}
*/
_getWastedMs(audit) {
if (
audit.result.details &&
audit.result.details.summary &&
typeof audit.result.details.summary.wastedMs === 'number'
) {
return audit.result.details.summary.wastedMs;
} else {
return Number.MIN_VALUE;
}
}

/**
* @override
*/
render(category, groups) {
const element = this.dom.createElement('div', 'lh-category');
this.createPermalinkSpan(element, category.id);
element.appendChild(this.renderCategoryScore(category));
element.appendChild(this.renderCategoryHeader(category));

// Metrics
const metricAudits = category.audits.filter(audit => audit.group === 'metrics');
const metricAuditsEl = this.renderAuditGroup(groups['metrics'], {expandable: false});

// Metrics
const keyMetrics = metricAudits.filter(a => a.weight >= 3);
const otherMetrics = metricAudits.filter(a => a.weight < 3);

Expand All @@ -111,9 +129,16 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
otherMetrics.forEach(item => {
metricsColumn2El.appendChild(this._renderMetric(item));
});
const estValuesEl = this.dom.createChildOf(metricsColumn2El, 'div',
'lh-metrics__disclaimer lh-metrics__disclaimer');
estValuesEl.textContent = 'Values are estimated and may vary.';

metricAuditsEl.open = true;
metricAuditsEl.classList.add('lh-audit-group--metrics');
element.appendChild(metricAuditsEl);

// Filmstrip
const timelineEl = this.dom.createChildOf(metricAuditsEl, 'div', 'lh-timeline');
const timelineEl = this.dom.createChildOf(element, 'div', 'lh-filmstrip-container');
const thumbnailAudit = category.audits.find(audit => audit.id === 'screenshot-thumbnails');
const thumbnailResult = thumbnailAudit && thumbnailAudit.result;
if (thumbnailResult && thumbnailResult.details) {
Expand All @@ -124,15 +149,14 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
timelineEl.appendChild(filmstripEl);
}

metricAuditsEl.open = true;
element.appendChild(metricAuditsEl);

// Opportunities
const opportunityAudits = category.audits
.filter(audit => audit.group === 'load-opportunities' && !Util.showAsPassed(audit.result))
.sort((auditA, auditB) => auditB.result.rawValue - auditA.result.rawValue);
.sort((auditA, auditB) => this._getWastedMs(auditB) - this._getWastedMs(auditA));

if (opportunityAudits.length) {
const maxWaste = Math.max(...opportunityAudits.map(audit => audit.result.rawValue));
const wastedMsValues = opportunityAudits.map(audit => this._getWastedMs(audit));
const maxWaste = Math.max(...wastedMsValues);
const scale = Math.ceil(maxWaste / 1000) * 1000;
const groupEl = this.renderAuditGroup(groups['load-opportunities'], {expandable: false});
const tmpl = this.dom.cloneTemplate('#tmpl-lh-opportunity-header', this.templateContext);
Expand All @@ -141,6 +165,7 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
opportunityAudits.forEach((item, i) =>
groupEl.appendChild(this._renderOpportunity(item, i, scale)));
groupEl.open = true;
groupEl.classList.add('lh-audit-group--opportunities');
element.appendChild(groupEl);
}

Expand All @@ -157,9 +182,11 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
const groupEl = this.renderAuditGroup(groups['diagnostics'], {expandable: false});
diagnosticAudits.forEach((item, i) => groupEl.appendChild(this.renderAudit(item, i)));
groupEl.open = true;
groupEl.classList.add('lh-audit-group--diagnostics');
element.appendChild(groupEl);
}

// Passed audits
const passedElements = category.audits
.filter(audit => (audit.group === 'load-opportunities' || audit.group === 'diagnostics') &&
Util.showAsPassed(audit.result))
Expand Down
2 changes: 1 addition & 1 deletion lighthouse-core/report/html/renderer/report-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ ReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions
* name: string,
* id: string,
* score: (number|null),
* description: string,
* description: (string|undefined),
* manualDescription: string,
* audits: !Array<!ReportRenderer.AuditJSON>
* }}
Expand Down
Loading

0 comments on commit 1750799

Please sign in to comment.