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: header, footer, leftnav, share, oh my #2000

Merged
merged 34 commits into from
Apr 28, 2017
Merged

Report: header, footer, leftnav, share, oh my #2000

merged 34 commits into from
Apr 28, 2017

Conversation

ebidel
Copy link
Contributor

@ebidel ebidel commented Apr 12, 2017

R: all

  • Adds the header back to the report, redesigned to match the devtools mocks. The "Runtime settings" dropdown wasn't mocked.
  • Centers the report and gives it a max width for easier reading. I like it, but we can keep the justified left.
  • support for multiple calls to renderReport(e.g. future where we have re-run audit)
  • responsive tweaks
  • Logger is now a toast at the bottom of the page
  • also adds test for some of our helpers.

Most of the code is porting. I'll try to annotate what's what.

screen shot 2017-04-12 at 3 36 10 pm

screen shot 2017-04-12 at 3 36 19 pm

@ebidel ebidel changed the title Report: header, footer Report: header, footer, share, oh my Apr 12, 2017
* Logs messages via a UI butter.
* @class
*/
class Logger {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is a straight port from v1.

/**
* @param {!Document} document
*/
constructor(document) {
Copy link
Contributor Author

@ebidel ebidel Apr 12, 2017

Choose a reason for hiding this comment

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

this is a port from v1. Only difference is that the constructor does not automatically attache event listeners. You have to call attach() now to add the functionality to the DOM.

*/
onCopyButtonClick() {
if (window.ga) {
window.ga('send', 'event', 'report', 'copy');
Copy link
Contributor Author

@ebidel ebidel Apr 12, 2017

Choose a reason for hiding this comment

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

Would love to keep GA for viewer. Should be fine since this is a noop for contexts that don't load ga.

Copy link
Member

Choose a reason for hiding this comment

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

can we just remove for now since it's non-functional code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's always been functional in viewer (only context that loads GA atm).

// const reportGenerator = new ReportGeneratorV2();
// htmlStr = reportGenerator.generateReportHTML(this.json);
// }
// TODO: fix viewer.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Need to make this one work again.

Copy link
Collaborator

Choose a reason for hiding this comment

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

when we do, instead of generating it on the fly I'd be interested to publish the bundled JS with our releases instead that the viewer can just reference. This way the viewer can actually start supporting all versions going forward too

* @param {string} date
* @return {string}
*/
function formatDateTime(date) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

port from v1

*/
renderReport(report) {
renderReport(report, container) {
container.innerHTML = ''; // Remove previous report.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm hoping this one isn't contentious :)

document.body.appendChild(renderer.renderReport(window.__LIGHTHOUSE_JSON__));
window.addEventListener('DOMContentLoaded', _ => {
window.renderer = new ReportRenderer(document);
const container = document.querySelector('#lighthouse-content');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

made sense to render into a container rather than replace the whole world for every call to render.


<!-- Lighthouse header -->
<template id="tmpl-lighthouse-heading">
<style>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For these one-time-use partials....instead of creating separate .css file for each (or adding to the main stylesheet), I chose to keep related styles together with the markup that requires them. One nice thing about this is that these styles don't get added to the page unless a renderer uses the template. So if devtools renders its own header, we're not penalized.

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

I know this is mostly ports, but there's a lot going on without any tests :(

* @return {!DocumentFragment} A clone of the template content.
* @throws {Error}
*/
cloneTemplate(selector) {
const template = this._document.querySelector(selector);
cloneTemplate(selector, context = this._document) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nice

Copy link
Member

Choose a reason for hiding this comment

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

i have a conflict with this in flight: https://github.com/GoogleChrome/lighthouse/pull/2002/files#diff-67023326fb4e20403076afc5d18d67f2R51

I can adopt yours pretty easily though.

Copy link
Member

Choose a reason for hiding this comment

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

are you comfortable with using this.templatesContext instead?

cloneTemplate(selector) {
  const template = this.templatesContext.querySelector(selector);

this would be ideal for me. I'd set it right after instantiating the instance.
it would default to this._document

Copy link
Member

Choose a reason for hiding this comment

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

i have this implemented in #2002.
wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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


'use strict';

/* eslint-env browser */
Copy link
Collaborator

Choose a reason for hiding this comment

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

I've been avoiding setting this and preferring explicit globals instead, so when we test in node it's really explicit which globals it depends on, thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, we ca do explicit globals.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// const reportGenerator = new ReportGeneratorV2();
// htmlStr = reportGenerator.generateReportHTML(this.json);
// }
// TODO: fix viewer.
Copy link
Collaborator

Choose a reason for hiding this comment

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

when we do, instead of generating it on the fly I'd be interested to publish the bundled JS with our releases instead that the viewer can just reference. This way the viewer can actually start supporting all versions going forward too

month: 'numeric', day: 'numeric', year: 'numeric',
hour: 'numeric', minute: 'numeric', timeZoneName: 'short'
};
let formatter = new Intl.DateTimeFormat('en-US', options);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe there's an issue outstanding for our permanent use of en-US, since we're starting fresh, thoughts on just using .toISOString() for now and not bringing our baggage with us yet? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The devtools mocks make this even nicer:

screen shot 2017-04-13 at 1 43 47 pm

What if we go with that?

Copy link
Collaborator

Choose a reason for hiding this comment

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

sgtm!

* @param {Blob|File} blob The file to save.
*/
_saveFile(blob) {
const filename = window.getFilenamePrefix({
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should really get around to fixing getFilenamePrefix haha

@ebidel
Copy link
Contributor Author

ebidel commented Apr 13, 2017

Working on some tests...

@ebidel
Copy link
Contributor Author

ebidel commented Apr 13, 2017

Added tests for the non-interactive stuff (newer header/footer renders). Also added tests for some of the format helpers.

@@ -44,11 +44,12 @@ class DOM {

/**
* @param {string} selector
* @param {Element=} context Parent node to query for templates within.
Copy link
Member

Choose a reason for hiding this comment

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

{!Element=}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,71 @@
/**
Copy link
Member

Choose a reason for hiding this comment

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

does devtools want a logger? This PR might need to adjust to the model in #2002

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IDK. I'm just porting :)

Knowing the separation of LH proper and integrations of LH is still unclear. Right now the operating assumption is that we're keeping everything we currently have b/c #1937 doesn't capture these types of details.

For me, the dream is that there will be all sorts of existing tools that adopt a LH report. It's impossible to foresee all those integration touch points. Rather than designing our system around a particular integration , it might be easier to flip it. IOW, the integration (e.g. devtools) decides what parts of LH it wants to throw away or replace. Removing handlebars, adding templateContext are a step towards that.

For the features stuff, I've updated it to take the approach in b12f2ea. Devtools can decide not to load the feature (including logger).


Some open thoughts/questions for everyone. "Integration X" can be substituted for "devtools".

  • The core report should contain the features we want to show up in the most places. For example, if you run LH in the CLI/extension/WPT/Gulliver/LHaaS, you'll get the UI we vend (logger, export, etc.).
  • What happens if you generate an html report in Integration X and open it later? Do you see LH's base UI or their version?
  • What happens if you open that same report in Viewer?
  • CSS is the easiest way to get rid of base UI: #lh-log { display: none}. We're already doing that today.
    • Alternatively, Integration X loads its own stylesheets and effectively changes the entire report.
  • If you also want to nuke code, don't inject ReportUIFeature (here), or stub files: class Logger { log() {} }. We could even provide those files for people if it makes sense.

@@ -0,0 +1,272 @@
/**
Copy link
Member

Choose a reason for hiding this comment

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

same for this file and devtools. Is there stuff in here that won't be wanted there?


/* globals document window URL Blob Logger */

class ReportFeatures {
Copy link
Member

Choose a reason for hiding this comment

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

definitely need a better name. Might as well call it ReportThings while you're at it :P ReportUIFeatures? I don't know, but if I wanted to know where copy/paste was handled it definitely wouldn't be here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ReportUIFeatures sgtm.


/**
* Adds export button and print functionality to the report.
* @param {ReportJSON=} report
Copy link
Member

Choose a reason for hiding this comment

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

{!ReportJSON} (I don't think it's optional?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not anymore without viewer. Done.

}
}

warn(msg) {
Copy link
Member

Choose a reason for hiding this comment

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

need jsdoc on these

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

* @return {!Element}
*/
_renderReport(report) {
const element = this._dom.createElement('div', 'lh-report');
const container = this._dom.createElement('div', 'lh-content');
Copy link
Member

Choose a reason for hiding this comment

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

there's a .lh-content div inside a #lh-content div? Or is this a old code? Appears to be discarded since element is returned and attached elsewhere in the DOM

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea old code. Removed.

/**
* Click handler for export button.
*/
onExportButtonClick(e) {
Copy link
Member

Choose a reason for hiding this comment

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

need jsdoc on these es

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

*/
onCopyButtonClick() {
if (window.ga) {
window.ga('send', 'event', 'report', 'copy');
Copy link
Member

Choose a reason for hiding this comment

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

can we just remove for now since it's non-functional code?

case 'copy':
this.onCopyButtonClick();
break;
case 'open-viewer':
Copy link
Member

Choose a reason for hiding this comment

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

remove?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Disabled this in the UI for now. Left in the code.

Copy link
Member

Choose a reason for hiding this comment

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

Disabled this in the UI for now. Left in the code

this is classic YAGNI, though. If usage doesn't remain the same, there's double duty of removing old implementation and adding the new one. Even if usage remains exactly the same, non-functional code and out-of-date comments are a maintenance burden. Meanwhile a pristine copy is over in report v1 ready for copy/paste when that happens.

@patrickhulce
Copy link
Collaborator

rebased for ya @ebidel, biggest change was extracting the format helpers to a new file

@paulirish
Copy link
Member

paulirish commented Apr 22, 2017

Alright, brendan and I walked through the features and components in this PR and identified how we'll handle for the devtools case.

🎅 Will include and use as-is in DevTools

  • print report (w/ details expanding behavior)
  • download HTML
  • download JSON
  • copy JSON to clipboard
  • header
  • header's runtime setting
  • footer

🤶 Will include, but subclass in DevTools

  • _saveFile - DT has its own way to save files to disk, so we'll override for that.
  • the Share menu component - devtools has a native UI.ContextMenu component that we can use. If you expose this sort of API, we can maximize overlap:
contextMenu.appendItem(menuItemTitleStr, handlerFn);
// ... later on ...
contextMenu.show();

I think this also means we don't have declarative menu items over in .lh-export .export__dropdown in the template. They should be added at runtime.

🙍 Will not include in devtools right now

  • Share to viewer - I'd like to defer adding this to devtools, as I don't want to walk through the security concerns right now. We could explore in 2 months. Can we separate out this into a new file? (Maybe then reportFeatures exposes a registerFeature method that will end up calling contextMenu.appendItem etc).
  • Logger toasts component - Proposal: emit events from reportFeatures.. like dispatchEvent of CustomEvent on the .lh-report dom element. The standalone logger component will addEventListener for "lh-error" events.. In DevTools, we'll also listen but just send to console. Hows that sound?
  • Google Analytics - (the ga.send calls) - Same idea as above, but probably an event called "lh-telemetry" or whatever. Looks like objects can be attached to the detail property of the event.

actual action item summary (because so many words (sorry)):

  • move all share-to-viewer stuff to separate file
  • extract all logger mentions from report-features and adopt event-based API for logger
  • remove ga.send mentions from report-features and dispatch events for those.
  • refactor the .lh-export dropdown to add items at runtime.

aside from these bits, i'm happy with the rest and looking forward to some nice looks on the report! :)

@paulirish
Copy link
Member

then no problem as static methods? It has the benefit of making the global and the export parallel, too. But again, testing implementation details isn't all that useful. Asserting that calculateRating(10) returns 'fail' means that a refactor of how scoring is calculated has to update tests, even if the refactor maintains unchanged scoring thresholds in the actual report. The tests are already using sampleResults; would it be feasible to pull out the scores of a report generated from those and test for how they're marked?

I'm also supportive of putting this on as static methods.
I'd also prefer this vs adding the new format-helpers.js file. 🐈

@brendankenny
Copy link
Member

turning on closure checking produced a number of errors in here, some legitimate, some from just things like document.execCommand's return type (apparently) being implementation specific, so the default externs don't have a type for them. I can help sort them out on Monday.

@ebidel
Copy link
Contributor Author

ebidel commented Apr 26, 2017

@brendankenny I need your help with the remaining closure issues. Casting didn't take care of document.execCommand or the MessageEvent stuff.

@paulirish

move all share-to-viewer stuff to separate file

I've added a few other methods that can be overridden. See if that works. See example. below.

Instead of worrying about potential (and future) security concerns, Lighthouse should really just go through a full security review before we launch DT. There are more questionable areas of the codebase than opening a tab and sending JSON to it :)

I think it's also good to keep features that have already landed in the project. If you really want to omit features from DT, can the DT integration just remove them?

extract all logger mentions from report-features and adopt event-based API for logger

Easy to do already. See example below.

remove ga.send mentions from report-features and dispatch events for those.

Done! Although, kinda silly that something trivial has turned into something more complex. But hopefully we can use _fireEventOn for more stuff?

Before

if (self.ga) {
  self.ga('send', 'event', 'report', 'copy');
}

After

/**
 * Fires a custom DOM event on target.
 * @param {string} name Name of the event.
 * @param {!Document|!Element} target DOM node to fire the event on.
 * @param {Object<{detail: Object<string, *>}>=} detail Custom data to include.
 */
_fireEventOn(name, target = this._document, detail) {
  const event = new CustomEvent(name, detail ? {detail} : null);
  this._document.dispatchEvent(event);
}

// Elsewhere in the app.
document.addEventListener('lh-analytics', e => {
  if (self.ga) {
    self.ga(e.detail.cmd, e.detail.fields);
  }
});

refactor the .lh-export dropdown to add items at runtime.

In the example. Although, the UI.ContextMenu() in devtools looks like it renders a system UI menu. Not sure if that's what you want or not.

screen shot 2017-04-25 at 8 47 21 pm


Ok, so here was my thinking for the feature stuff. Choosing what features go in and out of DT is going to be really adhoc. The code separation is only going to get more confusing. What I did with ReportUIFeatures was to design everything to be overridable. DT can override methods as it needs, or even swap in an entirely new implementation. Either way, everything gets DI'd into ReportRenderer and it JustWorks.

Proof is in the pudding. All of the tweaks suggested above can be done like so:

class ReportUIFeaturesDevTools extends ReportUIFeatures {
  /**
   * Routes Logger to console instead of the UI toast.
   * @override
   */
  _setupLogger() {
    this.logger = console;
    this.logger.hide = function() {};
  }

  // @override
  initFeatures(...args) {
    super.initFeatures(...args);
    // ... devtools adds more stuff at runtime ...
  }

  // @override
  _setupExportButton() {
    super._setupExportButton();

    // ... devtools uses its API to build the context menu.
    // const dropdown = this._dom.find('.lh-export__dropdown', this._document);
    // dropdown.textContent = '';
    // const menu = UI.ContextMenu();
    // menu.appendItem('Save HTML', this.);
    // menu.appendItem('Item 2', function handler {});
    // menu.appendItem('Item 3', function handler {});
  }

  // @override
  _saveFile(blob) {
    // ... devtools calls its own API ...
  }

  // @override
  sendJSONReport() {
    // super.sendJSONReport = null; // nuke it if you're paranoid.
    console.warn('DT: not implemented yet');
  }

  // @override
  onExport(e) {
    // ... devtools does its own thing ...
  }
}

@patrickhulce
Copy link
Collaborator

hahaha these commits bring me so much joy 😂
image

@ebidel ebidel mentioned this pull request Apr 27, 2017
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.

this is a really long PR :( We should split things up next time


/* globals self URL Blob Logger CustomEvent */

class ReportUIFeatures {
Copy link
Member

Choose a reason for hiding this comment

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

should move this file to report-ui-features.js

@@ -0,0 +1,80 @@
/*
* Copyright 2015 The Closure Compiler Authors.
Copy link
Member

Choose a reason for hiding this comment

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

2015?

function formatNumber(number) {
return number.toLocaleString(undefined, {maximumFractionDigits: 1});
}
/* globals self, formatNumber, calculateRating */
Copy link
Member

Choose a reason for hiding this comment

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

this global/module.exports thing is increasingly difficult to follow. If the export is just for jsdom and tests, I wonder if we should just do a proxied require for testing the report

@@ -50,7 +50,7 @@ class DOM {

/**
* @param {string} selector
* @param {!Document|!Element} context
* @param {!Document|!DocumentFragment|!Element} context
Copy link
Member

Choose a reason for hiding this comment

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

should we just switch this to {!Node}?

@@ -113,7 +113,7 @@ class DOM {
* Guaranteed context.querySelector. Always returns an element or throws if
* nothing matches query.
* @param {string} query
* @param {!DocumentFragment|!Element} context
* @param {!Document|!DocumentFragment|!Element} context
Copy link
Member

Choose a reason for hiding this comment

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

would {!Node} work? (and for findAll, below)

self.ReportUIFeatures = ReportUIFeatures;

/** @type {function({url: string, generatedTime: string}): string} */
self.getFilenamePrefix; // eslint-disable-line no-unused-expressions
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this already declared in the global scope?

Copy link
Member

Choose a reason for hiding this comment

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

oh, I see the issue, file-namer.js needs to be added to the closure compiled files list (and should use this one's {url: string, generatedTime: string} instead of Object like in that file)

}

return /** @type {!Element} **/ (element);
Copy link
Member

Choose a reason for hiding this comment

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

can also split into element = this._renderReport(report); container.appendChild(element); lines if you don't want to cast

Copy link
Member

Choose a reason for hiding this comment

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

actually, does this even need to return anything anymore? Return value isn't being used in report-template.html anymroe, and if adding to the container now, not even needed for testing

url.textContent = report.url;

const env = this._dom.find('.lh-env__items', header);
report.runtimeConfig.environment.forEach(runtime => {
Copy link
Member

Choose a reason for hiding this comment

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

s/runtime/env, maybe? (would need to rename env above to envContainer or whatever)

});

document.addEventListener('lh-analytics', e => {
if (self.ga) {
Copy link
Member

Choose a reason for hiding this comment

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

no need for self, I don't think

* @param {...?} var_args
* @return {?}
*/
function ga(methodName, var_args) {}
Copy link
Member

Choose a reason for hiding this comment

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

I don't think putting functions on this function will actually work. Is the rest of this file needed right now?

Definitely should avoid unknown type and do ...* for var_args and no return type if it's not needed. Right now the second argument is being used as !Object<string, *> and not any more arguments after that. Is that better?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I took this entire thing from closure's common externs.

@brendankenny
Copy link
Member

we definitely need some sanity around dependency injection vs overriding. There's basically no help right now on how to use stuff or how stuff works in these report files except reading all of them. Things that are injected need stuff that's overridden and vice versa, so some clarity with very clear decoupling boundaries would be amazing

@brendankenny
Copy link
Member

(that would be through a group effort, not expecting you to solve this eric :)

@brendankenny
Copy link
Member

move all share-to-viewer stuff to separate file

I've added a few other methods that can be overridden. See if that works. See example. below.

Instead of worrying about potential (and future) security concerns, Lighthouse should really just go through a full security review before we launch DT. There are more questionable areas of the codebase than opening a tab and sending JSON to it :)

even easier would be #2000 (comment) :) viewer code doesn't work, it's self contained, and can easily be added once we figure out viewer and get it working

Copy link
Member

@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.

okay eric and i just discussed this over chat. the summary:

  1. move logger to reuse the customEvent stuff.. logging "lh-log" events (or whatever) that any one with addEventListener can listen to.
  2. report-helpers -> util.js and install on a UTIL.namespace. self.UTIL = UTIL assignment at the bottom. We discussed a better future where these formatting methods are exposed on DetailsRenderer via a formatItem() with type == 'datetime' or 'number'.. but let's move ahead with UTIL in the meantime and refactor afterwards.
  3. nuke viewer stuff from report-features

with those 3 items taken care of i'm good and we can move ahead.

@ebidel
Copy link
Contributor Author

ebidel commented Apr 28, 2017

Changes are in and we're 💚 .

Dropping the mic 🎤 on this one. Any other changes, ya'll can do in followup prs.

@paulirish
Copy link
Member

merging to unblock the merge of other stuff.
thanks for the 2w slog on this monster, everyone.
💟

@paulirish paulirish merged commit fd507b3 into master Apr 28, 2017
@paulirish paulirish deleted the tablesclear branch April 28, 2017 21:09
brendankenny added a commit that referenced this pull request Apr 28, 2017
also update closure for mid-month release
paulirish pushed a commit that referenced this pull request Apr 29, 2017
also update closure for mid-month release
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