-
Notifications
You must be signed in to change notification settings - Fork 9.5k
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
core(lhr): make reportCategories shallow; move audit scores to AuditResultJSON #4711
Changes from 9 commits
539ec76
47669a6
e24e3c7
ef10f4f
cf4ae95
010f2fd
e4784a2
fddb987
5e1337d
c2e25b5
b4ac5f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,9 +30,11 @@ class ReportRenderer { | |
* @param {!Element} container Parent element to render the report into. | ||
*/ | ||
renderReport(report, container) { | ||
container.textContent = ''; // Remove previous report. | ||
const element = container.appendChild(this._renderReport(report)); | ||
// If any mutations happen to the report within the renderers, we want the original object untouched | ||
const clone = /** @type {!ReportRenderer.ReportJSON} */ (JSON.parse(JSON.stringify(report))); | ||
|
||
container.textContent = ''; // Remove previous report. | ||
const element = container.appendChild(this._renderReport(clone)); | ||
return /** @type {!Element} **/ (element); | ||
} | ||
|
||
|
@@ -155,24 +157,39 @@ class ReportRenderer { | |
perfCategoryRenderer.setTemplateContext(this._templateContext); | ||
|
||
const categories = reportSection.appendChild(this._dom.createElement('div', 'lh-categories')); | ||
|
||
ReportRenderer.smooshAuditResultsIntoCategories(report.audits, report.reportCategories); | ||
|
||
for (const category of report.reportCategories) { | ||
if (scoreHeader) { | ||
scoreHeader.appendChild(categoryRenderer.renderScoreGauge(category)); | ||
} | ||
|
||
let renderer = categoryRenderer; | ||
|
||
if (category.id === 'performance') { | ||
renderer = perfCategoryRenderer; | ||
} | ||
|
||
categories.appendChild(renderer.render(category, report.reportGroups)); | ||
} | ||
|
||
reportSection.appendChild(this._renderReportFooter(report)); | ||
|
||
return container; | ||
} | ||
|
||
/** | ||
* Place the AuditResult into the auditDfn (which has just weight & group) | ||
* @param {!Object<string, !ReportRenderer.AuditResultJSON>} audits | ||
* @param {!Array<!ReportRenderer.CategoryJSON>} reportCategories | ||
*/ | ||
static smooshAuditResultsIntoCategories(audits, reportCategories) { | ||
for (const category of reportCategories) { | ||
category.audits.forEach(auditMeta => { | ||
const result = audits[auditMeta.id]; | ||
auditMeta.result = result; | ||
}); | ||
} | ||
} | ||
} | ||
|
||
if (typeof module !== 'undefined' && module.exports) { | ||
|
@@ -181,6 +198,16 @@ if (typeof module !== 'undefined' && module.exports) { | |
self.ReportRenderer = ReportRenderer; | ||
} | ||
|
||
/** | ||
* @typedef {{ | ||
* id: string, | ||
* weight: number, | ||
* group: (string|undefined), | ||
* result: (ReportRenderer.AuditResultJSON|undefined) | ||
* }} | ||
*/ | ||
ReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. defined below (merge conflict, I think) |
||
|
||
/** | ||
* @typedef {{ | ||
* rawValue: (number|boolean|undefined), | ||
|
@@ -194,6 +221,7 @@ if (typeof module !== 'undefined' && module.exports) { | |
* scoringMode: string, | ||
* extendedInfo: Object, | ||
* error: boolean, | ||
* score: number, | ||
* details: (!DetailsRenderer.DetailsJSON|undefined), | ||
* }} | ||
*/ | ||
|
@@ -214,7 +242,6 @@ ReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions | |
* @typedef {{ | ||
* name: string, | ||
* id: string, | ||
* weight: number, | ||
* score: number, | ||
* description: string, | ||
* audits: !Array<!ReportRenderer.AuditJSON> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,9 +93,10 @@ class Runner { | |
const resultsById = {}; | ||
for (const audit of runResults.auditResults) resultsById[audit.name] = audit; | ||
|
||
let report; | ||
let categories; | ||
if (opts.config.categories) { | ||
report = Runner._scoreAndCategorize(opts, resultsById); | ||
Runner._scoreAndCategorize(opts, resultsById); | ||
categories = Object.values(opts.config.categories); | ||
} | ||
|
||
return { | ||
|
@@ -108,8 +109,7 @@ class Runner { | |
audits: resultsById, | ||
artifacts: runResults.artifacts, | ||
runtimeConfig: Runner.getRuntimeConfig(opts.flags), | ||
score: report ? report.score : 0, | ||
reportCategories: report ? report.categories : [], | ||
reportCategories: categories, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. before this defaulted to an empty array. Will it cause problems anywhere as |
||
reportGroups: opts.config.groups, | ||
}; | ||
}).catch(err => { | ||
|
@@ -192,7 +192,7 @@ class Runner { | |
* @param {{}} resultsById | ||
*/ | ||
static _scoreAndCategorize(opts, resultsById) { | ||
return ReportScoring.scoreAllCategories(opts.config, resultsById); | ||
ReportScoring.scoreAllCategories(opts.config, resultsById); | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,35 +13,49 @@ class ReportScoring { | |
* @return {number} | ||
*/ | ||
static arithmeticMean(items) { | ||
const results = items.reduce((result, item) => { | ||
const score = Number(item.score) || 0; | ||
const weight = Number(item.weight) || 0; | ||
return { | ||
weight: result.weight + weight, | ||
sum: result.sum + score * weight, | ||
}; | ||
}, {weight: 0, sum: 0}); | ||
const results = items.reduce( | ||
(result, item) => { | ||
// HACK. remove this in the next PR | ||
// Srsly. The score inconsitency has been very bad. | ||
let itemScore = item.score; | ||
if (typeof item.score === 'boolean') { | ||
itemScore = item.score ? 100 : 0; | ||
} | ||
|
||
const score = Number(itemScore) || 0; | ||
const weight = Number(item.weight) || 0; | ||
return { | ||
weight: result.weight + weight, | ||
sum: result.sum + score * weight, | ||
}; | ||
}, | ||
{weight: 0, sum: 0} | ||
); | ||
|
||
return (results.sum / results.weight) || 0; | ||
return results.sum / results.weight || 0; | ||
} | ||
|
||
/** | ||
* Returns the report JSON object with computed scores. | ||
* @param {{categories: !Object<string, {id: string|undefined, weight: number|undefined, audits: !Array<{id: string, weight: number|undefined}>}>}} config | ||
* @param {{categories: !Object<string, {id: string|undefined, weight: number|undefined, score: number|undefined, audits: !Array<{id: string, weight: number|undefined}>}>}} config | ||
* @param {!Object<string, {score: ?number|boolean|undefined, notApplicable: boolean, informative: boolean}>} resultsByAuditId | ||
* @return {{score: number, categories: !Array<{audits: !Array<{score: number, result: !Object}>}>}} | ||
*/ | ||
static scoreAllCategories(config, resultsByAuditId) { | ||
const categories = Object.keys(config.categories).map(categoryId => { | ||
const category = config.categories[categoryId]; | ||
for (const [categoryId, category] of Object.entries(config.categories)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎉 |
||
category.id = categoryId; | ||
|
||
const audits = category.audits.map(audit => { | ||
category.audits.forEach(audit => { | ||
const result = resultsByAuditId[audit.id]; | ||
// Cast to number to catch `null` and undefined when audits error | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should all of these line just be moved into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. more a comment for #4690 I suppose There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aye. It actually already happens in that PR: https://github.com/GoogleChrome/lighthouse/pull/4690/files#diff-488f5da33160a651526463bdb951e5deL31 |
||
let auditScore = Number(result.score) || 0; | ||
if (typeof result.score === 'boolean') { | ||
auditScore = result.score ? 100 : 0; | ||
// HACK removed in the next PR | ||
// While we'd like to do this boolean transformation happened on the auditDfn: | ||
// auditScore = result.score ? 100 : 0; | ||
// …the original result.score is untouched which means the smokehouse expectations will fail | ||
// We're officially rebaselining all those expectations in the next PR... | ||
// So for now, we'll keep keep both auditDfn.score and result.score boolean | ||
auditScore = result.score; | ||
} | ||
// If a result was not applicable, meaning its checks did not run against anything on | ||
// the page, force it's weight to 0. It will not count during the arithmeticMean() but | ||
|
@@ -53,15 +67,17 @@ class ReportScoring { | |
result.informative = true; | ||
} | ||
|
||
return Object.assign({}, audit, {result, score: auditScore}); | ||
result.score = auditScore; | ||
}); | ||
|
||
const categoryScore = ReportScoring.arithmeticMean(audits); | ||
return Object.assign({}, category, {audits, score: categoryScore}); | ||
}); | ||
|
||
const overallScore = ReportScoring.arithmeticMean(categories); | ||
return {score: overallScore, categories}; | ||
const scores = category.audits.map(audit => ({ | ||
score: resultsByAuditId[audit.id].score, | ||
weight: audit.weight, | ||
})); | ||
const categoryScore = ReportScoring.arithmeticMean(scores); | ||
// mutate config.categories[].score | ||
category.score = categoryScore; | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this make more sense up in
renderReport
before passing into_renderReport
? Then internally it'll always be the smooshed version and we won't have to remember not to usereportCategories
above this line