Skip to content

report: render sub-rows in their own tr#10553

Closed
connorjclark wants to merge 11 commits into
masterfrom
subrow-layout
Closed

report: render sub-rows in their own tr#10553
connorjclark wants to merge 11 commits into
masterfrom
subrow-layout

Conversation

@connorjclark

@connorjclark connorjclark commented Apr 7, 2020

Copy link
Copy Markdown
Collaborator

Part of #10369

Before this change, the sub rows columns didn't align well with each other, except in perfect conditions (short names, wide viewport). This PR makes it so each sub-row is rendered in its own tr element - the table layout does the alignment for us.

image

image

narrow:

image

@connorjclark connorjclark requested a review from a team as a code owner April 7, 2020 20:36
@connorjclark connorjclark requested review from paulirish and removed request for a team April 7, 2020 20:36
@connorjclark

Copy link
Copy Markdown
Collaborator Author

image

weird flake bro

@connorjclark connorjclark reopened this Apr 8, 2020
if (Array.isArray(values)) {
const subRowsElement = this._renderSubRows(values, subRowsHeading);
valueFragment.appendChild(subRowsElement);
if (!subRowHeadings.some(Boolean)) continue;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

did you just reinvent a if (!subRowHeadings.length) conditional? 😎

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

there is a return null; in the map

Comment thread lighthouse-core/report/html/renderer/details-renderer.js
Comment thread lighthouse-core/report/html/renderer/details-renderer.js Outdated
const tbodyElem = this._dom.createChildOf(tableElem, 'tbody');
for (const row of details.items) {
const rowElem = this._dom.createChildOf(tbodyElem, 'tr');
// Render the main row.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

can you split out into a _renderTableRow() method?


// All sub-row data arrays should be the same length, but just in case they are not,
// determine the longest data array and fill the missing values with an
// null pair (which creates an empty cell).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

adding nulls to create empty cells seems very weird to me.
its to maintain alignment with the parent headings right? but dont the parent headings already know what their subheadings are? can we use that relationship?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

background color only colors the width of the table if there is an element there.

@connorjclark connorjclark Apr 22, 2020

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Paul refactor to remove the nulls here: subrow-layout...subrow-layout-by-col

I think most of the complexity in your and my impl. comes from trying to pass the values to this _renderRow function. How about instead the fn uses a callback? I think it'd look like this:

_renderRow(headings, heading => row && row[heading.key])

for i in numSubRow:
  const subRowEl = _renderRow(headings, heading => row && Array.isArray(row[heading.key]) && row[heading.key][i])
  ...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah thats a nice solution. :)

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.

Peanut gallery: this seems like it's getting pretty involved for a static, self-generated table json of known width and height being turned into a <table> of known width and height. There's some table cruft we have to live with for legacy support, but subrows are brand new :)

Since (I don't believe) subrows are turned on anywhere yet (and we haven't shipped 6.0 yet regardless), maybe knowing more of the use case now gives a chance to step back and massage the data structure a bit to make dealing with them simpler? (#⁠5 on the classic https://users.ece.utexas.edu/~adnan/pike.html)

text-decoration: underline dotted #999;
}

.lh-sub-rows:not(:first-child) .lh-sub-row {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i kinda miss the previous styling.
i wonder if there's a way we can still have zebra striping on the non-subrows and the subrows feel like they belong.

... trying a few things out it seems impossible without way too much time. ah well.

@connorjclark connorjclark Apr 18, 2020

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

i kinda miss the previous styling.

I also prefer the previous styling. It's simple if the rendering code adds an "even" or "odd" class to all rows generated from a single data value. Then we just need to duplicate the CSS, one for both even / odd.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's simple if the rendering code adds an "even" or "odd" class to all rows generated from a single data value.

true! i don't mind that.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

done

Comment thread lighthouse-core/report/html/report-styles.css
Co-Authored-By: Paul Irish <paulirish@google.com>
subRowData.push({heading, value: values[i]});
}

const rowEl = this._renderRow(subRowData);

@paulirish paulirish Apr 22, 2020

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

just realized we got a renderTableRow() and a renderRow(). awkward.

