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 v2: Basic table formatter #2019

Merged
merged 10 commits into from
May 4, 2017
Merged

Report v2: Basic table formatter #2019

merged 10 commits into from
May 4, 2017

Conversation

paulirish
Copy link
Member

It's not very pretty but the basics are there.

  • adds thumbnail renderer
  • adds URL renderer (just displays as text for now)
  • adds table renderer (obviously)
  • introduces AuditResult.details.itemHeaders for the thead cells

screenshot, lol:
image

@paulirish paulirish requested a review from ebidel April 15, 2017 05:22
@@ -179,14 +179,17 @@ class UnusedCSSRules extends Audit {
return UnusedCSSRules.mapSheetToResult(indexedSheets[sheetId], pageUrl);
}).filter(sheet => sheet && sheet.wastedBytes > 1024);


const headings = [
{key: 'url', itemType: 'url', text: 'URL'},
Copy link
Collaborator

Choose a reason for hiding this comment

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

we can override this per row correct? it'd be nice to finally have a nice solution for displaying the inline code preview in the url column

Copy link
Member Author

Choose a reason for hiding this comment

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

to have an individual row change the itemType? that seems a little weird. whyd we want diff itemtypes in the column?

Copy link
Collaborator

Choose a reason for hiding this comment

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

in all the css-based ones the "url column" doesn't always have a url and it's a code snippet preview instead since it's an inline style. it'd be nice to have the details renderer support that too so the code snippets can say {type: "code", content: ".inline-style {...}"} or something

Copy link
Contributor

@ebidel ebidel left a comment

Choose a reason for hiding this comment

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

Sweet!

totalKb: 'Original',
potentialSavings: 'Potential Savings',
}
headings,
Copy link
Contributor

Choose a reason for hiding this comment

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

The @return docstring of this method needs to be updated to match.

@@ -88,6 +116,33 @@ class DetailsRenderer {
return element;
}


/**
* @param {!DetailsRenderer.TableDetailsJSON} details
Copy link
Contributor

Choose a reason for hiding this comment

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

is TableDetailsJSON typedef'd anywhere? not seeing it.

const element = this._dom.createElement('img', 'lh-thumbnail');
element.src = obj.text.url;
// ignore obj.text.mimeType
element.alt = 'Image preview';
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we also add element.title = obj.text.url; so there's a full url when users hover over the image?

Copy link
Member Author

Choose a reason for hiding this comment

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

sg

* @return {!Element}
*/
_renderTable(details) {
const element = this._dom.createElement('details', 'lh-details', {open: true});
Copy link
Contributor

Choose a reason for hiding this comment

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

should we open by default? There could be a lot of stuff in the table.

Copy link
Member Author

Choose a reason for hiding this comment

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

nah. was only for development. will remove. :)

element.appendChild(this._dom.createElement('summary')).textContent = details.header;
}

const tableElem = element.createChild('table', 'lh-table lh-table__multicolumn');
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you need lh-table__multicolumn? That was an impl detail of v1's handlebars table.

Copy link
Member Author

Choose a reason for hiding this comment

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

nope. will nuke.

