diff --git a/bin/nodejs-dashboard.js b/bin/nodejs-dashboard.js index 7dbfe6f..f3d1c38 100755 --- a/bin/nodejs-dashboard.js +++ b/bin/nodejs-dashboard.js @@ -15,7 +15,7 @@ var parseSettings = require("../lib/parse-settings"); var appName = appPkg.name || "node"; var program = new commander.Command(pkg.name); -// Mimic commander sintax errors (with offsets) for consistency +// Mimic commander syntax errors (with offsets) for consistency /* eslint-disable no-console */ var exitWithError = function () { var args = Array.prototype.slice.call(arguments); diff --git a/lib/dashboard.js b/lib/dashboard.js index 015e7d8..86a373f 100644 --- a/lib/dashboard.js +++ b/lib/dashboard.js @@ -13,7 +13,7 @@ var generateLayouts = require("./generate-layouts"); var LogProvider = require("./providers/log-provider"); var MetricsProvider = require("./providers/metrics-provider"); var BaseView = require("./views/base-view"); -var GotoTimeView = require("./views/goto-time-view.js"); +var GotoTimeView = require("./views/goto-time-view"); var THROTTLE_TIMEOUT = 150; @@ -72,21 +72,21 @@ Dashboard.prototype._configureKeys = function () { this._showLayout(target); }.bind(this), THROTTLE_TIMEOUT)); - var helpNode = this.helpView.node; this.container.key(["?", "h", "S-h"], function () { - helpNode.toggle(); + this.gotoTimeView.hide(); + this.helpView.toggle(); this.screen.render(); }.bind(this)); this.container.key(["g", "S-g"], function () { - helpNode.hide(); + this.helpView.hide(); this.gotoTimeView.toggle(); this.screen.render(); }.bind(this)); this.container.key("escape", function () { - if (helpNode.visible || this.gotoTimeView.isVisible()) { - helpNode.hide(); + if (this.helpView.isVisible() || this.gotoTimeView.isVisible()) { + this.helpView.hide(); this.gotoTimeView.hide(); this.screen.render(); } else { diff --git a/lib/providers/metrics-provider.js b/lib/providers/metrics-provider.js index 22fecd9..30e6910 100644 --- a/lib/providers/metrics-provider.js +++ b/lib/providers/metrics-provider.js @@ -6,10 +6,11 @@ var EventEmitter = require("events").EventEmitter; var _ = require("lodash"); +var constants = require("../constants"); +var time = require("../time"); // get the defined aggregation levels -var AGGREGATE_TIME_LEVELS = require("../constants.js").AGGREGATE_TIME_LEVELS; -var TIME_SCALES = require("../constants.js").TIME_SCALES; +var AGGREGATE_TIME_LEVELS = constants.AGGREGATE_TIME_LEVELS; // what a valid time offset looks like var TIME_LABEL_PATTERN = /^(\d+y)?\s*(\d{1,3}d)?\s*(\d{1,2})?(:\d{1,2})?(:\d{2})?$/i; @@ -78,177 +79,6 @@ var MetricsProvider = // MetricsProvider inherits from EventEmitter MetricsProvider.prototype = Object.create(EventEmitter.prototype); -/** - * Given a time index and unit of time measure, compute a condensed, human-readable label. - * - * @param {Number} timeIndex - * The logical index of time. - * - * @param {Number} aggregateTimeUnits - * The unit of time measure. - * - * @returns {String} - * A scaled, string-representation of time at the index is returned. - */ -var getTimeIndexLabel = - function getTimeIndexLabel(timeIndex, aggregateTimeUnits) { - var DIGITS_PER_UNIT = 2; - - var timeValue = timeIndex * aggregateTimeUnits; - var timeElements = []; - - if (timeValue === 0) { - return ":00"; - } - - _.every(TIME_SCALES, function (timeScale, index, timeScales) { - var timeElement = { - units: timeScale.units, - value: 0 - }; - - // stop reducing when it cannot be divided - if (timeValue < timeScale.divisor) { - return false; - } - - // don't capture a time element for milliseconds - if (timeScale.units !== "ms") { - // reduce by the divisor - timeElement.value = timeValue / timeScale.divisor; - - // if there are more elements after, take the modulo to get the remainder - if (index < timeScales.length - 1) { - timeElement.value = Math.floor(timeElement.value % timeScales[index + 1].divisor); - } else { - timeElement.value = Math.floor(timeElement.value); - } - - timeElements.push(timeElement); - } - - // reduce - timeValue /= timeScale.divisor; - - return true; - }); - - return _.reduce(timeElements, function (prev, curr, index) { - switch (curr.units) { - case "s": - return ":" + _.padStart(curr.value, DIGITS_PER_UNIT, "0"); - case "m": - case "h": - if (index < timeElements.length - 1) { - return (curr.units === "m" ? ":" : " ") - + _.padStart(curr.value, DIGITS_PER_UNIT, "0") - + prev; - } else { - return curr.value + prev; - } - default: - return curr.value + curr.units + prev; - } - }, ""); - }; - -/** - * Given a time label value (ex: 2y 5d 1:22:33), produce the actual - * time value in ms. - * - * @param {String} label - * The time label to convert. - * - * @throws {Error} - * An error is thrown if the time label cannot be converted to ms. - * - * @returns {Number} - * The time value in ms is returned. - */ -var convertTimeLabelToMilliseconds = function (label) { - /* eslint-disable no-magic-numbers */ - - // a container for all time elements - var timeElements = { - y: 0, - d: 0, - t: [], - h: 0, - m: 0, - s: 0 - }; - - // the initial divisor - var divisor = TIME_SCALES[0].divisor; - - // break up the input - var split = TIME_LABEL_PATTERN.exec(label); - - // take the broken apart pieces and consume them - _.each(_.slice(split, 1), function (value, index) { - var pieces; - - // skip undefined values - if (value === undefined) { - return; - } - - // get the numeric and unit components, if any - pieces = /^:?(\d*)([yd])?/.exec(value); - - switch (index) { - case 0: - case 1: - // year and day are just keys - timeElements[pieces[2]] = +pieces[1]; - break; - case 2: - case 3: - case 4: - // time is only slightly trickier; missing elements get pushed down - timeElements.t.push(+pieces[1]); - break; - } - }); - - while (timeElements.t.length < 3) { - // complete the time picture with leading zeros - timeElements.t.unshift(0); - } - - // convert time parts to keys - timeElements.h = timeElements.t[0]; - timeElements.m = timeElements.t[1]; - timeElements.s = timeElements.t[2]; - - // now we can discard the time array - delete timeElements.t; - - // now, reduce the time elements by the scaling factors - return _.reduce(TIME_SCALES, function (prev, timeScale, index) { - // the divisor grows with each time scale factor - divisor *= timeScale.divisor; - - // if the time element is represented, multiply by current divisor - if (timeElements[timeScale.units]) { - // if there are more time scales to go, make sure the current value - // does not exceed its limits (ex: 90s should be 1:30 instead) - if (index < TIME_SCALES.length - 1) { - if (timeElements[timeScale.units] >= TIME_SCALES[index + 1].divisor) { - throw new Error("Enter a valid time value"); - } - } - - // continue to accumulate the time - prev += timeElements[timeScale.units] * divisor; - } - - return prev; - }, 0); - - /* eslint-enable no-magic-numbers */ -}; - /** * Given a moment in time, the start time, and time units, produce the * correct time index. @@ -304,18 +134,15 @@ MetricsProvider.prototype.setZoomLevel = function setZoomLevel(zoom) { * An object containing the time range is returned */ MetricsProvider.prototype.getAvailableTimeRange = function getAvailableTimeRange() { + var maxAverages = this._aggregation[this.lowestAggregateTimeUnits].data.length - 1; return { minTime: { - label: getTimeIndexLabel(0, this.lowestAggregateTimeUnits), + label: time.getLabel(0), value: 0 }, maxTime: { - label: getTimeIndexLabel( - this._aggregation[this.lowestAggregateTimeUnits].data.length - 1, - this.lowestAggregateTimeUnits - ), - value: (this._aggregation[this.lowestAggregateTimeUnits].data.length - 1) - * this.lowestAggregateTimeUnits + label: time.getLabel(maxAverages * this.lowestAggregateTimeUnits), + value: maxAverages * this.lowestAggregateTimeUnits } }; }; @@ -758,7 +585,7 @@ MetricsProvider.prototype.getXAxis = timeIndex >= -scrollOffset; timeIndex-- ) { - xAxis.push(getTimeIndexLabel(timeIndex, +this.zoomLevelKey)); + xAxis.push(time.getLabel(timeIndex * +this.zoomLevelKey)); } return xAxis; @@ -792,7 +619,7 @@ MetricsProvider.prototype.validateTimeLabel = } // must be able to convert (this can throw too) - timeValue = convertTimeLabelToMilliseconds(label); + timeValue = time.convertTimeLabelToMilliseconds(label); // must be a number in range if (isNaN(timeValue) || !_.inRange(timeValue, 0, timeRange.maxTime.value + 1)) { diff --git a/lib/time.js b/lib/time.js new file mode 100644 index 0000000..7be5370 --- /dev/null +++ b/lib/time.js @@ -0,0 +1,173 @@ +"use strict"; + +var _ = require("lodash"); +var constants = require("./constants"); + +var TIME_SCALES = constants.TIME_SCALES; +var TIME_LABEL_PATTERN = constants.TIME_LABEL_PATTERN; + +/** + * Compute a condensed human-readable label for a given time value. + * + * @param {Number} timeValue + * The logical index of time. + * + * @returns {String} + * A scaled, string-representation of time at the index is returned. + */ +exports.getLabel = +function getLabel(timeValue) { + var DIGITS_PER_UNIT = 2; + var timeElements = []; + + if (timeValue === 0) { + return ":00"; + } + + _.every(TIME_SCALES, function (timeScale, index, timeScales) { + var timeElement = { + units: timeScale.units, + value: 0 + }; + + // stop reducing when it cannot be divided + if (timeValue < timeScale.divisor) { + return false; + } + + // don't capture a time element for milliseconds + if (timeScale.units !== "ms") { + // reduce by the divisor + timeElement.value = timeValue / timeScale.divisor; + + // if there are more elements after, take the modulo to get the remainder + if (index < timeScales.length - 1) { + timeElement.value = Math.floor(timeElement.value % timeScales[index + 1].divisor); + } else { + timeElement.value = Math.floor(timeElement.value); + } + + timeElements.push(timeElement); + } + + // reduce + timeValue /= timeScale.divisor; + + return true; + }); + + return _.reduce(timeElements, function (prev, curr, index) { + switch (curr.units) { + case "s": + return ":" + _.padStart(curr.value, DIGITS_PER_UNIT, "0"); + case "m": + case "h": + if (index < timeElements.length - 1) { + return (curr.units === "m" ? ":" : " ") + + _.padStart(curr.value, DIGITS_PER_UNIT, "0") + + prev; + } else { + return curr.value + prev; + } + default: + return curr.value + curr.units + prev; + } + }, ""); +}; + +/** + * Given a time label value (ex: 2y 5d 1:22:33), produce the actual + * time value in ms. + * + * @param {String} label + * The time label to convert. + * + * @throws {Error} + * An error is thrown if the time label cannot be converted to ms. + * + * @returns {Number} + * The time value in ms is returned. + */ +exports.convertTimeLabelToMilliseconds = function (label) { + /* eslint-disable no-magic-numbers */ + + // a container for all time elements + var timeElements = { + y: 0, + d: 0, + t: [], + h: 0, + m: 0, + s: 0 + }; + + // the initial divisor + var divisor = TIME_SCALES[0].divisor; + + // break up the input + var split = TIME_LABEL_PATTERN.exec(label); + + // take the broken apart pieces and consume them + _.each(_.slice(split, 1), function (value, index) { + var pieces; + + // skip undefined values + if (value === undefined) { + return; + } + + // get the numeric and unit components, if any + pieces = /^:?(\d*)([yd])?/.exec(value); + + switch (index) { + case 0: + case 1: + // year and day are just keys + timeElements[pieces[2]] = +pieces[1]; + break; + case 2: + case 3: + case 4: + // time is only slightly trickier; missing elements get pushed down + timeElements.t.push(+pieces[1]); + break; + } + }); + + while (timeElements.t.length < 3) { + // complete the time picture with leading zeros + timeElements.t.unshift(0); + } + + // convert time parts to keys + timeElements.h = timeElements.t[0]; + timeElements.m = timeElements.t[1]; + timeElements.s = timeElements.t[2]; + + // now we can discard the time array + delete timeElements.t; + + // now, reduce the time elements by the scaling factors + return _.reduce(TIME_SCALES, function (prev, timeScale, index) { + // the divisor grows with each time scale factor + divisor *= timeScale.divisor; + + // if the time element is represented, multiply by current divisor + if (timeElements[timeScale.units]) { + // if there are more time scales to go, make sure the current value + // does not exceed its limits (ex: 90s should be 1:30 instead) + if (index < TIME_SCALES.length - 1) { + if (timeElements[timeScale.units] >= TIME_SCALES[index + 1].divisor) { + throw new Error("Enter a valid time value"); + } + } + + // continue to accumulate the time + prev += timeElements[timeScale.units] * divisor; + } + + return prev; + }, 0); + + /* eslint-enable no-magic-numbers */ +}; diff --git a/lib/views/base-view.js b/lib/views/base-view.js index ff5e198..14d1200 100644 --- a/lib/views/base-view.js +++ b/lib/views/base-view.js @@ -14,7 +14,9 @@ var BaseView = function BaseView(options) { options.parent.screen.on("resize", this._boundRecalculatePosition); this.parent = options.parent; - this.layoutConfig = _.assign(this.getDefaultLayoutConfig(options), options.layoutConfig.view); + this.layoutConfig = Object.assign( + this.getDefaultLayoutConfig(options), + options.layoutConfig.view); }; BaseView.prototype.getDefaultLayoutConfig = function () { diff --git a/lib/views/help.js b/lib/views/help.js index a46054f..cebbcca 100644 --- a/lib/views/help.js +++ b/lib/views/help.js @@ -17,7 +17,7 @@ var HelpView = function HelpView(options) { "{cyan-fg} h, ?{/} toggle this window", "{cyan-fg} ctrl-c, q{/} quit", "", - "{right}{gray-fg}version: " + pkg.version + "{/}" + "{right}{white-fg}version: " + pkg.version + "{/}" ].join("\n"); this.node = blessed.box({ @@ -43,7 +43,45 @@ var HelpView = function HelpView(options) { hidden: true }); + this.node.on("show", function () { + options.parent.screen.saveFocus(); + this.node.setFront(); + }.bind(this)); + + this.node.on("hide", function () { + options.parent.screen.restoreFocus(); + }); + options.parent.append(this.node); }; +/** + * Toggle the visibility of the view. + * + * @returns {void} + */ +HelpView.prototype.toggle = function () { + this.node.toggle(); +}; + +/** + * Hide the view. + * + * @returns {void} + */ +HelpView.prototype.hide = function () { + this.node.hide(); +}; + +/** + * Check to see if the view is visible. + * + * @returns {Boolean} + * Truthy if the view is visible, falsey otherwise. + */ +HelpView.prototype.isVisible = function () { + return this.node.visible; +}; + + module.exports = HelpView; diff --git a/test/lib/providers/metrics-provider.spec.js b/test/lib/providers/metrics-provider.spec.js index 7654192..d76f80a 100644 --- a/test/lib/providers/metrics-provider.spec.js +++ b/test/lib/providers/metrics-provider.spec.js @@ -6,7 +6,7 @@ var expect = require("chai").expect; var sinon = require("sinon"); var _ = require("lodash"); -var AGGREGATE_TIME_LEVELS = require("../../../lib/constants.js").AGGREGATE_TIME_LEVELS; +var AGGREGATE_TIME_LEVELS = require("../../../lib/constants").AGGREGATE_TIME_LEVELS; var utils = require("../../utils"); var MetricsProvider = require("../../../lib/providers/metrics-provider");