diff --git a/package.json b/package.json index 6b13820..5d2c8ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prelert_swimlane_vis", - "version": "0.1.1", + "version": "5.0.0", "description": "Prelert swimlane visualization plugin", "license": "Apache-2.0" } diff --git a/public/prelert_swimlane_vis.html b/public/prelert_swimlane_vis.html index edcfb8d..d6edd27 100644 --- a/public/prelert_swimlane_vis.html +++ b/public/prelert_swimlane_vis.html @@ -11,12 +11,12 @@ info="This interval creates {{ vis.params.interval.scale > 1 ? 'buckets that are too large' : 'too many buckets' }} to show in the selected time range, so it has been scaled to {{ vis.params.interval.description }}"> - +
- Prelert logo + Prelert logo
- +
-
+ diff --git a/public/prelert_swimlane_vis.js b/public/prelert_swimlane_vis.js index d3d4b58..93bd1e8 100644 --- a/public/prelert_swimlane_vis.js +++ b/public/prelert_swimlane_vis.js @@ -18,79 +18,76 @@ **************************************************************************** */ -define(function(require) { +import 'plugins/prelert_swimlane_vis/prelert_swimlane_vis_controller.js'; +import 'plugins/prelert_swimlane_vis/prelert_swimlane_vis.less'; - require('plugins/prelert_swimlane_vis/prelert_swimlane_vis_controller.js'); - require('plugins/prelert_swimlane_vis/prelert_swimlane_vis.less'); +import visTypes from 'ui/registry/vis_types'; +import TemplateVisTypeProvider from 'ui/template_vis_type/template_vis_type'; +import VisSchemasProvider from 'ui/vis/schemas'; - // Register the PrelertSwimlaneVisProvider with the visualization registry. - require('ui/registry/vis_types').register(PrelertSwimlaneVisProvider); +// Register the PrelertSwimlaneVisProvider with the visualization registry. +visTypes.register(PrelertSwimlaneVisProvider); - function PrelertSwimlaneVisProvider(Private) { - var TemplateVisType = Private(require('ui/template_vis_type/TemplateVisType')); - var Schemas = Private(require('ui/Vis/Schemas')); +function PrelertSwimlaneVisProvider(Private) { + const TemplateVisType = Private(TemplateVisTypeProvider); + const Schemas = Private(VisSchemasProvider); - // Return a new instance describing this visualization. - return new TemplateVisType({ - name : 'prelert_swimlane', - title : 'Swimlane', - icon : 'fa-bars', - description : 'Swimlane visualization displaying the behavior of a metric ' + - 'over time across a field from the results. ' + - 'Each lane displays a different value of the field, with the ' + - 'relative size of the metric over each interval indicated ' + - 'by the color of the symbol at that time. ' + - 'Created by Prelert for Behavioral Analytics www.prelert.com', - template : require('plugins/prelert_swimlane_vis/prelert_swimlane_vis.html'), - params : { - editor : require('plugins/prelert_swimlane_vis/prelert_swimlane_vis_params.html'), - defaults : { - interval : { display : 'Auto', val : 'auto' }, - lowThreshold: 0, - warningThreshold: 3, - minorThreshold: 25, - majorThreshold: 50, - criticalThreshold: 75, - tooltipNumberFormat: '0.0' - }, - intervalOptions: [{display:'Auto', val:'auto'}, - {display:'5 minutes', val:'custom', customInterval:'5m'}, - {display:'10 minutes', val:'custom', customInterval:'10m'}, - {display:'30 minutes', val:'custom', customInterval:'30m'}, - {display:'1 hour', val:'h'}, - {display:'3 hours', val:'custom', customInterval:'3h'}, - {display:'12 hours', val:'custom', customInterval:'12h'}, - {display:'1 day', val:'d'}] + // Return a new instance describing this visualization. + return new TemplateVisType({ + name : 'prelert_swimlane', + title : 'Swimlane', + icon : 'fa-bars', + description : 'Swimlane visualization displaying the behavior of a metric ' + + 'over time across a field from the results. ' + + 'Each lane displays a different value of the field, with the ' + + 'relative size of the metric over each interval indicated ' + + 'by the color of the symbol at that time. ' + + 'Created by Prelert for Behavioral Analytics www.prelert.com', + template : require('plugins/prelert_swimlane_vis/prelert_swimlane_vis.html'), + params : { + editor : require('plugins/prelert_swimlane_vis/prelert_swimlane_vis_params.html'), + defaults : { + interval : { display : 'Auto', val : 'auto' }, + lowThreshold: 0, + warningThreshold: 3, + minorThreshold: 25, + majorThreshold: 50, + criticalThreshold: 75, + tooltipNumberFormat: '0.0' }, - schemas : new Schemas([ { - group : 'metrics', - name : 'metric', - title : 'Value', - min : 1, - max : 1, - aggFilter : [ 'count', 'avg', 'sum', 'min', 'max' ] - }, { - group : 'buckets', - name : 'viewBy', - icon : 'fa fa-eye', - title : 'View by', - mustBeFirst : true, - min : 0, - max : 1, - aggFilter : 'terms' - }, { - group : 'buckets', - name : 'timeSplit', - icon : 'fa fa-th', - title : 'Time field', - min : 1, - max : 1, - aggFilter : 'date_histogram' - } ]) - }); - } - - // export the provider so that the visType can be required with Private() - return PrelertSwimlaneVisProvider; - -}); + intervalOptions: [{display:'Auto', val:'auto'}, + {display:'5 minutes', val:'custom', customInterval:'5m'}, + {display:'10 minutes', val:'custom', customInterval:'10m'}, + {display:'30 minutes', val:'custom', customInterval:'30m'}, + {display:'1 hour', val:'h'}, + {display:'3 hours', val:'custom', customInterval:'3h'}, + {display:'12 hours', val:'custom', customInterval:'12h'}, + {display:'1 day', val:'d'}] + }, + schemas : new Schemas([ { + group : 'metrics', + name : 'metric', + title : 'Value', + min : 1, + max : 1, + aggFilter : [ 'count', 'avg', 'sum', 'min', 'max', 'cardinality' ] + }, { + group : 'buckets', + name : 'viewBy', + icon : 'fa fa-eye', + title : 'View by', + mustBeFirst : true, + min : 0, + max : 1, + aggFilter : 'terms' + }, { + group : 'buckets', + name : 'timeSplit', + icon : 'fa fa-th', + title : 'Time field', + min : 1, + max : 1, + aggFilter : 'date_histogram' + } ]) + }); +}; diff --git a/public/prelert_swimlane_vis.less b/public/prelert_swimlane_vis.less index 2e6469b..77c2240 100644 --- a/public/prelert_swimlane_vis.less +++ b/public/prelert_swimlane_vis.less @@ -15,7 +15,7 @@ padding-right: 5px; padding-left: 5px; } - + .flot-y-axis { .flot-tick-label { @@ -28,7 +28,7 @@ z-index: 1; } } - + .prl-swimlane-vis-point-over { cursor: pointer !important; } @@ -36,14 +36,14 @@ .prl-swimlane-vis-options { div.threshold-input-container { - display: block; + display: block; padding-bottom: 7px; - + .fa-circle { - display:inline; + display:inline; font-size:16px; } - + .icon-severity-critical { color: #fe5050; } @@ -59,7 +59,7 @@ .icon-severity-warning { color: #8bc8fb; } - + .icon-severity-low { color: #d2e9f7; } @@ -67,12 +67,12 @@ .icon-severity-unknown { color: #e6e6e6; } - + input[type="number"] { display:inline; width: 110px; font-size: 12px; - } + } } } @@ -86,7 +86,7 @@ background-color: #303030; font-family: Roboto, Droid, Helvetica Neue, Helvetica, Arial, sans-serif; font-size: 12px; - + hr { margin-top: 3px; margin-bottom: 3px; @@ -98,9 +98,8 @@ .prl-swimlane-vis { color: #cecece; } - + .flot-tick-label { color: #cecece; } } - diff --git a/public/prelert_swimlane_vis_controller.js b/public/prelert_swimlane_vis_controller.js index 57112c7..189c798 100644 --- a/public/prelert_swimlane_vis_controller.js +++ b/public/prelert_swimlane_vis_controller.js @@ -18,21 +18,24 @@ **************************************************************************** */ -var _ = require('lodash'); -var $ = require('jquery'); -var moment = require('moment'); -var numeral = require('numeral'); -var flot = require("imports?$=jquery!./lib/bower_components/flot/jquery.flot"); -require("imports?$=jquery!./lib/bower_components/flot/jquery.flot.selection"); -require("imports?$=jquery!./lib/bower_components/flot/jquery.flot.time"); -require("imports?$=jquery,this=>window!./lib/bower_components/flot/jquery.flot.resize"); -require('ui/courier'); -require('ui/timefilter'); -require('ui/directives/inequality'); - -var module = require('ui/modules').get('prelert_swimlane_vis/prelert_swimlane_vis', ['kibana']); - -module.controller('PrelertSwimlaneVisController', function($scope, courier) { +import angular from 'angular'; +import _ from 'lodash'; +import $ from 'jquery'; +import moment from 'moment'; +import numeral from 'numeral'; +require('imports?$=jquery!./lib/bower_components/flot/jquery.flot'); +require('imports?$=jquery!./lib/bower_components/flot/jquery.flot.selection'); +require('imports?$=jquery!./lib/bower_components/flot/jquery.flot.time'); +require('imports?$=jquery,this=>window!./lib/bower_components/flot/jquery.flot.resize'); + +import 'ui/courier'; +import 'ui/timefilter'; +import 'ui/directives/inequality'; +import chrome from 'ui/chrome'; +import uiModules from 'ui/modules'; + +const module = uiModules.get('prelert_swimlane_vis/prelert_swimlane_vis', ['kibana']); +module.controller('PrelertSwimlaneVisController', function ($scope, courier) { $scope.$watch('esResponse', function (resp) { @@ -44,8 +47,8 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { if (resp.hits.total !== 0) { // Remove ng-hide from the parent div as that has display:none, // resulting in the flot chart labels falling inside the chart area on first render. - var ngHideContainer = $("prl-swimlane-vis").closest( ".ng-hide" ); - ngHideContainer.removeClass("ng-hide"); + let ngHideContainer = $('prl-swimlane-vis').closest('.ng-hide'); + ngHideContainer.removeClass('ng-hide'); } // Process the aggregations in the ES response. @@ -53,49 +56,49 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { syncViewControls(); - // Tell the swimlane directive to render. + // Tell the swimlane directive to render. $scope.$emit('render'); }); - $scope.processAggregations = function(aggregations) { - var dataByViewBy = {}; + $scope.processAggregations = function (aggregations) { + let dataByViewBy = {}; - if (aggregations && - ($scope.vis.aggs.bySchemaName['metric'] !== undefined) && - ($scope.vis.aggs.bySchemaName['timeSplit'] !== undefined) ) { + if (aggregations && + ($scope.vis.aggs.bySchemaName.metric !== undefined) && + ($scope.vis.aggs.bySchemaName.timeSplit !== undefined)) { // Retrieve the visualization aggregations. - var metricsAgg = $scope.vis.aggs.bySchemaName['metric'][0]; - var timeAgg = $scope.vis.aggs.bySchemaName['timeSplit'][0]; - var timeAggId = timeAgg.id; - - if ($scope.vis.aggs.bySchemaName['viewBy'] !== undefined) { + let metricsAgg = $scope.vis.aggs.bySchemaName.metric[0]; + let timeAgg = $scope.vis.aggs.bySchemaName.timeSplit[0]; + let timeAggId = timeAgg.id; + + if ($scope.vis.aggs.bySchemaName.viewBy !== undefined) { // Get the buckets of the viewBy aggregation. - var viewByAgg = $scope.vis.aggs.bySchemaName['viewBy'][0]; - var buckets = aggregations[viewByAgg.id].buckets; - _.each(buckets, function(bucket){ + let viewByAgg = $scope.vis.aggs.bySchemaName.viewBy[0]; + let viewByBuckets = aggregations[viewByAgg.id].buckets; + _.each(viewByBuckets, function (bucket) { // There will be 1 bucket for each 'view by' value. - var viewByValue = bucket.key; - var timesForViewBy = {}; + let viewByValue = bucket.key; + let timesForViewBy = {}; dataByViewBy[viewByValue] = timesForViewBy; - var bucketsForViewByValue = bucket[timeAggId].buckets; - _.each(bucketsForViewByValue, function(valueBucket) { + let bucketsForViewByValue = bucket[timeAggId].buckets; + _.each(bucketsForViewByValue, function (valueBucket) { // time is the 'valueBucket' key. timesForViewBy[valueBucket.key] = { - value: metricsAgg.getValue(valueBucket) + value: metricsAgg.getValue(valueBucket) }; }); }); } else { // No 'View by' selected - compile data for a single swimlane // showing the time bucketed metric value. - var timesForViewBy = {}; - var buckets = aggregations[timeAggId].buckets; - _.each(buckets, function(bucket){ + let timesForViewBy = {}; + let buckets = aggregations[timeAggId].buckets; + _.each(buckets, function (bucket) { timesForViewBy[bucket.key] = { value: metricsAgg.getValue(bucket) }; }); - + // Use the metric label as the swimlane label. dataByViewBy[metricsAgg.makeLabel()] = timesForViewBy; } @@ -109,25 +112,25 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { function syncViewControls() { // Synchronize the Interval control to match the aggregation run in the view, // e.g. if being edited via the Kibana Visualization tab sidebar. - if ($scope.vis.aggs.length === 0 || $scope.vis.aggs.bySchemaName['timeSplit'] === undefined) { + if ($scope.vis.aggs.length === 0 || $scope.vis.aggs.bySchemaName.timeSplit === undefined) { return; } // Retrieve the visualization aggregations. - var timeAgg = $scope.vis.aggs.bySchemaName['timeSplit'][0]; + let timeAgg = $scope.vis.aggs.bySchemaName.timeSplit[0]; // Update the scope 'interval' field. - var aggInterval = _.get(timeAgg, ['params', 'interval', 'val']); + let aggInterval = _.get(timeAgg, ['params', 'interval', 'val']); if (aggInterval === 'custom') { aggInterval = _.get(timeAgg, ['params', 'customInterval']); - } + } - var scopeInterval = $scope.vis.params.interval.val; + let scopeInterval = $scope.vis.params.interval.val; if (scopeInterval && scopeInterval === 'custom') { scopeInterval = $scope.vis.params.interval.customInterval; } - var setToInterval = _.findWhere($scope.vis.type.params.intervalOptions, {val: aggInterval}); + let setToInterval = _.findWhere($scope.vis.type.params.intervalOptions, {val: aggInterval}); if (!setToInterval) { setToInterval = _.findWhere($scope.vis.type.params.intervalOptions, {customInterval: aggInterval}); } @@ -137,12 +140,12 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { if (_.get(timeAgg, ['params', 'interval', 'val']) !== 'custom') { setToInterval.val = _.get(timeAgg, ['params', 'interval', 'val']); - setToInterval.display = "Custom: " + _.get(timeAgg, ['params', 'interval', 'val']); + setToInterval.display = 'Custom: ' + _.get(timeAgg, ['params', 'interval', 'val']); } else { - setToInterval.val = "custom"; + setToInterval.val = 'custom'; setToInterval.customInterval = _.get(timeAgg, ['params', 'customInterval']); - setToInterval.display = "Custom: " + _.get(timeAgg, ['params', 'customInterval']); - } + setToInterval.display = 'Custom: ' + _.get(timeAgg, ['params', 'customInterval']); + } $scope.vis.type.params.intervalOptions.push(setToInterval); } @@ -150,32 +153,32 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { // Set the flags which indicate if the interval has been scaled. // e.g. if requesting points at 5 min interval would result in too many buckets being returned. - var timeBucketsInterval = timeAgg.buckets.getInterval(); + let timeBucketsInterval = timeAgg.buckets.getInterval(); setToInterval.scaled = timeBucketsInterval.scaled; setToInterval.scale = timeBucketsInterval.scale; setToInterval.description = timeBucketsInterval.description; - $scope.vis.params.interval = setToInterval; + $scope.vis.params.interval = setToInterval; } - $scope.updateViewState = function() { + $scope.updateViewState = function () { // Set up the visualization in response to a change in the Interval control. setupVisualization() .then(function () { // Re-run the dashboard search. return courier.fetch(); }) - .catch(function(error) { - console.log("Error updating swimlane visualization with new view state.", error); + .catch(function (error) { + console.log('Error updating swimlane visualization with new view state.', error); }); }; function setupVisualization() { // Set the params of the time aggregation to the selected 'interval' field. - if ($scope.vis) { + if ($scope.vis) { // Set the aggregation interval of the 'timeSplit' aggregation. - var visState = $scope.vis.getState(); - var timeAgg = _.find(visState.aggs, { 'schema': 'timeSplit' }); + let visState = $scope.vis.getState(); + let timeAgg = _.find(visState.aggs, { 'schema': 'timeSplit' }); timeAgg.params.interval = $scope.vis.params.interval.val; if ($scope.vis.params.interval.val === 'custom') { timeAgg.params.customInterval = $scope.vis.params.interval.customInterval; @@ -186,167 +189,166 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { // Update the time interval of the 'editable vis' // i.e. if visualization is being viewed in the Kibana Visualize view, // we need to update the configurations for the aggregations in the editor sidebar. - var editableVis = $scope.vis.getEditableVis(); + let editableVis = $scope.vis.getEditableVis(); if (editableVis) { - var editableVisState = editableVis.getState(); - var editableTimeAgg = _.find(editableVisState.aggs, { 'schema': 'timeSplit' }); + let editableVisState = editableVis.getState(); + let editableTimeAgg = _.find(editableVisState.aggs, { 'schema': 'timeSplit' }); editableTimeAgg.params.interval = $scope.vis.params.interval.val; if ($scope.vis.params.interval.val === 'custom') { editableTimeAgg.params.customInterval = $scope.vis.params.interval.customInterval; } editableVis.setState(editableVisState); - } + } return Promise.resolve($scope.vis); } } + $scope.prelertLogoSrc = chrome.getBasePath() + '/plugins/prelert_swimlane_vis/prelert_logo_24.png'; + }) -.directive('prlSwimlaneVis', function($compile, timefilter) { +.directive('prlSwimlaneVis', function ($compile, timefilter) { function link(scope, element, attrs) { scope._previousHoverPoint = null; scope._influencerHoverScope = null; - scope.$on('render',function(event, d){ - if (scope.vis.aggs.length !== 0 && scope.vis.aggs.bySchemaName['timeSplit'] !== undefined) { + scope.$on('render',function (event, d) { + if (scope.vis.aggs.length !== 0 && scope.vis.aggs.bySchemaName.timeSplit !== undefined) { renderSwimlane(); } }); - + function renderSwimlane() { - var chartData = scope.metricsData || []; - var allSeries = []; + let chartData = scope.metricsData || []; + let allSeries = []; - // Create a series for each severity color band, + // Create a series for each severity color band, // plus an 'unknown' series for scores less than the 'low' threshold. - var colorBands = ['#e6e6e6', '#d2e9f7', '#8bc8fb', '#ffdd00', '#ff7e00', '#fe5050']; - var seriesLabels = ['unknown','low','warning','minor','major','critical']; - _.each(colorBands, function(color, i){ - var series = {}; + const colorBands = ['#e6e6e6', '#d2e9f7', '#8bc8fb', '#ffdd00', '#ff7e00', '#fe5050']; + const seriesLabels = ['unknown','low','warning','minor','major','critical']; + _.each(colorBands, function (color, i) { + let series = {}; series.label = seriesLabels[i]; series.color = color; series.points = { fillColor: color, show: true, radius: 5, symbol: drawChartSymbol, lineWidth: 1 }; series.data = []; series.shadowSize = 0; allSeries.push(series); - }); + }); // Sort the lane labels in reverse so that the order is a-z from the top. chartData = sortChartDataByLaneLabel(chartData); - var laneIds = _.keys(chartData); + let laneIds = _.keys(chartData); - var laneIndex = 0; - _.each(chartData, function(bucketsForViewByValue, viewByValue) { + let laneIndex = 0; + _.each(chartData, function (bucketsForViewByValue, viewByValue) { laneIndex = laneIds.indexOf(viewByValue); - _.each(bucketsForViewByValue, function(dataForTime, time) { - var value = dataForTime.value; - - var pointData = new Array(); + _.each(bucketsForViewByValue, function (dataForTime, time) { + const value = dataForTime.value; + + const pointData = new Array(); pointData[0] = moment(Number(time)); pointData[1] = laneIndex + 0.5; // Store the score in an additional object property for each point. pointData[2] = {score: value}; - var seriesIndex = getSeriesIndex(value); + const seriesIndex = getSeriesIndex(value); allSeries[seriesIndex].data.push(pointData); - - }); + }); }); // Extract the bounds of the time filter so we can set the x-axis min and max. // If no min/max supplied, Flot will automatically set them according to the data values. - var bounds = timefilter.getActiveBounds(); - var earliest = null; - var latest = null; + const bounds = timefilter.getActiveBounds(); + let earliest = null; + let latest = null; if (bounds) { - var timeAgg = scope.vis.aggs.bySchemaName['timeSplit'][0]; - var aggInterval = timeAgg.buckets.getInterval(); + let timeAgg = scope.vis.aggs.bySchemaName.timeSplit[0]; + let aggInterval = timeAgg.buckets.getInterval(); // Elasticsearch aggregation returns points at start of bucket, // so set the x-axis min to the start of the aggregation interval. earliest = moment(bounds.min).startOf(aggInterval.description).valueOf(); latest = moment(bounds.max).valueOf(); - } - - - var options = { - xaxis: { - mode: "time", - timeformat: "%d %b %H:%M", - tickFormatter: function(v, axis) { - // Only show time if tick spacing is less than a day. - var tickGap = (axis.max - axis.min)/10000; // Approx 10 ticks, convert to sec. - if (tickGap < 86400) { - return moment(v).format('MMM D HH:mm'); - } else { - return moment(v).format('MMM D YYYY'); - } - }, - min: _.isUndefined(earliest) ? null : earliest, - max: _.isUndefined(latest) ? null : latest, - color: '#d5d5d5' - }, - yaxis: { - min: 0, - color: null, - tickColor: null, - tickLength: 0, - }, - grid: { - backgroundColor: null, - borderWidth: 1, - hoverable: true, - clickable: false, - borderColor: '#cccccc', - color: null, - }, - legend : { - show: false + } + + + const options = { + xaxis: { + mode: 'time', + timeformat: '%d %b %H:%M', + tickFormatter: function (v, axis) { + // Only show time if tick spacing is less than a day. + const tickGap = (axis.max - axis.min) / 10000; // Approx 10 ticks, convert to sec. + if (tickGap < 86400) { + return moment(v).format('MMM D HH:mm'); + } else { + return moment(v).format('MMM D YYYY'); + } }, - selection: { - mode: "x", - color: '#bbbbbb' - } + min: _.isUndefined(earliest) ? null : earliest, + max: _.isUndefined(latest) ? null : latest, + color: '#d5d5d5' + }, + yaxis: { + min: 0, + color: null, + tickColor: null, + tickLength: 0, + }, + grid: { + backgroundColor: null, + borderWidth: 1, + hoverable: true, + clickable: false, + borderColor: '#cccccc', + color: null, + }, + legend : { + show: false + }, + selection: { + mode: 'x', + color: '#bbbbbb' + } }; // Set the alternate lane marking color depending on whether Kibana dark theme is being used. - var alternateLaneColor = element.closest('.theme-dark').length === 0 ? '#f5f5f5' : "#4a4a4a"; - + const alternateLaneColor = element.closest('.theme-dark').length === 0 ? '#f5f5f5' : '#4a4a4a'; + options.yaxis.max = laneIds.length; options.yaxis.ticks = []; options.grid.markings = []; - var yaxisMarking; - _.each(laneIds, function(labelId, i){ - var labelText = labelId; + let yaxisMarking; + _.each(laneIds, function (labelId, i) { + let labelText = labelId; // Crop 'viewBy' labels over 27 chars of more so that the y-axis labels don't take up too much width. - var labelText = (labelText.length < 28 ? labelText : labelText.substring(0, 25) + "..."); - var tick = [i+0.5, labelText]; + labelText = (labelText.length < 28 ? labelText : labelText.substring(0, 25) + '...'); + let tick = [i + 0.5, labelText]; options.yaxis.ticks.push(tick); // Set up marking effects for each lane. if (i > 0) { yaxisMarking = {}; yaxisMarking.from = i; - yaxisMarking.to = i+0.03; - var marking = {yaxis: yaxisMarking, color: "#d5d5d5"}; - options.grid.markings.push(marking); + yaxisMarking.to = i + 0.03; + options.grid.markings.push({yaxis: yaxisMarking, color: '#d5d5d5'}); } - if (i % 2 != 0) { + if (i % 2 !== 0) { yaxisMarking = {}; - yaxisMarking.from = i+0.03; - yaxisMarking.to = i+1; - var marking = {yaxis: yaxisMarking, color: alternateLaneColor}; - options.grid.markings.push(marking); + yaxisMarking.from = i + 0.03; + yaxisMarking.to = i + 1; + options.grid.markings.push({yaxis: yaxisMarking, color: alternateLaneColor}); } }); @@ -355,36 +357,36 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { element.height((laneIds.length * 32) + 50); // Draw the plot. - var plot = $.plot(element, allSeries, options); + const plot = $.plot(element, allSeries, options); // Add tooltips to the y-axis labels to display the full 'viewBy' field // - useful for cases where a long text value has been cropped. // NB. requires z-index set in CSS so that hover is picked up on label. - var yAxisLabelDivs = $('.flot-y-axis', angular.element(element)).find('.flot-tick-label'); - _.each(laneIds, function(labelId, i) { - var labelText = labelId; + const yAxisLabelDivs = $('.flot-y-axis', angular.element(element)).find('.flot-tick-label'); + _.each(laneIds, function (labelId, i) { + const labelText = labelId; $(yAxisLabelDivs[i]).attr('title', labelText); }); // Show tooltips on point hover. - element.unbind("plothover"); - element.bind("plothover", function (event, pos, item) { + element.unbind('plothover'); + element.bind('plothover', function (event, pos, item) { if (item) { element.addClass('prl-swimlane-vis-point-over '); - if (scope._previousHoverPoint != item.dataIndex) { + if (scope._previousHoverPoint !== item.dataIndex) { scope._previousHoverPoint = item.dataIndex; - $(".prl-swimlane-vis-tooltip").remove(); + $('.prl-swimlane-vis-tooltip').remove(); if (scope._influencerHoverScope) { scope._influencerHoverScope.$destroy(); } - var laneIndex = item.series.data[item.dataIndex][1] - 0.5; - var laneLabel = laneIds[laneIndex]; + const laneIndex = item.series.data[item.dataIndex][1] - 0.5; + const laneLabel = laneIds[laneIndex]; showTooltip(item, laneLabel); } } else { element.removeClass('prl-swimlane-vis-point-over '); - $(".prl-swimlane-vis-tooltip").remove(); + $('.prl-swimlane-vis-tooltip').remove(); scope._previousHoverPoint = null; if (scope._influencerHoverScope) { scope._influencerHoverScope.$destroy(); @@ -393,19 +395,19 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { }); // Set the Kibana timefilter if the user selects a range on the chart. - element.unbind("plotselected"); - element.bind("plotselected", function (event, ranges) { - var zoomFrom = ranges.xaxis.from; - var zoomTo = ranges.xaxis.to; + element.unbind('plotselected'); + element.bind('plotselected', function (event, ranges) { + let zoomFrom = ranges.xaxis.from; + let zoomTo = ranges.xaxis.to; // Aggregation returns points at start of bucket, so make sure the time // range zoomed in to covers the full aggregation interval. - var timeAgg = scope.vis.aggs.bySchemaName['timeSplit'][0]; - var aggIntervalMs = timeAgg.buckets.getInterval().asMilliseconds(); + const timeAgg = scope.vis.aggs.bySchemaName.timeSplit[0]; + const aggIntervalMs = timeAgg.buckets.getInterval().asMilliseconds(); // Add a bit of extra padding before start time. - zoomFrom = zoomFrom - (aggIntervalMs/4); - zoomTo = zoomTo+aggIntervalMs; + zoomFrom = zoomFrom - (aggIntervalMs / 4); + zoomTo = zoomTo + aggIntervalMs; timefilter.time.from = moment.utc(zoomFrom); timefilter.time.to = moment.utc(zoomTo); @@ -413,7 +415,7 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { }); } - + function getSeriesIndex(value) { // Maps value to the index of the series used for values in that range. // Uses the five colour bands configured in the visualization options, @@ -435,15 +437,15 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { } if (value >= scope.vis.params.criticalThreshold) { return 5; - } + } } - + function sortChartDataByLaneLabel(list) { // Sorts chart data according to lane label. - var keys = _.sortBy(_.keys(list), function (key) { + let keys = _.sortBy(_.keys(list), function (key) { return key; }); - + // Reverse so that the order is a-z from the top. keys = keys.reverse(); @@ -453,43 +455,44 @@ module.controller('PrelertSwimlaneVisController', function($scope, courier) { } function drawChartSymbol(ctx, x, y, radius, shadow) { - var size = radius * Math.sqrt(Math.PI) / 2; + const size = radius * Math.sqrt(Math.PI) / 2; ctx.rect(x - size, y - 14, size + size, 28); } function showTooltip(item, laneLabel) { - var pointTime = item.datapoint[0]; - var dataModel = item.series.data[item.dataIndex][2]; - var score = parseInt(dataModel.score); - var metricsAgg = scope.vis.aggs.bySchemaName['metric'][0]; - var metricLabel = metricsAgg.makeLabel(); - var displayScore = numeral(dataModel.score).format(scope.vis.params.tooltipNumberFormat); + const pointTime = item.datapoint[0]; + const dataModel = item.series.data[item.dataIndex][2]; + const score = parseInt(dataModel.score); + const metricsAgg = scope.vis.aggs.bySchemaName.metric[0]; + const metricLabel = metricsAgg.makeLabel(); + const displayScore = numeral(dataModel.score).format(scope.vis.params.tooltipNumberFormat); // Display date using same format as used in Kibana visualizations. - var formattedDate = moment(pointTime).format('MMMM Do YYYY, HH:mm'); - var contents = formattedDate + "

"; - - contents += (metricLabel + ": " + displayScore); - - var x = item.pageX; - var y = item.pageY; - var offset = 5; - $("
" + contents + "
").css({ - "position": "absolute", - "display": "none", - "top": y + offset, - "left": x + offset - }).appendTo("body").fadeIn(200); + const formattedDate = moment(pointTime).format('MMMM Do YYYY, HH:mm'); + let contents = formattedDate + '

'; + + contents += (metricLabel + ': ' + displayScore); + + const x = item.pageX; + const y = item.pageY; + const offset = 5; + $('
' + contents + '
').css({ + 'position': 'absolute', + 'display': 'none', + 'z-index': 1, + 'top': y + offset, + 'left': x + offset + }).appendTo('body').fadeIn(200); // Position the tooltip. - var $win = $(window); - var winHeight = $win.height(); - var yOffset = window.pageYOffset; - var width = $(".prl-swimlane-vis-tooltip").outerWidth(true); - var height = $(".prl-swimlane-vis-tooltip").outerHeight(true); - - $(".prl-swimlane-vis-tooltip").css('left', x + offset + width > $win.width() ? x - offset - width : x + offset); - $(".prl-swimlane-vis-tooltip").css('top', y + height < winHeight + yOffset ? y : y -height); + const $win = $(window); + const winHeight = $win.height(); + const yOffset = window.pageYOffset; + const width = $('.prl-swimlane-vis-tooltip').outerWidth(true); + const height = $('.prl-swimlane-vis-tooltip').outerHeight(true); + + $('.prl-swimlane-vis-tooltip').css('left', x + offset + width > $win.width() ? x - offset - width : x + offset); + $('.prl-swimlane-vis-tooltip').css('top', y + height < winHeight + yOffset ? y : y - height); } }