* @return {!Element}
*/
self.Element.prototype.createChild = function(elementName, className) {
const element = instance.createElement(elementName, className);
Copy link
Contributor

Choose a reason for hiding this comment

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

pipe through the attrs param too?

Copy link
Member Author

Choose a reason for hiding this comment

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

i dont think we can because in devtools this method will already exist and it doesnt do attrs

self.Element.prototype.createChild = function(elementName, className) {
const element = instance.createElement(elementName, className);
this.appendChild(element);
return element;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: return this.appendChild(element);

* @param {string=} className
* @return {!Element}
*/
self.Element.prototype.createChild = function(elementName, className) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're comfortable with modifying built in prototypes, then I have a bunch of other helpers to add :)

Copy link
Member Author

Choose a reason for hiding this comment

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

check out https://github.com/ChromeDevTools/devtools-frontend/blob/master/front_end/dom_extension/DOMExtension.js .. we can polyfill whatever from there we want. (or easier: just load that file in the report)

--image-preview: 24px;
}

img.lh-thumbnail {
Copy link
Contributor

Choose a reason for hiding this comment

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

just .lh-thumbnail? Seems ok to be tag-agnostic.

* @param {string=} className
* @return {!Element}
*/
self.Element.prototype.createChild = function(elementName, className) {
Copy link
Member

Choose a reason for hiding this comment

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

why not just make this a method on DOM? We add five characters (DOM and a , ) but avoid all the usual downsides of doing this. Also has the benefit that Closure will understand it when not compiled with devtools, we can more easily test it, etc

@@ -57,6 +57,60 @@ class Audit {
}

/**
* @param {!Audit.Headings} headings
* @return {*}
Copy link
Member

@brendankenny brendankenny Apr 20, 2017

Choose a reason for hiding this comment

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

@return {!Array<string>}?


/**
* @param {!Audit.Headings} headings
* @param {!Object} results
Copy link
Member

@brendankenny brendankenny Apr 20, 2017

Choose a reason for hiding this comment

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

hmm, definitely not an object. {!Array<!Object<string, string>>}? It's hard to follow the old createTable helper

@@ -57,6 +57,60 @@ class Audit {
}

/**
* @param {!Audit.Headings} headings
Copy link
Member

Choose a reason for hiding this comment

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

just one space (here and elsewhere). Is this a vscode setting that needs to be changed?

Copy link
Member Author

Choose a reason for hiding this comment

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


/**
* @param {!Audit.Headings} headings
* @return {!Array<!DetailsRenderer.DetailsJSON>}
Copy link
Member

Choose a reason for hiding this comment

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

this is fine for now, but it won't actually work if we were to turn on type checking on this file. Closure won't be able to see the typedef since it's in another module. We'll have to think of how we want to do that (move report typedefs to their own file?)


/**
* @param {!Audit.Headings} headings
* @param {!Object} results
Copy link
Member

Choose a reason for hiding this comment

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

same type as above results

*/
Audit.Heading; // eslint-disable-line no-unused-expressions

/** @typedef {!Array<!Audit.Heading>} */
Copy link
Member

Choose a reason for hiding this comment

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

is it worth making a typedef for this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually yeah. Basically we pass around this typedef and never use the Audit.Heading one directly. Not entirely worth it to nuke this guy, i tried.

Copy link
Member

Choose a reason for hiding this comment

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

if that's the case, what about

/** @typedef {
 * !Array<{
 *   key: string,
 *   itemType: string,
 *   text: string,
 * }>}
 */
Audit.Headings;

and skip Audit.Heading?

*/
_renderThumbnail(obj) {
const element = this._dom.createElement('img', 'lh-thumbnail');
element.src = obj.text.url;
Copy link
Member

Choose a reason for hiding this comment

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

this isn't a thing on DetailsJSON

const tableRows = results.map(item => {
return headings.map(heading => {
return {
type: heading.itemType,
Copy link
Member

Choose a reason for hiding this comment

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

why not do this in _renderTable rather than in the JSON report?

Copy link
Member Author

Choose a reason for hiding this comment

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

shrugz

I'm okay with either.
patrick is fine with either but thinks its slightly better to have all things coming into renderer be our details objects
dgozman also feels the same way, and thinks that if we wanted to conserve the size of the LH object we should shorten type to t etc. ;) ;)

so I'm comfortable expanding the objects over here before we deliver the LHR.

Copy link
Member

Choose a reason for hiding this comment

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

so I'm comfortable expanding the objects over here before we deliver the LHR.

I think with our less rigid details taxonomy now I'm actually more OK with this extra verboseness :)

We can keep an eye on report file size and how much is taken up by "type": "text" and figure out some defaults (default to text or default to header itemType) if/when needed

Copy link
Member Author

@paulirish paulirish left a comment

Choose a reason for hiding this comment

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

note from sync: default item details type should be text, handled as such in the details-renderer

@@ -57,6 +57,60 @@ class Audit {
}

/**
* @param {!Audit.Headings} headings
Copy link
Member Author

Choose a reason for hiding this comment

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

@ebidel
Copy link
Contributor

ebidel commented May 1, 2017

@paulirish how's this coming? We've got a lot of audits that need this guy.

@paulirish
Copy link
Member Author

@paulirish how's this coming? We've got a lot of audits that need this guy.

roger that. on it.

@paulirish paulirish force-pushed the table branch 2 times, most recently from bc73833 to 160e6ef Compare May 1, 2017 23:44
@paulirish
Copy link
Member Author

PTAL. updated.

const tableRows = results.map(item => {
return headings.map(heading => {
const value = item[heading.key];
if (typeof value === 'object' && value.type) return value;
Copy link
Collaborator

Choose a reason for hiding this comment

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

🎉

@@ -424,4 +424,14 @@ summary.lh-passed-audits-summary::-webkit-details-marker {
}
}

.lh-table {
--image-preview: 24px;
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: --image-preview-size or something?

Copy link
Contributor

@ebidel ebidel left a comment

Choose a reason for hiding this comment

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

Couple of things I want to tweak, but this is LGTM to get in.

@ebidel
Copy link
Contributor

ebidel commented May 3, 2017

@brendankenny to you

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

reviewed!

* type: string,
* header: ({text: string}|undefined),
* items: !Array<!Array<!DetailsRenderer.DetailsJSON>>,
* itemHeaders: !Array<!DetailsRenderer.DetailsJSON>
Copy link
Member

Choose a reason for hiding this comment

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

by letting these be DetailsJSON we're basically descending into recursive madness again since in theory when you call this.render on them they could embed a whole other table (but of course, our css would quickly fall over itself if that sort of thing occurred). This leaves these basically untyped (except our assurance to the compiler that we're correct when we cast in this.render)

I'm not sure of the right solution...maybe we could separate top level rendering (anything currently in this.render) from more primitive-level rendering (just text, url, and thumbnail as of this PR) in a future PR or something, but we would need to make whatever we did non-annoying.

Copy link
Member Author

Choose a reason for hiding this comment

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

we're basically descending into recursive madness again

are we though?

detailsjson only has type and text.. it can't contain other types.
really it and thumbnail should include the word "item" as they are ALWAYS leaf nodes.

and the render() method should accept all of these leaf & group types. the real bug seems that render's param is typed to a leaf node when we know its not.

Copy link
Member

Choose a reason for hiding this comment

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

detailsjson is the base (structural) type that is the superset of all of them. It only has fields all of them have: type and optional text (really text is there from when they all had text and should probably be removed). render() takes that and casts according to its type to the appropriate type, and we'll take responsibility to make sure that if type is set to x, then that object really is of type x. It's a hack, but simple (we only have to have humans in the loop checking the top level details object), and can easily be reworked once we know the full outlines of the types we need.

Allowing arbitrary nesting means that it's much less simple since we have to guarantee that all the nested items and itemHeaders are really whatever their type says. It also means we're implicitly breaking the no-typedef-cycles, just getting around it by casting, which is what I meant by recursive madness.

If you're saying we should have a different type explicitly for leaf nodes to make this better, I think that's great

Copy link
Member Author

Choose a reason for hiding this comment

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

ya makes sense. let's sort this out in voice as i have a few ideas.

* @return {!Element}
*/
_renderThumbnail(value) {
if (/^image/.test(value.mimeType) === false) {
Copy link
Member

Choose a reason for hiding this comment

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

make a note of this in a jsdoc description?

Copy link
Member Author

Choose a reason for hiding this comment

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

done


const element = this._dom.createElement('img', 'lh-thumbnail');
element.src = value.url;
element.alt = 'Image preview';
Copy link
Member

Choose a reason for hiding this comment

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

best we can do?

Copy link
Member Author

Choose a reason for hiding this comment

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

was matching our v1. :) i changed it to empty string, which seems better, actually.

@@ -80,8 +81,7 @@ class UsesResponsiveImages extends ByteEfficiencyAudit {

/**
* @param {!Artifacts} artifacts
* @return {{results: !Array<Object>, tableHeadings: Object,
* passes: boolean=, debugString: string=}}
* @return {{results: !Array<Object>, headings: !Audit.Headings, passes: boolean=, debugString: string=}}
Copy link
Member

Choose a reason for hiding this comment

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

boolean= would be (boolean|undefined), but doesn't look like it can be undefined?
string= would be (string|undefined)

(= only works for @params...technically it means optional param, which just so happens to manifest as being undefined)

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks. made a new typdef for this guy since he was everywhere.

@@ -46,7 +46,7 @@ class ResponsesAreCompressed extends ByteEfficiencyAudit {
/**
* @param {!Artifacts} artifacts
* @param {number} networkThroughput
* @return {!AuditResult}
* @return {{results: !Array<Object>, passes: boolean=, headings: !Audit.Headings, debugString: string=}}
Copy link
Member

Choose a reason for hiding this comment

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

see below

@@ -63,8 +63,7 @@ class UsesOptimizedImages extends ByteEfficiencyAudit {

/**
* @param {!Artifacts} artifacts
* @return {{results: !Array<Object>, tableHeadings: Object,
* passes: boolean=, debugString: string=}}
* @return {{results: !Array<Object>, headings: !Audit.Headings, passes: boolean=, debugString: string=}}
Copy link
Member

Choose a reason for hiding this comment

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

samsies

@@ -95,8 +96,7 @@ class OffscreenImages extends ByteEfficiencyAudit {

/**
* @param {!Artifacts} artifacts
* @return {{results: !Array<Object>, tableHeadings: Object,
* passes: boolean=, debugString: string=}}
* @return {{results: !Array<Object>, headings: !Audit.Headings, debugString: string=}}
Copy link
Member

Choose a reason for hiding this comment

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

see notes below

*/
Audit.Heading; // eslint-disable-line no-unused-expressions

/** @typedef {!Array<!Audit.Heading>} */
Copy link
Member

Choose a reason for hiding this comment

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

if that's the case, what about

/** @typedef {
 * !Array<{
 *   key: string,
 *   itemType: string,
 *   text: string,
 * }>}
 */
Audit.Headings;

and skip Audit.Heading?

const tableRows = results.map(item => {
return headings.map(heading => {
const value = item[heading.key];
if (typeof value === 'object' && value.type) return value;
Copy link
Member

Choose a reason for hiding this comment

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

results values might not just be a string, then?

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

@ebidel ebidel deleted the table branch May 4, 2017 21:30
paulirish added a commit to paulirish/lighthouse that referenced this pull request May 5, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants