diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..72df373 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,20 @@ +# unconventional js +/blueprints/*/files/ +/vendor/ + +# compiled output +/dist/ +/tmp/ + +# dependencies +/bower_components/ +/node_modules/ + +# misc +/coverage/ +!.* + +# ember-try +/.node_modules.ember-try/ +/bower.json.ember-try +/package.json.ember-try diff --git a/.eslintrc.js b/.eslintrc.js index 2873e2f..dd6462b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -4,10 +4,38 @@ module.exports = { ecmaVersion: 2017, sourceType: 'module' }, - extends: 'eslint:recommended', + plugins: [ + 'ember' + ], + extends: [ + 'eslint:recommended', + 'plugin:ember/recommended' + ], env: { browser: true }, rules: { - } + }, + overrides: [ + // node files + { + files: [ + '.eslintrc.js', + '.template-lintrc.js', + 'ember-cli-build.js', + 'testem.js', + 'blueprints/*/index.js', + 'config/**/*.js', + 'lib/*/index.js' + ], + parserOptions: { + sourceType: 'script', + ecmaVersion: 2015 + }, + env: { + browser: false, + node: true + } + } + ] }; diff --git a/.gitignore b/.gitignore index 8fa39a6..f18e5fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,24 @@ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled output -/dist -/tmp +/dist/ +/tmp/ # dependencies -/node_modules -/bower_components +/bower_components/ +/node_modules/ # misc +/.env* /.sass-cache /connect.lock -/coverage/* +/coverage/ /libpeerconnection.log -npm-debug.log* -yarn-error.log -testem.log +/npm-debug.log* +/testem.log +/yarn-error.log # ember-try -.node_modules.ember-try/ -bower.json.ember-try -package.json.ember-try +/.node_modules.ember-try/ +/bower.json.ember-try +/package.json.ember-try diff --git a/.template-lintrc.js b/.template-lintrc.js new file mode 100644 index 0000000..b45e96f --- /dev/null +++ b/.template-lintrc.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = { + extends: 'recommended' +}; diff --git a/.travis.yml b/.travis.yml index 224eafc..a206935 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ --- language: node_js node_js: - - "6" + - "10" sudo: false dist: trusty @@ -25,4 +25,6 @@ install: - yarn install --non-interactive script: + - yarn lint:hbs + - yarn lint:js - yarn test diff --git a/app/components/basic-tree.js b/app/components/basic-tree.js deleted file mode 100644 index c5987e9..0000000 --- a/app/components/basic-tree.js +++ /dev/null @@ -1,310 +0,0 @@ -import Ember from 'ember'; - -// Import the D3 packages we want to use -import { select, event } from 'd3-selection'; -import { cluster, hierarchy } from 'd3-hierarchy'; -import { zoom, zoomIdentity } from 'd3-zoom'; - -const { run, get, inject } = Ember; - -const DURATION = 500; - -// The offset amount (in px) from the left or right side of a node -// box to offset lines between nodes, so the lines don't come right -// up to the edge of the box. -const NODE_OFFSET_SIZE = 50; - -// copied these functions temporarily from `broccoli-viz` here: -// https://github.com/ember-cli/broccoli-viz/blob/master/lib/node-by-id.js - -export default Ember.Component.extend({ - classNames: ['basic-tree'], - - graph: inject.service(), - - init() { - this._super(...arguments); - - this._graphData = null; - }, - - didReceiveAttrs() { - let graphData = get(this, 'graphData'); - - if (this._lastGraphData !== graphData && graphData) { - run.schedule('render', this, this.drawTree, graphData); - - this._lastGraphData = graphData; - } - }, - - nodeFilter(node) { - return node.label.broccoliNode; - }, - - drawTree(graphNode) { - let svgContainer = this.element.querySelector('.svg-container'); - svgContainer.innerHTML = ''; - - let svg = select(svgContainer) - .append("svg") - .attr("preserveAspectRatio", "xMinYMin meet") - .attr("viewBox", "0 0 300 300") - .classed("svg-content", true); - - let g = svg.append("g"); - - // Compute the width of a line of text. For now we'll fake it - // by assuming a constant char width. Add 20 for 'padding'. - // TODO: convert to the real line size based on the real characters. - function computeLineWidth(str) { - const CHAR_WIDTH = 5; - let val = str.length * CHAR_WIDTH + 20; - return val; - } - - // Given a node, compute the width of the box needed to hold - // the text of the element, by computing the max of the widths - // of all the text lines. - function computeNodeWidth(d) { - return Math.max(computeLineWidth(d.data.label.name), - computeLineWidth(`total: ${(d.value / 1000000).toFixed(2)}`), - computeLineWidth(`self: ${(d.data._stats.time.self / 1000000).toFixed(2)}`), - computeLineWidth(`node id: ${d.data._id}`)); - } - - let root = hierarchy(graphNode, node => { - let children = []; - for (let child of node.adjacentIterator()) { - if (this.nodeFilter && !this.nodeFilter(child)) { - continue; - } - - children.push(child); - } - - return children; - }) - .sum(d => d._stats.time.self) - .each(d => d.computedWidth = computeNodeWidth(d)); - - // For each node height (distance above leaves, which are height = 0) - // keep track of the maximum cell width at that height and then use that - // to compute the desired X position for all the nodes at that height. - let nodeHeightData = []; - - root.each((d) => { - let heightData = nodeHeightData[d.height]; - if (heightData === undefined) { - heightData = {maxWidth: d.computedWidth, x: 0 } - nodeHeightData[d.height] = heightData; - } else if (heightData.maxWidth < d.computedWidth) { - heightData.maxWidth = d.computedWidth; - } - }); - - // Now that we have the maxWidth data for all the heights, compute - // the X position for all the cells at each height. - // Each height except the root will have NODE_OFFSET_SIZE on the front. - // Each height except the leaves (height=0) will have NODE_OFFSET_SIZE after it. - // We have to iterate through the list in reverse, since height 0 - // has its X value calculated last. - let currX = 0; - - for (let i = nodeHeightData.length - 1; i >= 0; i--) { - let item = nodeHeightData[i]; - item.x = currX; - currX = currX + item.maxWidth + (2 * NODE_OFFSET_SIZE); - } - - // for debugging - self.root = root; - - // Create the graph. The nodeSize() is [8,280] (width, height) because we - // want to change the orientation of the graph from top-down to left-right. - // To do that we reverse X and Y for calculations and translations. - let graph = cluster() - .separation(() => 8) - .nodeSize([9, 280]); - - function update(source) { - graph(root); - let nodes = root.descendants(); - let links = root.links(); - let node = g - .selectAll(".node") - .data(nodes, d => d.data.id); - - // The graph is laid out by graph() as vertically oriented - // (the root is at the top). We want to show it as horizontally - // oriented (the root is at the left). In addition, we want - // each 'row' of nodes to show up as a column with the cells - // aligned on their left edge at the cell's 0 point. - // To do all this, we'll flip the d.x and d.y values when translating - // the node to its position. - root.each(d => d.y = nodeHeightData[d.height].x); - - // For the 'enter' set, create a node for each entry. - // Move the node to the computed node point (remembering - // that X and Y are reversed so we get a horizontal graph). - let nodeEnter = node - .enter() - .append("g") - .attr("class", 'node') - .attr("transform", d => `translate(${d.y},${d.x})`) - .on('click', (d) => { - // Toggle children on click. - if (d.children) { - d._children = d.children; - d.children = null; - } else { - d.children = d._children; - d._children = null; - } - update(d); - }); - - // Draw the node in a box - nodeEnter.append("rect") - .attr('x', 0) - .attr('y', '-2em') - .attr('width', function(d) { - return d.computedWidth; - }) - .attr('height', "4em") - .attr('stroke', "black") - .attr('stroke-width', 1) - .style('fill', "#fff"); - - // Draw a box in a separate color for the first line as - // a 'title'. - nodeEnter.append("rect") - .attr('x', 0) - .attr('y', '-2em') - .attr('width', function(d) { - return d.computedWidth; - }) - .attr('height', "1em") - .attr('stroke', "black") - .attr('stroke-width', 1) - .style('fill', "#000000"); - - nodeEnter - .append("text") - .attr('text-anchor', 'middle') - .attr("x", d => d.computedWidth/2) - .attr("y", '-1.7em') - .attr("class", "nodetitle") - .attr("font-weight", "bold") - .text(function(d) { - return `${d.data.label.name}`; - }); - - nodeEnter - .append("text") - .attr('text-anchor', 'middle') - .attr("x", d => d.computedWidth/2) - .attr("y", '-0.4em') - .text(function(d) { - return `total: ${(d.value / 1000000).toFixed(2)}`; - }); - - nodeEnter - .append("text") - .attr('text-anchor', 'middle') - .attr("x", d => d.computedWidth/2) - .attr("y", '0.8em') - .text(function(d) { - return `self: ${(d.data._stats.time.self / 1000000).toFixed(2)}`; - }); - - nodeEnter - .append("text") - .attr('text-anchor', 'middle') - .attr("x", d => d.computedWidth/2) - .attr("y", '2.0em') - .text(function(d) { - return `node id: ${d.data._id}`; - }); - - // update exiting node locations - node - .transition() - .duration(DURATION) - .attr('transform', d => `translate(${d.y},${d.x})`); - - // Transition exiting nodes to the parent's new position. - node - .exit() - .transition() - .duration(DURATION) - .attr("transform", function () { - return "translate(" + source.x + "," + source.y + ")"; - }) - .remove(); - - // Create all the links between the various nodes. Each node - // will have the link from an earlier node (higher height) - // come into the 0 point for the node, and the links to lower - // height nodes start at the right edge of the node (+ NODE_OFFSET_SIZE). - let link = g - .selectAll(".link") - .data(links, d => d.target.data.id); - - link - .enter() - .append("path") - .attr("class", "link") - .attr("d", function(d) { - let sourceExitY = d.source.y + d.source.computedWidth + NODE_OFFSET_SIZE; - let targetEntranceY = d.target.y - NODE_OFFSET_SIZE; - - return "M" + d.target.y + "," + d.target.x - + "L" + targetEntranceY + "," + d.target.x - + " " + sourceExitY + "," + d.target.x - + " " + sourceExitY + "," + d.source.x - + " " + (sourceExitY - NODE_OFFSET_SIZE) + "," + d.source.x; - }); - - link - .transition() - .duration(DURATION) - .attr("d", function(d) { - let sourceExitY = d.source.y + d.source.computedWidth + NODE_OFFSET_SIZE; - let targetEntranceY = d.target.y - NODE_OFFSET_SIZE; - - return "M" + d.target.y + "," + d.target.x - + "L" + targetEntranceY + "," + d.target.x - + " " + sourceExitY + "," + d.target.x - + " " + sourceExitY + "," + d.source.x - + " " + (sourceExitY - NODE_OFFSET_SIZE) + "," + d.source.x; - }); - - // update exiting link locations - link - .exit() - .transition() - .duration(DURATION / 2) - .attr("transform", function () { - return "translate(" + source.x + "," + source.y + ")"; - }) - .remove(); - } - - update(root); - - let zoomHandler = zoom() - .on("zoom", () => { - g.attr("transform", event.transform); - }); - - function transform() { - return zoomIdentity - .translate(48, 120) - .scale(0.10); - } - - svg.call(zoomHandler.transform, transform()); - svg.call(zoomHandler); - } -}); diff --git a/app/components/dynamic-height.js b/app/components/dynamic-height.js new file mode 100644 index 0000000..f226e85 --- /dev/null +++ b/app/components/dynamic-height.js @@ -0,0 +1,20 @@ +import Component from '@ember/component'; + +export default Component.extend({ + didInsertElement() { + this._super(...arguments); + + const ensureCorrectHeight = () => { + this.element.style.height = `${window.innerHeight - Number(this.top)}px`; + this.element.style.display = 'block'; + this.element.style.overflow = 'scroll'; + this.raf = requestAnimationFrame(ensureCorrectHeight); + }; + + ensureCorrectHeight(); + }, + willDestroyElement() { + this._super(...arguments); + cancelAnimationFrame(this.raf); + } +}); diff --git a/app/components/flame-graph.js b/app/components/flame-graph.js index 19935da..ee6b79a 100644 --- a/app/components/flame-graph.js +++ b/app/components/flame-graph.js @@ -1,13 +1,18 @@ import Ember from 'ember'; import FlameGraph from '../utils/d3-flame-graphs-v4/d3-flame-graph'; -const { run, get, inject } = Ember; +import { run } from '@ember/runloop'; +import { get } from '@ember/object'; +import Component from '@ember/component'; +import { readOnly } from '@ember/object/computed'; -export default Ember.Component.extend({ +const { inject } = Ember; + +export default Component.extend({ classNames: ['flame-graph'], graph: inject.service(), flameGraph: null, - totalTime: Ember.computed.alias('graph.data.summary.totalTime'), + totalTime: readOnly('graph.data.summary.totalTime'), init() { this._super(...arguments); diff --git a/app/components/slow-node-times.js b/app/components/slow-node-times.js index ba785d4..ed70c61 100644 --- a/app/components/slow-node-times.js +++ b/app/components/slow-node-times.js @@ -1,9 +1,9 @@ import Ember from 'ember'; +import { get, set } from '@ember/object'; +import { computed } from '@ember/object'; +import Component from '@ember/component'; const { - get, - set, - computed, inject } = Ember; @@ -23,22 +23,24 @@ function selfTime(node) { // Note: we skip the non-broccoliNodes except at the beginning // (the root of the tree is not a broccoliNode, but we want to // proceed to its children -function computeNodeTimes(node) { - var total = selfTime(node); +function computeNodeTimes(root, totalMap=new WeakMap()) { + for (let node of root.dfsIterator({order: 'post'})) { + let total = selfTime(node); + for (let child of node.childIterator()) { + total += totalMap.get(child); + } + totalMap.set(node, total); - for (let childNode of node.adjacentIterator()) { - if (childNode.label.broccoliNode) { - total += computeNodeTimes(childNode); + if (node.label.broccoliNode) { + set(node._stats.time, 'plugin', total); } } - Ember.set(node._stats.time, 'plugin', total); - - return total; + return totalMap.get(root); } -export default Ember.Component.extend({ +export default Component.extend({ graph: inject.service(), init() { @@ -85,7 +87,7 @@ export default Ember.Component.extend({ }, {}); nodes = []; - + for (let pluginName in pluginNameMap) { nodes.push({ groupedByPluginName: true, @@ -112,7 +114,7 @@ export default Ember.Component.extend({ // off the label as the plugin name. If not, we need // to create a map of the plugin names and return that. let pluginNames = []; - + if (nodes[0].groupedByPluginName === true) { pluginNames = nodes.map(node => node.label.name); } else { diff --git a/app/controllers/application.js b/app/controllers/application.js index 6095d94..2905404 100644 --- a/app/controllers/application.js +++ b/app/controllers/application.js @@ -1,11 +1,12 @@ import Ember from 'ember'; import fetch from "fetch"; +import Controller from '@ember/controller'; const { inject } = Ember; -export default Ember.Controller.extend({ +export default Controller.extend({ graph: inject.service(), actions: { diff --git a/app/controllers/flame.js b/app/controllers/flame.js index 95fea6a..914224b 100644 --- a/app/controllers/flame.js +++ b/app/controllers/flame.js @@ -1,7 +1,7 @@ import Ember from 'ember'; +import Controller from '@ember/controller'; const { - Controller, inject } = Ember; diff --git a/app/controllers/graph.js b/app/controllers/graph.js deleted file mode 100644 index 03743af..0000000 --- a/app/controllers/graph.js +++ /dev/null @@ -1,19 +0,0 @@ -import Ember from 'ember'; - -const { - Controller, - getOwner, - computed, - inject -} = Ember; - -export default Controller.extend({ - graph: inject.service(), - - route: computed.alias('router.currentPath'), - - init() { - this._super(...arguments); - this.set('router', getOwner(this).lookup('router:main')); - } -}); diff --git a/app/controllers/graph/index.js b/app/controllers/graph/index.js deleted file mode 100644 index 02720cc..0000000 --- a/app/controllers/graph/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import Ember from 'ember'; - -const { - inject -} = Ember; - -export default Ember.Controller.extend({ - graph: inject.service() -}); diff --git a/app/controllers/graph/node.js b/app/controllers/graph/node.js deleted file mode 100644 index 02720cc..0000000 --- a/app/controllers/graph/node.js +++ /dev/null @@ -1,9 +0,0 @@ -import Ember from 'ember'; - -const { - inject -} = Ember; - -export default Ember.Controller.extend({ - graph: inject.service() -}); diff --git a/app/controllers/selected-node.js b/app/controllers/selected-node.js index 041dfb3..56e467c 100644 --- a/app/controllers/selected-node.js +++ b/app/controllers/selected-node.js @@ -1,9 +1,10 @@ import Ember from 'ember'; +import Controller from '@ember/controller'; const { inject } = Ember; -export default Ember.Controller.extend({ +export default Controller.extend({ graph: inject.service() }) diff --git a/app/controllers/slow-nodes.js b/app/controllers/slow-nodes.js index b962b63..593b5ee 100644 --- a/app/controllers/slow-nodes.js +++ b/app/controllers/slow-nodes.js @@ -1,10 +1,11 @@ +import Controller from '@ember/controller'; import Ember from 'ember'; const { inject } = Ember; -export default Ember.Controller.extend({ +export default Controller.extend({ graph: inject.service(), actions: { diff --git a/app/helpers/includes.js b/app/helpers/includes.js index 80c2d14..fe61691 100644 --- a/app/helpers/includes.js +++ b/app/helpers/includes.js @@ -1,5 +1,5 @@ -import Ember from 'ember'; +import { helper as buildHelper } from '@ember/component/helper'; -export default Ember.Helper.helper(function([haystack, needle]) { +export default buildHelper(function([haystack, needle]) { return haystack && haystack.includes && haystack.includes(needle); }); diff --git a/app/helpers/ns-to-ms.js b/app/helpers/ns-to-ms.js index d8af177..00223d4 100644 --- a/app/helpers/ns-to-ms.js +++ b/app/helpers/ns-to-ms.js @@ -1,7 +1,7 @@ -import Ember from 'ember'; +import { helper as buildHelper } from '@ember/component/helper'; export function nsToMs([time]) { return (time / 1000000).toFixed(2); } -export default Ember.Helper.helper(nsToMs); +export default buildHelper(nsToMs); diff --git a/app/helpers/stats-iterator.js b/app/helpers/stats-iterator.js index bd9f87e..ecee3f9 100644 --- a/app/helpers/stats-iterator.js +++ b/app/helpers/stats-iterator.js @@ -1,6 +1,6 @@ -import Ember from 'ember'; +import { helper as buildHelper } from '@ember/component/helper'; -export default Ember.Helper.helper(function([node]) { +export default buildHelper(function([node]) { let stats = {}; for (let [name, value] of node.statsIterator()) { diff --git a/app/router.js b/app/router.js index 17c32dc..eaeeeda 100644 --- a/app/router.js +++ b/app/router.js @@ -7,11 +7,7 @@ const Router = EmberRouter.extend({ }); Router.map(function() { - this.route('graph', { path: '/' }, function() { - this.route('node'); - }); - - this.route('slow-nodes'); + this.route('slow-nodes', { path: '/' }); this.route('flame'); }); diff --git a/app/services/graph.js b/app/services/graph.js index f9eceff..8da8b80 100644 --- a/app/services/graph.js +++ b/app/services/graph.js @@ -1,15 +1,12 @@ -import Ember from 'ember'; import config from '../config/environment'; import heimdallGraph from 'heimdalljs-graph'; - -const { - getOwner -} = Ember; +import { getOwner } from '@ember/application'; +import Service from '@ember/service'; const DATA_STORAGE_KEY = `${config.storageVersion}_graph-data`; const SELECTED_NODE_STORAGE_KEY = `${config.storageVersion}_selected-node-id`; -export default Ember.Service.extend({ +export default Service.extend({ init() { this._super(...arguments); diff --git a/app/templates/application.hbs b/app/templates/application.hbs index ed00887..acede4d 100644 --- a/app/templates/application.hbs +++ b/app/templates/application.hbs @@ -1,9 +1,8 @@ -