Skip to content

Report v2: Basic table formatter#2019

Merged
paulirish merged 10 commits intomasterfrom
table
May 4, 2017
Merged

Report v2: Basic table formatter#2019
paulirish merged 10 commits intomasterfrom
table

Conversation

@paulirish
Copy link
Copy Markdown
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


const headings = [
{key: 'url', itemType: 'url', text: 'URL'},
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.



/**
* @param {!DetailsRenderer.TableDetailsJSON} details
Copy link
Copy Markdown
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.

_renderThumbnail(obj) {
const element = this._dom.createElement('img', 'lh-thumbnail');
element.src = obj.text.url;
// ignore obj.text.mimeType
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor

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

}

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

@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
Copy link
Copy Markdown
Contributor

@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

});
}

/**
Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy link
Copy Markdown
Contributor

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
Copy link
Copy Markdown
Contributor

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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
Contributor

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?

* @return {!Element}
*/
_renderThumbnail(obj) {
const element = this._dom.createElement('img', 'lh-thumbnail');
Copy link
Copy Markdown
Contributor

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

static makeV2TableRows(headings, results) {
const tableRows = results.map(item => {
return headings.map(heading => {
return {
Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
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

});
}

/**
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Member Author

PTAL. updated.

static makeV2TableRows(headings, results) {
const tableRows = results.map(item => {
return headings.map(heading => {
const value = item[heading.key];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🎉

}

.lh-table {
--image-preview: 24px;
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor

ebidel commented May 3, 2017

@brendankenny to you

Copy link
Copy Markdown
Contributor

@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
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
Contributor

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
Copy Markdown
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.

* @param {!DetailsRenderer.ThumbnailDetails} value
* @return {!Element}
*/
_renderThumbnail(value) {
Copy link
Copy Markdown
Contributor

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
Copy Markdown
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;
Copy link
Copy Markdown
Contributor

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
Copy Markdown
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.

* @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
Copy Markdown
Contributor

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
Copy Markdown
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.

* @param {!Artifacts} artifacts
* @param {number} networkThroughput
* @return {!AuditResult}
* @return {{results: !Array<Object>, passes: boolean=, headings: !Audit.Headings, debugString: string=}}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

see below

* @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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

samsies

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

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
Copy Markdown
Contributor

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?

static makeV2TableRows(headings, results) {
const tableRows = results.map(item => {
return headings.map(heading => {
const value = item[heading.key];
Copy link
Copy Markdown
Contributor

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
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixed

@paulirish paulirish merged commit 8652478 into master May 4, 2017
@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