maybe renderTableRow => buildTableItemRow ?


// All sub-row data arrays should be the same length, but just in case they are not,
// determine the longest data array and fill the missing values with an
// null pair (which creates an empty cell).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah thats a nice solution. :)

@paulirish

Copy link
Copy Markdown
Member

Awaiting brendan's typecheck magic for subRows. Will enable a new details subrow shape.

@paulirish

Copy link
Copy Markdown
Member

This was the draft data shape for subrows we discussed last time.

image

one subRows array (instead of multiple arrays).

@connorjclark

Copy link
Copy Markdown
Collaborator Author

might also go with subItems: [...]

@connorjclark

Copy link
Copy Markdown
Collaborator Author

@brendankenny do you have an update on the types? If you can't devote any time in the next couple weeks can you possibly share what you've done so far so that I could prioritize it?

@brendankenny

Copy link
Copy Markdown
Contributor

@brendankenny do you have an update on the types? If you can't devote any time in the next couple weeks can you possibly share what you've done so far so that I could prioritize it?

I was hoping to iterate on this more, but lots of things have gotten in the way :) Maybe this is good enough or you can find some ways to improve.

tl;dr: branch with audits and some of the tests converted to try out the proposal: master...subrowstraw

The biggest constraint is what we identified in the beginning: since TableItem has a generic index (so table columns can be named whatever they want to be), all item properties have to have the union of all possible item types as at least their base type (though individual properties can be constrained within that base type).

That means we can't have a unique subRows property with a unique form (if it has that form, every other TableItem property also has to be able to take that form). IIRC, @connorjclark this is what you originally considered and the downside is why you didn't pursue that idea.

The other constraint is for downstream renderers: ideally if a report renderer doesn't support subrows yet, they'll just silently be ignored.

So, possible approaches:

  1. Rather than having a TableItem = {[p: string]: undefined | ItemValue} indexed type, enumerate all possible table item properties. TableItem = {subRows?: TableSubRow[], url?: string, transferSize?: number, location?: SourceLocationValue, ...}. I tried this to see what it would look like and we already have an enormous number of these and would require either reusing existing item property names or adding to audit-details.d.ts every time a new audit table is added. Doable but fairly ridiculous? We really do want it to be an indexed type.

  2. Have a specific TableItems['subRows'] property with aTableSubRow[] type, but have to let any other TableItem property take a TableSubRow[] type, even if they're never that. Feels bad type-wise but can enforce that it's only a subRows property at runtime like we do with a few other aspects of details rendering.

  3. Let only a TableItems['subRows'] take a TableSubRow[], @ts-ignore all the places where this breaks the type system (let's not do this one :)

  4. Current solution on master with downsides discussed above

  5. Other solutions outside of items. e.g. maybe a parallel subItems, but would require bookkeeping of indices between which subItems are connected to which items.

After checking out (1) and not liking it, I tried out (2) and I'm reasonably happy. With this proposal, subrows become a bit more predictable (looking and acting exactly like items), easier to iterate on in the report renderer, and still have the desired property that renderers that don't support subrows yet will just ignore them by default. Downside is looseness of TableSubRows showing up in places we don't want it, but I don't think that's a big issue in practice.

Heart of things:

/** A table item for rows that are nested within a top-level TableItem (row). */
export interface TableSubRows {
  type: 'subrows';
  items: TableItem[];
}

/** Possible types of values found within table items. */
type ItemValue = string | number | boolean | DebugData | NodeValue | SourceLocationValue | LinkValue | UrlValue | CodeValue | TableSubRows;

export interface TableItem {
  debugData?: DebugData;
  subRows?: TableSubRows;
  [p: string]: undefined | ItemValue;
}

and an item with subrows would look something like

{
  "url": "http://example.com/example.js",
  "totalBytes": 71654,
  "wastedBytes": 30470,
  "wastedPercent": 42.52388078488413,
  "subRows": {
    "type": "subrows",
    "items": [{
        "sources": "...Control/assets/js/vendor/katex.min.js",
        "sourceBytes": 14773,
        "sourceWastedBytes": 500,
    }, {
      "//": "..."
    }],
  },
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants