From ef9dd014c783548c0b3df3e2460f63f75ceac2a6 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 31 Jul 2017 14:03:03 +0300 Subject: [PATCH 01/71] heatmap: add color scale options, issue #8539 --- .../panel/heatmap/partials/display_editor.html | 12 ++++++++++++ public/app/plugins/panel/heatmap/rendering.ts | 12 +++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/panel/heatmap/partials/display_editor.html b/public/app/plugins/panel/heatmap/partials/display_editor.html index 863fcc49d07..2c66b90b949 100644 --- a/public/app/plugins/panel/heatmap/partials/display_editor.html +++ b/public/app/plugins/panel/heatmap/partials/display_editor.html @@ -43,6 +43,18 @@ +
+
Color scale
+
+ + +
+
+ + +
+
+
Buckets
diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index de2c2fde719..c6f898fcb28 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -385,9 +385,11 @@ export default function link(scope, elem, attrs, ctrl) { } let cardsData = convertToCards(data.buckets); - let maxValue = d3.max(cardsData, card => card.count); + let maxValueAuto = d3.max(cardsData, card => card.count); + let maxValue = panel.color.max || maxValueAuto; + let minValue = panel.color.min || 0; - colorScale = getColorScale(maxValue); + colorScale = getColorScale(maxValue, minValue); setOpacityScale(maxValue); setCardSize(); @@ -434,14 +436,14 @@ export default function link(scope, elem, attrs, ctrl) { .style("stroke-width", 0); } - function getColorScale(maxValue) { + function getColorScale(maxValue, minValue = 0) { let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); let colorInterpolator = d3[colorScheme.value]; let colorScaleInverted = colorScheme.invert === 'always' || (colorScheme.invert === 'dark' && !contextSrv.user.lightTheme); - let start = colorScaleInverted ? maxValue : 0; - let end = colorScaleInverted ? 0 : maxValue; + let start = colorScaleInverted ? maxValue : minValue; + let end = colorScaleInverted ? minValue : maxValue; return d3.scaleSequential(colorInterpolator).domain([start, end]); } From 55b24be11585737911fc990c7bc2e46ebae13bc6 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 31 Jul 2017 17:45:05 +0300 Subject: [PATCH 02/71] heatmap: refactor, directive for color legend --- .../app/plugins/panel/heatmap/color_legend.ts | 136 ++++++++++++++++++ .../plugins/panel/heatmap/display_editor.ts | 6 + public/app/plugins/panel/heatmap/module.ts | 2 +- .../heatmap/partials/display_editor.html | 10 +- public/app/plugins/panel/heatmap/rendering.ts | 67 --------- 5 files changed, 147 insertions(+), 74 deletions(-) create mode 100644 public/app/plugins/panel/heatmap/color_legend.ts diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts new file mode 100644 index 00000000000..572d26c7330 --- /dev/null +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -0,0 +1,136 @@ +/// +import angular from 'angular'; +import _ from 'lodash'; +import $ from 'jquery'; +import d3 from 'd3'; +import {contextSrv} from 'app/core/core'; + +let module = angular.module('grafana.directives'); +module.directive('colorLegend', function() { + return { + restrict: 'E', + template: '
', + link: function(scope, elem, attrs) { + let ctrl = scope.ctrl; + let panel = scope.ctrl.panel; + + render(); + + ctrl.events.on('render', function() { + render(); + }); + + function render() { + let legendElem = $(elem).find('svg'); + let legendWidth = Math.floor(legendElem.outerWidth()); + + if (panel.color.mode === 'spectrum') { + let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); + let colorScale = getColorScale(colorScheme, legendWidth); + drawColorLegend(elem, colorScale); + } else if (panel.color.mode === 'opacity') { + let colorOptions = panel.color; + drawOpacityLegend(elem, colorOptions); + } + } + } + }; +}); + +module.directive('heatmapLegend', function() { + return { + restrict: 'E', + template: '
', + link: function(scope, elem, attrs) { + let ctrl = scope.ctrl; + let panel = scope.ctrl.panel; + + ctrl.events.on('render', function() { + if (!_.isEmpty(ctrl.data)) { + let legendElem = $(elem).find('svg'); + let legendWidth = Math.floor(legendElem.outerWidth()); + + if (panel.color.mode === 'spectrum') { + let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); + let colorScale = getColorScale(colorScheme, legendWidth); + drawColorLegend(elem, colorScale); + } else if (panel.color.mode === 'opacity') { + let colorOptions = panel.color; + drawOpacityLegend(elem, colorOptions); + } + } + }); + } + }; +}); + +function drawColorLegend(elem, colorScale) { + let legendElem = $(elem).find('svg'); + legendElem.find("rect").remove(); + + let legendWidth = Math.floor(legendElem.outerWidth()); + let legendHeight = legendElem.attr("height"); + + let rangeStep = 2; + let valuesRange = d3.range(0, legendWidth, rangeStep); + + let legend = d3.select(legendElem.get(0)); + var legendRects = legend.selectAll(".heatmap-color-legend-rect").data(valuesRange); + + legendRects.enter().append("rect") + .attr("x", d => d) + .attr("y", 0) + .attr("width", rangeStep + 1) // Overlap rectangles to prevent gaps + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", d => colorScale(d)); +} + +function clearLegend(elem) { + let legendElem = $(elem).find('svg'); + legendElem.find("rect").remove(); +} + +function drawOpacityLegend(elem, options) { + let legendElem = $(elem).find('svg'); + clearLegend(elem); + + let legend = d3.select(legendElem.get(0)); + let legendWidth = Math.floor(legendElem.outerWidth()); + let legendHeight = legendElem.attr("height"); + + let legendOpacityScale; + if (options.colorScale === 'linear') { + legendOpacityScale = d3.scaleLinear() + .domain([0, legendWidth]) + .range([0, 1]); + } else if (options.colorScale === 'sqrt') { + legendOpacityScale = d3.scalePow().exponent(options.exponent) + .domain([0, legendWidth]) + .range([0, 1]); + } + + let rangeStep = 1; + let valuesRange = d3.range(0, legendWidth, rangeStep); + var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange); + + legendRects.enter().append("rect") + .attr("x", d => d) + .attr("y", 0) + .attr("width", rangeStep) + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", options.cardColor) + .style("opacity", d => legendOpacityScale(d)); +} + +function getColorScale(colorScheme, maxValue, minValue = 0) { + let colorInterpolator = d3[colorScheme.value]; + let colorScaleInverted = colorScheme.invert === 'always' || + (colorScheme.invert === 'dark' && !contextSrv.user.lightTheme); + + let start = colorScaleInverted ? maxValue : minValue; + let end = colorScaleInverted ? minValue : maxValue; + + return d3.scaleSequential(colorInterpolator).domain([start, end]); +} diff --git a/public/app/plugins/panel/heatmap/display_editor.ts b/public/app/plugins/panel/heatmap/display_editor.ts index 775017b00da..1cea9505567 100644 --- a/public/app/plugins/panel/heatmap/display_editor.ts +++ b/public/app/plugins/panel/heatmap/display_editor.ts @@ -1,4 +1,10 @@ /// +import _ from 'lodash'; +import $ from 'jquery'; +import d3 from 'd3'; +import {contextSrv} from 'app/core/core'; + +const COLOR_LEGEND_SELECTOR = '.heatmap-color-legend'; export class HeatmapDisplayEditorCtrl { panel: any; diff --git a/public/app/plugins/panel/heatmap/module.ts b/public/app/plugins/panel/heatmap/module.ts index d6926455563..d5ef7291308 100644 --- a/public/app/plugins/panel/heatmap/module.ts +++ b/public/app/plugins/panel/heatmap/module.ts @@ -1,5 +1,5 @@ /// - +import './color_legend'; import {HeatmapCtrl} from './heatmap_ctrl'; export { diff --git a/public/app/plugins/panel/heatmap/partials/display_editor.html b/public/app/plugins/panel/heatmap/partials/display_editor.html index 2c66b90b949..4f215f3fcce 100644 --- a/public/app/plugins/panel/heatmap/partials/display_editor.html +++ b/public/app/plugins/panel/heatmap/partials/display_editor.html @@ -25,9 +25,6 @@
-
- -
@@ -37,9 +34,10 @@
-
- -
+ + +
+
diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index c6f898fcb28..74b433653f1 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -706,78 +706,11 @@ export default function link(scope, elem, attrs, ctrl) { } } - function drawColorLegend() { - d3.select("#heatmap-color-legend").selectAll("rect").remove(); - - let legend = d3.select("#heatmap-color-legend"); - let legendWidth = Math.floor($(d3.select("#heatmap-color-legend").node()).outerWidth()); - let legendHeight = d3.select("#heatmap-color-legend").attr("height"); - - let legendColorScale = getColorScale(legendWidth); - - let rangeStep = 2; - let valuesRange = d3.range(0, legendWidth, rangeStep); - var legendRects = legend.selectAll(".heatmap-color-legend-rect").data(valuesRange); - - legendRects.enter().append("rect") - .attr("x", d => d) - .attr("y", 0) - .attr("width", rangeStep + 1) // Overlap rectangles to prevent gaps - .attr("height", legendHeight) - .attr("stroke-width", 0) - .attr("fill", d => { - return legendColorScale(d); - }); - } - - function drawOpacityLegend() { - d3.select("#heatmap-opacity-legend").selectAll("rect").remove(); - - let legend = d3.select("#heatmap-opacity-legend"); - let legendWidth = Math.floor($(d3.select("#heatmap-opacity-legend").node()).outerWidth()); - let legendHeight = d3.select("#heatmap-opacity-legend").attr("height"); - - let legendOpacityScale; - if (panel.color.colorScale === 'linear') { - legendOpacityScale = d3.scaleLinear() - .domain([0, legendWidth]) - .range([0, 1]); - } else if (panel.color.colorScale === 'sqrt') { - legendOpacityScale = d3.scalePow().exponent(panel.color.exponent) - .domain([0, legendWidth]) - .range([0, 1]); - } - - let rangeStep = 1; - let valuesRange = d3.range(0, legendWidth, rangeStep); - var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange); - - legendRects.enter().append("rect") - .attr("x", d => d) - .attr("y", 0) - .attr("width", rangeStep) - .attr("height", legendHeight) - .attr("stroke-width", 0) - .attr("fill", panel.color.cardColor) - .style("opacity", d => { - return legendOpacityScale(d); - }); - } - function render() { data = ctrl.data; panel = ctrl.panel; timeRange = ctrl.range; - // Draw only if color editor is opened - if (!d3.select("#heatmap-color-legend").empty()) { - drawColorLegend(); - } - - if (!d3.select("#heatmap-opacity-legend").empty()) { - drawOpacityLegend(); - } - if (!setElementHeight() || !data) { return; } From c79a68dcd173395859b56b29ca35eba46a6f6037 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 31 Jul 2017 18:15:03 +0300 Subject: [PATCH 03/71] heatmap: refactor, build cards before rendering --- public/app/plugins/panel/heatmap/heatmap_ctrl.ts | 14 ++++++++++++-- public/app/plugins/panel/heatmap/rendering.ts | 6 +++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 37aa9410ea8..03bb46a104f 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -7,7 +7,7 @@ import TimeSeries from 'app/core/time_series'; import {axesEditor} from './axes_editor'; import {heatmapDisplayEditor} from './display_editor'; import rendering from './rendering'; -import { convertToHeatMap, elasticHistogramToHeatmap, calculateBucketSize, getMinLog} from './heatmap_data_converter'; +import {convertToHeatMap, convertToCards, elasticHistogramToHeatmap, calculateBucketSize, getMinLog} from './heatmap_data_converter'; let X_BUCKET_NUMBER_DEFAULT = 30; let Y_BUCKET_NUMBER_DEFAULT = 10; @@ -188,11 +188,21 @@ export class HeatmapCtrl extends MetricsPanelCtrl { yBucketSize = 1; } + let cardsData = convertToCards(bucketsData); + let maxCardsValue = _.max(_.map(cardsData, 'count')); + let minCardsValue = _.min(_.map(cardsData, 'count')); + let cardStats = { + max: maxCardsValue, + min: minCardsValue + }; + this.data = { buckets: bucketsData, heatmapStats: heatmapStats, xBucketSize: xBucketSize, - yBucketSize: yBucketSize + yBucketSize: yBucketSize, + cards: cardsData, + cardStats: cardStats }; } diff --git a/public/app/plugins/panel/heatmap/rendering.ts b/public/app/plugins/panel/heatmap/rendering.ts index 74b433653f1..8d85186b78e 100644 --- a/public/app/plugins/panel/heatmap/rendering.ts +++ b/public/app/plugins/panel/heatmap/rendering.ts @@ -8,7 +8,7 @@ import {appEvents, contextSrv} from 'app/core/core'; import {tickStep, getScaledDecimals, getFlotTickSize} from 'app/core/utils/ticks'; import d3 from 'd3'; import {HeatmapTooltip} from './heatmap_tooltip'; -import {convertToCards, mergeZeroBuckets} from './heatmap_data_converter'; +import {mergeZeroBuckets} from './heatmap_data_converter'; let MIN_CARD_SIZE = 1, CARD_PADDING = 1, @@ -384,8 +384,8 @@ export default function link(scope, elem, attrs, ctrl) { data.buckets = mergeZeroBuckets(data.buckets, _.min(tick_values)); } - let cardsData = convertToCards(data.buckets); - let maxValueAuto = d3.max(cardsData, card => card.count); + let cardsData = data.cards; + let maxValueAuto = data.cardStats.max; let maxValue = panel.color.max || maxValueAuto; let minValue = panel.color.min || 0; From 2aa26c98b6ba41bebb0671cb0c57c356d8040ae4 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 31 Jul 2017 20:11:55 +0300 Subject: [PATCH 04/71] heatmap: initial legend --- .../app/plugins/panel/heatmap/color_legend.ts | 229 ++++++++++++++---- public/app/plugins/panel/heatmap/module.html | 3 + public/sass/components/_panel_heatmap.scss | 37 +++ 3 files changed, 227 insertions(+), 42 deletions(-) diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 572d26c7330..0b23a10e000 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -4,12 +4,13 @@ import _ from 'lodash'; import $ from 'jquery'; import d3 from 'd3'; import {contextSrv} from 'app/core/core'; +import {tickStep} from 'app/core/utils/ticks'; let module = angular.module('grafana.directives'); module.directive('colorLegend', function() { return { restrict: 'E', - template: '
', + template: '
', link: function(scope, elem, attrs) { let ctrl = scope.ctrl; let panel = scope.ctrl.panel; @@ -27,10 +28,10 @@ module.directive('colorLegend', function() { if (panel.color.mode === 'spectrum') { let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); let colorScale = getColorScale(colorScheme, legendWidth); - drawColorLegend(elem, colorScale); + drawSimpleColorLegend(elem, colorScale); } else if (panel.color.mode === 'opacity') { let colorOptions = panel.color; - drawOpacityLegend(elem, colorOptions); + drawSimpleOpacityLegend(elem, colorOptions); } } } @@ -40,7 +41,7 @@ module.directive('colorLegend', function() { module.directive('heatmapLegend', function() { return { restrict: 'E', - template: '
', + template: '
', link: function(scope, elem, attrs) { let ctrl = scope.ctrl; let panel = scope.ctrl.panel; @@ -50,13 +51,19 @@ module.directive('heatmapLegend', function() { let legendElem = $(elem).find('svg'); let legendWidth = Math.floor(legendElem.outerWidth()); + // let maxValue = ctrl.data.cardStats.max || legendWidth; + let rangeFrom = ctrl.data.cardStats.min; + let rangeTo = ctrl.data.cardStats.max; + let maxValue = panel.color.max || rangeTo; + let minValue = panel.color.min || 0; + if (panel.color.mode === 'spectrum') { let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); - let colorScale = getColorScale(colorScheme, legendWidth); - drawColorLegend(elem, colorScale); + let colorScale = getColorScale(colorScheme, maxValue, minValue); + drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue); } else if (panel.color.mode === 'opacity') { let colorOptions = panel.color; - drawOpacityLegend(elem, colorOptions); + drawOpacityLegend(elem, colorOptions, rangeFrom, rangeTo, maxValue, minValue); } } }); @@ -64,34 +71,116 @@ module.directive('heatmapLegend', function() { }; }); -function drawColorLegend(elem, colorScale) { +function drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue) { let legendElem = $(elem).find('svg'); - legendElem.find("rect").remove(); + clearLegend(elem); - let legendWidth = Math.floor(legendElem.outerWidth()); + let legendWidth = Math.floor(legendElem.outerWidth()) - 30; let legendHeight = legendElem.attr("height"); - let rangeStep = 2; - let valuesRange = d3.range(0, legendWidth, rangeStep); + let rangeStep = 1; + if (rangeTo - rangeFrom > legendWidth) { + rangeStep = Math.floor((rangeTo - rangeFrom) / legendWidth); + } + let widthFactor = legendWidth / (rangeTo - rangeFrom); + let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); let legend = d3.select(legendElem.get(0)); var legendRects = legend.selectAll(".heatmap-color-legend-rect").data(valuesRange); legendRects.enter().append("rect") - .attr("x", d => d) - .attr("y", 0) - .attr("width", rangeStep + 1) // Overlap rectangles to prevent gaps - .attr("height", legendHeight) - .attr("stroke-width", 0) - .attr("fill", d => colorScale(d)); + .attr("x", d => d * widthFactor) + .attr("y", 0) + .attr("width", rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", d => colorScale(d)); + + drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth); +} + +function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue) { + let legendElem = $(elem).find('svg'); + clearLegend(elem); + + let legendWidth = Math.floor(legendElem.outerWidth()) - 30; + let legendHeight = legendElem.attr("height"); + + let rangeStep = 10; + let widthFactor = legendWidth / (rangeTo - rangeFrom); + let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); + let legend = d3.select(legendElem.get(0)); + var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange); + + let legendOpacityScale = getOpacityScale(options, maxValue, minValue); + legendRects.enter().append("rect") + .attr("x", d => d * widthFactor) + .attr("y", 0) + .attr("width", rangeStep * widthFactor) + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", options.cardColor) + .style("opacity", d => legendOpacityScale(d)); + + drawLegendValues(elem, legendOpacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth); +} + +function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) { + let legendElem = $(elem).find('svg'); + let legend = d3.select(legendElem.get(0)); + + let legendValueDomain = _.sortBy(colorScale.domain()); + let legendValueScale = d3.scaleLinear() + .domain([0, rangeTo]) + .range([0, legendWidth]); + + let ticks = buildLegendTicks(0, rangeTo, maxValue, minValue); + let xAxis = d3.axisBottom(legendValueScale) + .tickValues(ticks) + .tickSize(3); + + let legendElemHeight = legendElem.height(); + let posY = legendElemHeight - 23; + let posX = getSvgElemX(legendElem.find(":first-child")); + d3.select(legendElem.get(0)).append("g") + .attr("class", "axis") + .attr("transform", "translate(" + posX + "," + posY + ")") + .call(xAxis); + + legend.select(".axis").select(".domain").remove(); +} + +function drawSimpleColorLegend(elem, colorScale) { + let legendElem = $(elem).find('svg'); + clearLegend(elem); + + let legendWidth = Math.floor(legendElem.outerWidth()); + let legendHeight = legendElem.attr("height"); + + if (legendWidth) { + let valuesNumber = Math.floor(legendWidth / 2); + let rangeStep = Math.floor(legendWidth / valuesNumber); + let valuesRange = d3.range(0, legendWidth, rangeStep); + + let legend = d3.select(legendElem.get(0)); + var legendRects = legend.selectAll(".heatmap-color-legend-rect").data(valuesRange); + + legendRects.enter().append("rect") + .attr("x", d => d) + .attr("y", 0) + .attr("width", rangeStep + 1) // Overlap rectangles to prevent gaps + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", d => colorScale(d)); + } } function clearLegend(elem) { let legendElem = $(elem).find('svg'); - legendElem.find("rect").remove(); + legendElem.empty(); } -function drawOpacityLegend(elem, options) { +function drawSimpleOpacityLegend(elem, options) { let legendElem = $(elem).find('svg'); clearLegend(elem); @@ -99,29 +188,31 @@ function drawOpacityLegend(elem, options) { let legendWidth = Math.floor(legendElem.outerWidth()); let legendHeight = legendElem.attr("height"); - let legendOpacityScale; - if (options.colorScale === 'linear') { - legendOpacityScale = d3.scaleLinear() - .domain([0, legendWidth]) - .range([0, 1]); - } else if (options.colorScale === 'sqrt') { - legendOpacityScale = d3.scalePow().exponent(options.exponent) - .domain([0, legendWidth]) - .range([0, 1]); + if (legendWidth) { + let legendOpacityScale; + if (options.colorScale === 'linear') { + legendOpacityScale = d3.scaleLinear() + .domain([0, legendWidth]) + .range([0, 1]); + } else if (options.colorScale === 'sqrt') { + legendOpacityScale = d3.scalePow().exponent(options.exponent) + .domain([0, legendWidth]) + .range([0, 1]); + } + + let rangeStep = 10; + let valuesRange = d3.range(0, legendWidth, rangeStep); + var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange); + + legendRects.enter().append("rect") + .attr("x", d => d) + .attr("y", 0) + .attr("width", rangeStep) + .attr("height", legendHeight) + .attr("stroke-width", 0) + .attr("fill", options.cardColor) + .style("opacity", d => legendOpacityScale(d)); } - - let rangeStep = 1; - let valuesRange = d3.range(0, legendWidth, rangeStep); - var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange); - - legendRects.enter().append("rect") - .attr("x", d => d) - .attr("y", 0) - .attr("width", rangeStep) - .attr("height", legendHeight) - .attr("stroke-width", 0) - .attr("fill", options.cardColor) - .style("opacity", d => legendOpacityScale(d)); } function getColorScale(colorScheme, maxValue, minValue = 0) { @@ -134,3 +225,57 @@ function getColorScale(colorScheme, maxValue, minValue = 0) { return d3.scaleSequential(colorInterpolator).domain([start, end]); } + +function getOpacityScale(options, maxValue, minValue = 0) { + let legendOpacityScale; + if (options.colorScale === 'linear') { + legendOpacityScale = d3.scaleLinear() + .domain([minValue, maxValue]) + .range([0, 1]); + } else if (options.colorScale === 'sqrt') { + legendOpacityScale = d3.scalePow().exponent(options.exponent) + .domain([minValue, maxValue]) + .range([0, 1]); + } + return legendOpacityScale; +} + +function getSvgElemX(elem) { + return elem.get(0).x.baseVal.value; +} + +function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) { + let range = rangeTo - rangeFrom; + let tickStepSize = tickStep(rangeFrom, rangeTo, 3); + let ticksNum = Math.floor(range / tickStepSize); + let ticks = []; + + for (let i = 0; i < ticksNum; i++) { + let current = tickStepSize * i; + // Add user-defined min and max if it had been set + if (isValueCloseTo(minValue, current, tickStepSize)) { + ticks.push(minValue); + continue; + } else if (minValue < current) { + ticks.push(minValue); + } + if (isValueCloseTo(maxValue, current, tickStepSize)) { + ticks.push(maxValue); + continue; + } else if (maxValue < current) { + ticks.push(maxValue); + } + ticks.push(tickStepSize * i); + } + if (!isValueCloseTo(maxValue, rangeTo, tickStepSize)) { + ticks.push(maxValue); + } + ticks.push(rangeTo); + ticks = _.sortBy(_.uniq(ticks)); + return ticks; +} + +function isValueCloseTo(val, valueTo, step) { + let diff = Math.abs(val - valueTo); + return diff < step * 0.3; +} diff --git a/public/app/plugins/panel/heatmap/module.html b/public/app/plugins/panel/heatmap/module.html index 6cb89f2e1f2..ac9652a5afc 100644 --- a/public/app/plugins/panel/heatmap/module.html +++ b/public/app/plugins/panel/heatmap/module.html @@ -7,5 +7,8 @@
+
+ +
diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss index 9d07e6a363c..9ca7b6f4bba 100644 --- a/public/sass/components/_panel_heatmap.scss +++ b/public/sass/components/_panel_heatmap.scss @@ -46,3 +46,40 @@ stroke-width: 1; } } + +.heatmap-legend-wrapper { + @include clearfix(); + margin: 0 $spacer; + padding-top: 10px; + + svg { + width: 100%; + max-width: 300px; + height: 38px; + float: left; + white-space: nowrap; + padding-left: 10px; + } + + .heatmap-legend-values { + display: inline-block; + } + + .axis .tick { + text { + fill: $text-color; + color: $text-color; + font-size: $font-size-sm; + } + + line { + opacity: 0.4; + stroke: $text-color-weak; + } + + .domain { + opacity: 0.4; + stroke: $text-color-weak; + } + } +} From e72baca4a714076ffb375eb8cd6e6c638d08bbda Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 13:54:09 +0300 Subject: [PATCH 05/71] heatmap: fix black selection area --- public/sass/components/_panel_heatmap.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss index 9ca7b6f4bba..e593f6e2bf8 100644 --- a/public/sass/components/_panel_heatmap.scss +++ b/public/sass/components/_panel_heatmap.scss @@ -47,6 +47,13 @@ } } +.heatmap-selection { + stroke-width: 1; + opacity: 0.3; + fill: #828282; + stroke: darken($red,15%); +} + .heatmap-legend-wrapper { @include clearfix(); margin: 0 $spacer; From 91a921e12d51f1d75275a1ea556102cedabeb37b Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 14:07:52 +0300 Subject: [PATCH 06/71] heatmap: add show legend option --- public/app/plugins/panel/heatmap/color_legend.ts | 14 ++++++++++++-- public/app/plugins/panel/heatmap/heatmap_ctrl.ts | 3 +++ public/app/plugins/panel/heatmap/module.html | 2 +- .../panel/heatmap/partials/display_editor.html | 8 ++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 0b23a10e000..2d52acb8ddb 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -46,7 +46,12 @@ module.directive('heatmapLegend', function() { let ctrl = scope.ctrl; let panel = scope.ctrl.panel; + render(); ctrl.events.on('render', function() { + render(); + }); + + function render() { if (!_.isEmpty(ctrl.data)) { let legendElem = $(elem).find('svg'); let legendWidth = Math.floor(legendElem.outerWidth()); @@ -66,7 +71,7 @@ module.directive('heatmapLegend', function() { drawOpacityLegend(elem, colorOptions, rangeFrom, rangeTo, maxValue, minValue); } } - }); + } } }; }); @@ -241,7 +246,12 @@ function getOpacityScale(options, maxValue, minValue = 0) { } function getSvgElemX(elem) { - return elem.get(0).x.baseVal.value; + let svgElem = elem.get(0); + if (svgElem && svgElem.x && svgElem.x.baseVal) { + return elem.get(0).x.baseVal.value; + } else { + return 0; + } } function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) { diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 03bb46a104f..3b8d1fa9d41 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -26,6 +26,9 @@ let panelDefaults = { exponent: 0.5, colorScheme: 'interpolateOranges', }, + legend: { + show: false + }, dataFormat: 'timeseries', xAxis: { show: true, diff --git a/public/app/plugins/panel/heatmap/module.html b/public/app/plugins/panel/heatmap/module.html index ac9652a5afc..272aac99772 100644 --- a/public/app/plugins/panel/heatmap/module.html +++ b/public/app/plugins/panel/heatmap/module.html @@ -8,7 +8,7 @@
- +
diff --git a/public/app/plugins/panel/heatmap/partials/display_editor.html b/public/app/plugins/panel/heatmap/partials/display_editor.html index 4f215f3fcce..f161bf6cab4 100644 --- a/public/app/plugins/panel/heatmap/partials/display_editor.html +++ b/public/app/plugins/panel/heatmap/partials/display_editor.html @@ -53,6 +53,14 @@ +
+
Legend
+ + +
+
Buckets
From f64ebf538c11672bc389ebad59cca0a8ea0b26b8 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 15:25:52 +0300 Subject: [PATCH 07/71] heatmap: minor legend fixes --- .../app/plugins/panel/heatmap/color_legend.ts | 17 +++++++++++------ public/sass/components/_panel_heatmap.scss | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 2d52acb8ddb..0cbbddd0317 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -52,7 +52,8 @@ module.directive('heatmapLegend', function() { }); function render() { - if (!_.isEmpty(ctrl.data)) { + clearLegend(elem); + if (!_.isEmpty(ctrl.data) && !_.isEmpty(ctrl.data.cards)) { let legendElem = $(elem).find('svg'); let legendWidth = Math.floor(legendElem.outerWidth()); @@ -131,6 +132,10 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue } function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) { + if (legendWidth <= 0) { + return; + } + let legendElem = $(elem).find('svg'); let legend = d3.select(legendElem.get(0)); @@ -142,11 +147,11 @@ function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minVal let ticks = buildLegendTicks(0, rangeTo, maxValue, minValue); let xAxis = d3.axisBottom(legendValueScale) .tickValues(ticks) - .tickSize(3); + .tickSize(2); - let legendElemHeight = legendElem.height(); - let posY = legendElemHeight - 23; - let posX = getSvgElemX(legendElem.find(":first-child")); + let colorRect = legendElem.find(":first-child"); + let posY = colorRect.height() + 2; + let posX = getSvgElemX(colorRect); d3.select(legendElem.get(0)).append("g") .attr("class", "axis") .attr("transform", "translate(" + posX + "," + posY + ")") @@ -257,7 +262,7 @@ function getSvgElemX(elem) { function buildLegendTicks(rangeFrom, rangeTo, maxValue, minValue) { let range = rangeTo - rangeFrom; let tickStepSize = tickStep(rangeFrom, rangeTo, 3); - let ticksNum = Math.floor(range / tickStepSize); + let ticksNum = Math.round(range / tickStepSize); let ticks = []; for (let i = 0; i < ticksNum; i++) { diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss index e593f6e2bf8..3fa2e331c7b 100644 --- a/public/sass/components/_panel_heatmap.scss +++ b/public/sass/components/_panel_heatmap.scss @@ -62,7 +62,7 @@ svg { width: 100%; max-width: 300px; - height: 38px; + height: 33px; float: left; white-space: nowrap; padding-left: 10px; From b01a4e65839878b99c889bee37171d8366d5ca77 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 16:21:40 +0300 Subject: [PATCH 08/71] heatmap: use the same color for selection as for graph --- public/sass/components/_panel_heatmap.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/sass/components/_panel_heatmap.scss b/public/sass/components/_panel_heatmap.scss index 3fa2e331c7b..134186f25a2 100644 --- a/public/sass/components/_panel_heatmap.scss +++ b/public/sass/components/_panel_heatmap.scss @@ -49,9 +49,8 @@ .heatmap-selection { stroke-width: 1; - opacity: 0.3; - fill: #828282; - stroke: darken($red,15%); + fill: rgba(102, 102, 102, 0.4); + stroke: rgba(102, 102, 102, 0.8); } .heatmap-legend-wrapper { From 663a3293eed813c746617dfeedac37ad55fcbde4 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 16:23:01 +0300 Subject: [PATCH 09/71] heatmap: some legend fixes --- .../app/plugins/panel/heatmap/color_legend.ts | 58 ++++++++++--------- public/app/plugins/panel/heatmap/module.html | 4 +- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/public/app/plugins/panel/heatmap/color_legend.ts b/public/app/plugins/panel/heatmap/color_legend.ts index 0cbbddd0317..60bbe38d689 100644 --- a/public/app/plugins/panel/heatmap/color_legend.ts +++ b/public/app/plugins/panel/heatmap/color_legend.ts @@ -7,6 +7,10 @@ import {contextSrv} from 'app/core/core'; import {tickStep} from 'app/core/utils/ticks'; let module = angular.module('grafana.directives'); + +/** + * Color legend for heatmap editor. + */ module.directive('colorLegend', function() { return { restrict: 'E', @@ -38,6 +42,9 @@ module.directive('colorLegend', function() { }; }); +/** + * Heatmap legend with scale values. + */ module.directive('heatmapLegend', function() { return { restrict: 'E', @@ -54,19 +61,14 @@ module.directive('heatmapLegend', function() { function render() { clearLegend(elem); if (!_.isEmpty(ctrl.data) && !_.isEmpty(ctrl.data.cards)) { - let legendElem = $(elem).find('svg'); - let legendWidth = Math.floor(legendElem.outerWidth()); - - // let maxValue = ctrl.data.cardStats.max || legendWidth; - let rangeFrom = ctrl.data.cardStats.min; + let rangeFrom = 0; let rangeTo = ctrl.data.cardStats.max; let maxValue = panel.color.max || rangeTo; let minValue = panel.color.min || 0; if (panel.color.mode === 'spectrum') { let colorScheme = _.find(ctrl.colorSchemes, {value: panel.color.colorScheme}); - let colorScale = getColorScale(colorScheme, maxValue, minValue); - drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue); + drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue); } else if (panel.color.mode === 'opacity') { let colorOptions = panel.color; drawOpacityLegend(elem, colorOptions, rangeFrom, rangeTo, maxValue, minValue); @@ -77,8 +79,9 @@ module.directive('heatmapLegend', function() { }; }); -function drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue) { +function drawColorLegend(elem, colorScheme, rangeFrom, rangeTo, maxValue, minValue) { let legendElem = $(elem).find('svg'); + let legend = d3.select(legendElem.get(0)); clearLegend(elem); let legendWidth = Math.floor(legendElem.outerWidth()) - 30; @@ -91,10 +94,10 @@ function drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValu let widthFactor = legendWidth / (rangeTo - rangeFrom); let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); - let legend = d3.select(legendElem.get(0)); - var legendRects = legend.selectAll(".heatmap-color-legend-rect").data(valuesRange); - - legendRects.enter().append("rect") + let colorScale = getColorScale(colorScheme, maxValue, minValue); + legend.selectAll(".heatmap-color-legend-rect") + .data(valuesRange) + .enter().append("rect") .attr("x", d => d * widthFactor) .attr("y", 0) .attr("width", rangeStep * widthFactor + 1) // Overlap rectangles to prevent gaps @@ -107,6 +110,7 @@ function drawColorLegend(elem, colorScale, rangeFrom, rangeTo, maxValue, minValu function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue) { let legendElem = $(elem).find('svg'); + let legend = d3.select(legendElem.get(0)); clearLegend(elem); let legendWidth = Math.floor(legendElem.outerWidth()) - 30; @@ -115,30 +119,30 @@ function drawOpacityLegend(elem, options, rangeFrom, rangeTo, maxValue, minValue let rangeStep = 10; let widthFactor = legendWidth / (rangeTo - rangeFrom); let valuesRange = d3.range(rangeFrom, rangeTo, rangeStep); - let legend = d3.select(legendElem.get(0)); - var legendRects = legend.selectAll(".heatmap-opacity-legend-rect").data(valuesRange); - let legendOpacityScale = getOpacityScale(options, maxValue, minValue); - legendRects.enter().append("rect") + let opacityScale = getOpacityScale(options, maxValue, minValue); + legend.selectAll(".heatmap-opacity-legend-rect") + .data(valuesRange) + .enter().append("rect") .attr("x", d => d * widthFactor) .attr("y", 0) .attr("width", rangeStep * widthFactor) .attr("height", legendHeight) .attr("stroke-width", 0) .attr("fill", options.cardColor) - .style("opacity", d => legendOpacityScale(d)); + .style("opacity", d => opacityScale(d)); - drawLegendValues(elem, legendOpacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth); + drawLegendValues(elem, opacityScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth); } function drawLegendValues(elem, colorScale, rangeFrom, rangeTo, maxValue, minValue, legendWidth) { - if (legendWidth <= 0) { - return; - } - let legendElem = $(elem).find('svg'); let legend = d3.select(legendElem.get(0)); + if (legendWidth <= 0 || legendElem.get(0).childNodes.length === 0) { + return; + } + let legendValueDomain = _.sortBy(colorScale.domain()); let legendValueScale = d3.scaleLinear() .domain([0, rangeTo]) @@ -185,11 +189,6 @@ function drawSimpleColorLegend(elem, colorScale) { } } -function clearLegend(elem) { - let legendElem = $(elem).find('svg'); - legendElem.empty(); -} - function drawSimpleOpacityLegend(elem, options) { let legendElem = $(elem).find('svg'); clearLegend(elem); @@ -225,6 +224,11 @@ function drawSimpleOpacityLegend(elem, options) { } } +function clearLegend(elem) { + let legendElem = $(elem).find('svg'); + legendElem.empty(); +} + function getColorScale(colorScheme, maxValue, minValue = 0) { let colorInterpolator = d3[colorScheme.value]; let colorScaleInverted = colorScheme.invert === 'always' || diff --git a/public/app/plugins/panel/heatmap/module.html b/public/app/plugins/panel/heatmap/module.html index 272aac99772..5b5c5296ca1 100644 --- a/public/app/plugins/panel/heatmap/module.html +++ b/public/app/plugins/panel/heatmap/module.html @@ -7,8 +7,8 @@
-
- +
+
From f7ea08dba710cfe950d94595a3bc4d4ece89b300 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 1 Aug 2017 16:51:55 +0300 Subject: [PATCH 10/71] heatmap: fix rendering tests --- .../plugins/panel/heatmap/specs/renderer_specs.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts index 5d3eb665e55..884ee1e81f4 100644 --- a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts +++ b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts @@ -11,8 +11,7 @@ import TimeSeries from 'app/core/time_series2'; import moment from 'moment'; import { Emitter } from 'app/core/core'; import rendering from '../rendering'; -import { convertToHeatMap } from '../heatmap_data_converter'; -// import d3 from 'd3'; +import {convertToHeatMap, convertToCards} from '../heatmap_data_converter'; describe('grafanaHeatmap', function () { @@ -115,8 +114,15 @@ describe('grafanaHeatmap', function () { let bucketsData = convertToHeatMap(ctx.series, ctx.data.yBucketSize, ctx.data.xBucketSize, logBase); ctx.data.buckets = bucketsData; - // console.log("bucketsData", bucketsData); - // console.log("series", ctrl.panel.yAxis.logBase, ctx.series.length); + let cardsData = convertToCards(bucketsData); + let maxCardsValue = _.max(_.map(cardsData, 'count')); + let minCardsValue = _.min(_.map(cardsData, 'count')); + let cardStats = { + max: maxCardsValue, + min: minCardsValue + }; + ctx.data.cards = cardsData; + ctx.data.cardStats = cardStats; let elemHtml = `
From 6224a25e42edb9618bdbf5c69c5ec249adaa5342 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Aug 2017 11:16:37 +0200 Subject: [PATCH 11/71] feat: Elasticsearch filtering wip, moved to typescript --- .../datasource/elasticsearch/datasource.js | 2 + .../elasticsearch/elastic_response.d.ts | 2 - .../elasticsearch/elastic_response.js | 350 ------------------ .../elasticsearch/elastic_response.ts | 346 +++++++++++++++++ .../specs/elastic_response_specs.ts | 2 +- 5 files changed, 349 insertions(+), 353 deletions(-) delete mode 100644 public/app/plugins/datasource/elasticsearch/elastic_response.d.ts delete mode 100644 public/app/plugins/datasource/elasticsearch/elastic_response.js create mode 100644 public/app/plugins/datasource/elasticsearch/elastic_response.ts diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 49398a894e7..5f3ce0fa361 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -11,6 +11,8 @@ define([ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticResponse) { 'use strict'; + ElasticResponse = ElasticResponse.ElasticResponse; + /** @ngInject */ function ElasticDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) { this.basicAuth = instanceSettings.basicAuth; diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.d.ts b/public/app/plugins/datasource/elasticsearch/elastic_response.d.ts deleted file mode 100644 index c3318b8e133..00000000000 --- a/public/app/plugins/datasource/elasticsearch/elastic_response.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare var test: any; -export default test; diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.js b/public/app/plugins/datasource/elasticsearch/elastic_response.js deleted file mode 100644 index 9e944774dc9..00000000000 --- a/public/app/plugins/datasource/elasticsearch/elastic_response.js +++ /dev/null @@ -1,350 +0,0 @@ -define([ - "lodash", - "./query_def" -], -function (_, queryDef) { - 'use strict'; - - function ElasticResponse(targets, response) { - this.targets = targets; - this.response = response; - } - - ElasticResponse.prototype.processMetrics = function(esAgg, target, seriesList, props) { - var metric, y, i, newSeries, bucket, value; - - for (y = 0; y < target.metrics.length; y++) { - metric = target.metrics[y]; - if (metric.hide) { - continue; - } - - switch(metric.type) { - case 'count': { - newSeries = { datapoints: [], metric: 'count', props: props}; - for (i = 0; i < esAgg.buckets.length; i++) { - bucket = esAgg.buckets[i]; - value = bucket.doc_count; - newSeries.datapoints.push([value, bucket.key]); - } - seriesList.push(newSeries); - break; - } - case 'percentiles': { - if (esAgg.buckets.length === 0) { - break; - } - - var firstBucket = esAgg.buckets[0]; - var percentiles = firstBucket[metric.id].values; - - for (var percentileName in percentiles) { - newSeries = {datapoints: [], metric: 'p' + percentileName, props: props, field: metric.field}; - - for (i = 0; i < esAgg.buckets.length; i++) { - bucket = esAgg.buckets[i]; - var values = bucket[metric.id].values; - newSeries.datapoints.push([values[percentileName], bucket.key]); - } - seriesList.push(newSeries); - } - - break; - } - case 'extended_stats': { - for (var statName in metric.meta) { - if (!metric.meta[statName]) { - continue; - } - - newSeries = {datapoints: [], metric: statName, props: props, field: metric.field}; - - for (i = 0; i < esAgg.buckets.length; i++) { - bucket = esAgg.buckets[i]; - var stats = bucket[metric.id]; - - // add stats that are in nested obj to top level obj - stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper; - stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower; - - newSeries.datapoints.push([stats[statName], bucket.key]); - } - - seriesList.push(newSeries); - } - - break; - } - default: { - newSeries = { datapoints: [], metric: metric.type, field: metric.field, props: props}; - for (i = 0; i < esAgg.buckets.length; i++) { - bucket = esAgg.buckets[i]; - - value = bucket[metric.id]; - if (value !== undefined) { - if (value.normalized_value) { - newSeries.datapoints.push([value.normalized_value, bucket.key]); - } else { - newSeries.datapoints.push([value.value, bucket.key]); - } - } - - } - seriesList.push(newSeries); - break; - } - } - } - }; - - ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, target, docs, props) { - var metric, y, i, bucket, metricName, doc; - - for (i = 0; i < esAgg.buckets.length; i++) { - bucket = esAgg.buckets[i]; - doc = _.defaults({}, props); - doc[aggDef.field] = bucket.key; - - for (y = 0; y < target.metrics.length; y++) { - metric = target.metrics[y]; - - switch(metric.type) { - case "count": { - metricName = this._getMetricName(metric.type); - doc[metricName] = bucket.doc_count; - break; - } - case 'extended_stats': { - for (var statName in metric.meta) { - if (!metric.meta[statName]) { - continue; - } - - var stats = bucket[metric.id]; - // add stats that are in nested obj to top level obj - stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper; - stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower; - - metricName = this._getMetricName(statName); - doc[metricName] = stats[statName]; - } - break; - } - default: { - metricName = this._getMetricName(metric.type); - var otherMetrics = _.filter(target.metrics, {type: metric.type}); - - // if more of the same metric type include field field name in property - if (otherMetrics.length > 1) { - metricName += ' ' + metric.field; - } - - doc[metricName] = bucket[metric.id].value; - break; - } - } - } - - docs.push(doc); - } - }; - - // This is quite complex - // neeed to recurise down the nested buckets to build series - ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, docs, props, depth) { - var bucket, aggDef, esAgg, aggId; - var maxDepth = target.bucketAggs.length-1; - - for (aggId in aggs) { - aggDef = _.find(target.bucketAggs, {id: aggId}); - esAgg = aggs[aggId]; - - if (!aggDef) { - continue; - } - - if (depth === maxDepth) { - if (aggDef.type === 'date_histogram') { - this.processMetrics(esAgg, target, seriesList, props); - } else { - this.processAggregationDocs(esAgg, aggDef, target, docs, props); - } - } else { - for (var nameIndex in esAgg.buckets) { - bucket = esAgg.buckets[nameIndex]; - props = _.clone(props); - if (bucket.key !== void 0) { - props[aggDef.field] = bucket.key; - } else { - props["filter"] = nameIndex; - } - if (bucket.key_as_string) { - props[aggDef.field] = bucket.key_as_string; - } - this.processBuckets(bucket, target, seriesList, docs, props, depth+1); - } - } - } - }; - - ElasticResponse.prototype._getMetricName = function(metric) { - var metricDef = _.find(queryDef.metricAggTypes, {value: metric}); - if (!metricDef) { - metricDef = _.find(queryDef.extendedStats, {value: metric}); - } - - return metricDef ? metricDef.text : metric; - }; - - ElasticResponse.prototype._getSeriesName = function(series, target, metricTypeCount) { - var metricName = this._getMetricName(series.metric); - - if (target.alias) { - var regex = /\{\{([\s\S]+?)\}\}/g; - - return target.alias.replace(regex, function(match, g1, g2) { - var group = g1 || g2; - - if (group.indexOf('term ') === 0) { return series.props[group.substring(5)]; } - if (series.props[group] !== void 0) { return series.props[group]; } - if (group === 'metric') { return metricName; } - if (group === 'field') { return series.field; } - - return match; - }); - } - - if (series.field && queryDef.isPipelineAgg(series.metric)) { - var appliedAgg = _.find(target.metrics, { id: series.field }); - if (appliedAgg) { - metricName += ' ' + queryDef.describeMetric(appliedAgg); - } else { - metricName = 'Unset'; - } - } else if (series.field) { - metricName += ' ' + series.field; - } - - var propKeys = _.keys(series.props); - if (propKeys.length === 0) { - return metricName; - } - - var name = ''; - for (var propName in series.props) { - name += series.props[propName] + ' '; - } - - if (metricTypeCount === 1) { - return name.trim(); - } - - return name.trim() + ' ' + metricName; - }; - - ElasticResponse.prototype.nameSeries = function(seriesList, target) { - var metricTypeCount = _.uniq(_.map(seriesList, 'metric')).length; - var fieldNameCount = _.uniq(_.map(seriesList, 'field')).length; - - for (var i = 0; i < seriesList.length; i++) { - var series = seriesList[i]; - series.target = this._getSeriesName(series, target, metricTypeCount, fieldNameCount); - } - }; - - ElasticResponse.prototype.processHits = function(hits, seriesList) { - var series = {target: 'docs', type: 'docs', datapoints: [], total: hits.total}; - var propName, hit, doc, i; - - for (i = 0; i < hits.hits.length; i++) { - hit = hits.hits[i]; - doc = { - _id: hit._id, - _type: hit._type, - _index: hit._index - }; - - if (hit._source) { - for (propName in hit._source) { - doc[propName] = hit._source[propName]; - } - } - - for (propName in hit.fields) { - doc[propName] = hit.fields[propName]; - } - series.datapoints.push(doc); - } - - seriesList.push(series); - }; - - ElasticResponse.prototype.trimDatapoints = function(aggregations, target) { - var histogram = _.find(target.bucketAggs, { type: 'date_histogram'}); - - var shouldDropFirstAndLast = histogram && histogram.settings && histogram.settings.trimEdges; - if (shouldDropFirstAndLast) { - var trim = histogram.settings.trimEdges; - for(var prop in aggregations) { - var points = aggregations[prop]; - if (points.datapoints.length > trim * 2) { - points.datapoints = points.datapoints.slice(trim, points.datapoints.length - trim); - } - } - } - }; - - ElasticResponse.prototype.getErrorFromElasticResponse = function(response, err) { - var result = {}; - result.data = JSON.stringify(err, null, 4); - if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) { - result.message = err.root_cause[0].reason; - } else { - result.message = err.reason || 'Unkown elatic error response'; - } - - if (response.$$config) { - result.config = response.$$config; - } - - return result; - }; - - ElasticResponse.prototype.getTimeSeries = function() { - var seriesList = []; - - for (var i = 0; i < this.response.responses.length; i++) { - var response = this.response.responses[i]; - if (response.error) { - throw this.getErrorFromElasticResponse(this.response, response.error); - } - - if (response.hits && response.hits.hits.length > 0) { - this.processHits(response.hits, seriesList); - } - - if (response.aggregations) { - var aggregations = response.aggregations; - var target = this.targets[i]; - var tmpSeriesList = []; - var docs = []; - - this.processBuckets(aggregations, target, tmpSeriesList, docs, {}, 0); - this.trimDatapoints(tmpSeriesList, target); - this.nameSeries(tmpSeriesList, target); - - for (var y = 0; y < tmpSeriesList.length; y++) { - seriesList.push(tmpSeriesList[y]); - } - - if (seriesList.length === 0 && docs.length > 0) { - seriesList.push({target: 'docs', type: 'docs', datapoints: docs}); - } - } - } - - return { data: seriesList }; - }; - - return ElasticResponse; -}); diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.ts b/public/app/plugins/datasource/elasticsearch/elastic_response.ts new file mode 100644 index 00000000000..04cfe20a9c1 --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/elastic_response.ts @@ -0,0 +1,346 @@ +/// + +import _ from 'lodash'; +import queryDef from "./query_def"; + +export function ElasticResponse(targets, response) { + this.targets = targets; + this.response = response; +} + +ElasticResponse.prototype.processMetrics = function(esAgg, target, seriesList, props) { + var metric, y, i, newSeries, bucket, value; + + for (y = 0; y < target.metrics.length; y++) { + metric = target.metrics[y]; + if (metric.hide) { + continue; + } + + switch (metric.type) { + case 'count': { + newSeries = { datapoints: [], metric: 'count', props: props}; + for (i = 0; i < esAgg.buckets.length; i++) { + bucket = esAgg.buckets[i]; + value = bucket.doc_count; + newSeries.datapoints.push([value, bucket.key]); + } + seriesList.push(newSeries); + break; + } + case 'percentiles': { + if (esAgg.buckets.length === 0) { + break; + } + + var firstBucket = esAgg.buckets[0]; + var percentiles = firstBucket[metric.id].values; + + for (var percentileName in percentiles) { + newSeries = {datapoints: [], metric: 'p' + percentileName, props: props, field: metric.field}; + + for (i = 0; i < esAgg.buckets.length; i++) { + bucket = esAgg.buckets[i]; + var values = bucket[metric.id].values; + newSeries.datapoints.push([values[percentileName], bucket.key]); + } + seriesList.push(newSeries); + } + + break; + } + case 'extended_stats': { + for (var statName in metric.meta) { + if (!metric.meta[statName]) { + continue; + } + + newSeries = {datapoints: [], metric: statName, props: props, field: metric.field}; + + for (i = 0; i < esAgg.buckets.length; i++) { + bucket = esAgg.buckets[i]; + var stats = bucket[metric.id]; + + // add stats that are in nested obj to top level obj + stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper; + stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower; + + newSeries.datapoints.push([stats[statName], bucket.key]); + } + + seriesList.push(newSeries); + } + + break; + } + default: { + newSeries = { datapoints: [], metric: metric.type, field: metric.field, props: props}; + for (i = 0; i < esAgg.buckets.length; i++) { + bucket = esAgg.buckets[i]; + + value = bucket[metric.id]; + if (value !== undefined) { + if (value.normalized_value) { + newSeries.datapoints.push([value.normalized_value, bucket.key]); + } else { + newSeries.datapoints.push([value.value, bucket.key]); + } + } + + } + seriesList.push(newSeries); + break; + } + } + } +}; + +ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, target, docs, props) { + var metric, y, i, bucket, metricName, doc; + + for (i = 0; i < esAgg.buckets.length; i++) { + bucket = esAgg.buckets[i]; + doc = _.defaults({}, props); + doc[aggDef.field] = bucket.key; + + for (y = 0; y < target.metrics.length; y++) { + metric = target.metrics[y]; + + switch (metric.type) { + case "count": { + metricName = this._getMetricName(metric.type); + doc[metricName] = bucket.doc_count; + break; + } + case 'extended_stats': { + for (var statName in metric.meta) { + if (!metric.meta[statName]) { + continue; + } + + var stats = bucket[metric.id]; + // add stats that are in nested obj to top level obj + stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper; + stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower; + + metricName = this._getMetricName(statName); + doc[metricName] = stats[statName]; + } + break; + } + default: { + metricName = this._getMetricName(metric.type); + var otherMetrics = _.filter(target.metrics, {type: metric.type}); + + // if more of the same metric type include field field name in property + if (otherMetrics.length > 1) { + metricName += ' ' + metric.field; + } + + doc[metricName] = bucket[metric.id].value; + break; + } + } + } + + docs.push(doc); + } +}; + +// This is quite complex +// neeed to recurise down the nested buckets to build series +ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, docs, props, depth) { + var bucket, aggDef, esAgg, aggId; + var maxDepth = target.bucketAggs.length-1; + + for (aggId in aggs) { + aggDef = _.find(target.bucketAggs, {id: aggId}); + esAgg = aggs[aggId]; + + if (!aggDef) { + continue; + } + + if (depth === maxDepth) { + if (aggDef.type === 'date_histogram') { + this.processMetrics(esAgg, target, seriesList, props); + } else { + this.processAggregationDocs(esAgg, aggDef, target, docs, props); + } + } else { + for (var nameIndex in esAgg.buckets) { + bucket = esAgg.buckets[nameIndex]; + props = _.clone(props); + if (bucket.key !== void 0) { + props[aggDef.field] = bucket.key; + } else { + props["filter"] = nameIndex; + } + if (bucket.key_as_string) { + props[aggDef.field] = bucket.key_as_string; + } + this.processBuckets(bucket, target, seriesList, docs, props, depth+1); + } + } + } +}; + +ElasticResponse.prototype._getMetricName = function(metric) { + var metricDef = _.find(queryDef.metricAggTypes, {value: metric}); + if (!metricDef) { + metricDef = _.find(queryDef.extendedStats, {value: metric}); + } + + return metricDef ? metricDef.text : metric; +}; + +ElasticResponse.prototype._getSeriesName = function(series, target, metricTypeCount) { + var metricName = this._getMetricName(series.metric); + + if (target.alias) { + var regex = /\{\{([\s\S]+?)\}\}/g; + + return target.alias.replace(regex, function(match, g1, g2) { + var group = g1 || g2; + + if (group.indexOf('term ') === 0) { return series.props[group.substring(5)]; } + if (series.props[group] !== void 0) { return series.props[group]; } + if (group === 'metric') { return metricName; } + if (group === 'field') { return series.field; } + + return match; + }); + } + + if (series.field && queryDef.isPipelineAgg(series.metric)) { + var appliedAgg = _.find(target.metrics, { id: series.field }); + if (appliedAgg) { + metricName += ' ' + queryDef.describeMetric(appliedAgg); + } else { + metricName = 'Unset'; + } + } else if (series.field) { + metricName += ' ' + series.field; + } + + var propKeys = _.keys(series.props); + if (propKeys.length === 0) { + return metricName; + } + + var name = ''; + for (var propName in series.props) { + name += series.props[propName] + ' '; + } + + if (metricTypeCount === 1) { + return name.trim(); + } + + return name.trim() + ' ' + metricName; +}; + +ElasticResponse.prototype.nameSeries = function(seriesList, target) { + var metricTypeCount = _.uniq(_.map(seriesList, 'metric')).length; + var fieldNameCount = _.uniq(_.map(seriesList, 'field')).length; + + for (var i = 0; i < seriesList.length; i++) { + var series = seriesList[i]; + series.target = this._getSeriesName(series, target, metricTypeCount, fieldNameCount); + } +}; + +ElasticResponse.prototype.processHits = function(hits, seriesList) { + var series = {target: 'docs', type: 'docs', datapoints: [], total: hits.total}; + var propName, hit, doc, i; + + for (i = 0; i < hits.hits.length; i++) { + hit = hits.hits[i]; + doc = { + _id: hit._id, + _type: hit._type, + _index: hit._index + }; + + if (hit._source) { + for (propName in hit._source) { + doc[propName] = hit._source[propName]; + } + } + + for (propName in hit.fields) { + doc[propName] = hit.fields[propName]; + } + series.datapoints.push(doc); + } + + seriesList.push(series); +}; + +ElasticResponse.prototype.trimDatapoints = function(aggregations, target) { + var histogram = _.find(target.bucketAggs, { type: 'date_histogram'}); + + var shouldDropFirstAndLast = histogram && histogram.settings && histogram.settings.trimEdges; + if (shouldDropFirstAndLast) { + var trim = histogram.settings.trimEdges; + for (var prop in aggregations) { + var points = aggregations[prop]; + if (points.datapoints.length > trim * 2) { + points.datapoints = points.datapoints.slice(trim, points.datapoints.length - trim); + } + } + } +}; + +ElasticResponse.prototype.getErrorFromElasticResponse = function(response, err) { + var result: any = {}; + result.data = JSON.stringify(err, null, 4); + if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) { + result.message = err.root_cause[0].reason; + } else { + result.message = err.reason || 'Unkown elatic error response'; + } + + if (response.$$config) { + result.config = response.$$config; + } + + return result; +}; + +ElasticResponse.prototype.getTimeSeries = function() { + var seriesList = []; + + for (var i = 0; i < this.response.responses.length; i++) { + var response = this.response.responses[i]; + if (response.error) { + throw this.getErrorFromElasticResponse(this.response, response.error); + } + + if (response.hits && response.hits.hits.length > 0) { + this.processHits(response.hits, seriesList); + } + + if (response.aggregations) { + var aggregations = response.aggregations; + var target = this.targets[i]; + var tmpSeriesList = []; + var docs = []; + + this.processBuckets(aggregations, target, tmpSeriesList, docs, {}, 0); + this.trimDatapoints(tmpSeriesList, target); + this.nameSeries(tmpSeriesList, target); + + for (var y = 0; y < tmpSeriesList.length; y++) { + seriesList.push(tmpSeriesList[y]); + } + + if (seriesList.length === 0 && docs.length > 0) { + seriesList.push({target: 'docs', type: 'docs', datapoints: docs}); + } + } + } + + return { data: seriesList }; +}; + diff --git a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts index bd89055c3b4..2e70cf18e9a 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts @@ -1,6 +1,6 @@ import {describe, beforeEach, it, expect} from 'test/lib/common'; -import ElasticResponse from '../elastic_response'; +import {ElasticResponse} from '../elastic_response'; describe('ElasticResponse', function() { var targets; From ede827f5c05cd6f8d19686ed251d71a847dc893a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Aug 2017 12:15:39 +0200 Subject: [PATCH 12/71] feat: Elasticsearch change to how queries without date histogram are transformed into Grafana data stucture, now it is processed into a table structure instead of json structure --- public/app/core/table_model.ts | 9 +++ .../elasticsearch/elastic_response.ts | 60 ++++++++++++------- .../specs/elastic_response_specs.ts | 28 ++++----- 3 files changed, 59 insertions(+), 38 deletions(-) diff --git a/public/app/core/table_model.ts b/public/app/core/table_model.ts index f3d0b81998f..6b02e906583 100644 --- a/public/app/core/table_model.ts +++ b/public/app/core/table_model.ts @@ -3,9 +3,11 @@ export default class TableModel { columns: any[]; rows: any[]; type: string; + columnMap: any; constructor() { this.columns = []; + this.columnMap = {}; this.rows = []; this.type = 'table'; } @@ -36,4 +38,11 @@ export default class TableModel { this.columns[options.col].desc = false; } } + + addColumn(col) { + if (!this.columnMap[col.text]) { + this.columns.push(col); + this.columnMap[col.text] = col; + } + } } diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.ts b/public/app/plugins/datasource/elasticsearch/elastic_response.ts index 04cfe20a9c1..a0416e8d91c 100644 --- a/public/app/plugins/datasource/elasticsearch/elastic_response.ts +++ b/public/app/plugins/datasource/elasticsearch/elastic_response.ts @@ -2,6 +2,7 @@ import _ from 'lodash'; import queryDef from "./query_def"; +import TableModel from 'app/core/table_model'; export function ElasticResponse(targets, response) { this.targets = targets; @@ -95,21 +96,35 @@ ElasticResponse.prototype.processMetrics = function(esAgg, target, seriesList, p } }; -ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, target, docs, props) { - var metric, y, i, bucket, metricName, doc; +ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, target, table, props) { + // add columns + if (table.columns.length === 0) { + for (let propKey of _.keys(props)) { + table.addColumn({text: propKey}); + } + table.addColumn({text: aggDef.field}); + } - for (i = 0; i < esAgg.buckets.length; i++) { - bucket = esAgg.buckets[i]; - doc = _.defaults({}, props); - doc[aggDef.field] = bucket.key; + // helper func to add values to value array + let addMetricValue = (values, metricName, value) => { + table.addColumn({text: metricName}); + values.push(value); + }; - for (y = 0; y < target.metrics.length; y++) { - metric = target.metrics[y]; + for (let bucket of esAgg.buckets) { + let values = []; + for (let propValues of _.values(props)) { + values.push(propValues); + } + + // add bucket key (value) + values.push(bucket.key); + + for (let metric of target.metrics) { switch (metric.type) { case "count": { - metricName = this._getMetricName(metric.type); - doc[metricName] = bucket.doc_count; + addMetricValue(values, this._getMetricName(metric.type), bucket.doc_count); break; } case 'extended_stats': { @@ -123,33 +138,32 @@ ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, targe stats.std_deviation_bounds_upper = stats.std_deviation_bounds.upper; stats.std_deviation_bounds_lower = stats.std_deviation_bounds.lower; - metricName = this._getMetricName(statName); - doc[metricName] = stats[statName]; + addMetricValue(values, this._getMetricName(statName), stats[statName]); } break; } default: { - metricName = this._getMetricName(metric.type); - var otherMetrics = _.filter(target.metrics, {type: metric.type}); + let metricName = this._getMetricName(metric.type); + let otherMetrics = _.filter(target.metrics, {type: metric.type}); // if more of the same metric type include field field name in property if (otherMetrics.length > 1) { metricName += ' ' + metric.field; } - doc[metricName] = bucket[metric.id].value; + addMetricValue(values, metricName, bucket[metric.id].value); break; } } } - docs.push(doc); + table.rows.push(values); } }; // This is quite complex // neeed to recurise down the nested buckets to build series -ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, docs, props, depth) { +ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, table, props, depth) { var bucket, aggDef, esAgg, aggId; var maxDepth = target.bucketAggs.length-1; @@ -165,7 +179,7 @@ ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, do if (aggDef.type === 'date_histogram') { this.processMetrics(esAgg, target, seriesList, props); } else { - this.processAggregationDocs(esAgg, aggDef, target, docs, props); + this.processAggregationDocs(esAgg, aggDef, target, table, props); } } else { for (var nameIndex in esAgg.buckets) { @@ -179,7 +193,7 @@ ElasticResponse.prototype.processBuckets = function(aggs, target, seriesList, do if (bucket.key_as_string) { props[aggDef.field] = bucket.key_as_string; } - this.processBuckets(bucket, target, seriesList, docs, props, depth+1); + this.processBuckets(bucket, target, seriesList, table, props, depth+1); } } } @@ -325,9 +339,9 @@ ElasticResponse.prototype.getTimeSeries = function() { var aggregations = response.aggregations; var target = this.targets[i]; var tmpSeriesList = []; - var docs = []; + var table = new TableModel(); - this.processBuckets(aggregations, target, tmpSeriesList, docs, {}, 0); + this.processBuckets(aggregations, target, tmpSeriesList, table, {}, 0); this.trimDatapoints(tmpSeriesList, target); this.nameSeries(tmpSeriesList, target); @@ -335,8 +349,8 @@ ElasticResponse.prototype.getTimeSeries = function() { seriesList.push(tmpSeriesList[y]); } - if (seriesList.length === 0 && docs.length > 0) { - seriesList.push({target: 'docs', type: 'docs', datapoints: docs}); + if (table.rows.length > 0) { + seriesList.push(table); } } } diff --git a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts index 2e70cf18e9a..ff78a99e10e 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts @@ -387,10 +387,9 @@ describe('ElasticResponse', function() { result = new ElasticResponse(targets, response).getTimeSeries(); }); - it('should return docs with byte and count', function() { - expect(result.data[0].datapoints.length).to.be(3); - expect(result.data[0].datapoints[0].Count).to.be(1); - expect(result.data[0].datapoints[0].bytes).to.be(1000); + it('should return table with byte and count', function() { + expect(result.data[0].rows.length).to.be(3); + expect(result.data[0].columns).to.eql([{text: 'bytes'}, {text: 'Count'}]); }); }); @@ -530,14 +529,14 @@ describe('ElasticResponse', function() { it('should return table', function() { expect(result.data.length).to.be(1); - expect(result.data[0].type).to.be('docs'); - expect(result.data[0].datapoints.length).to.be(2); - expect(result.data[0].datapoints[0].host).to.be("server-1"); - expect(result.data[0].datapoints[0].Average).to.be(1000); - expect(result.data[0].datapoints[0].Count).to.be(369); + expect(result.data[0].type).to.be('table'); + expect(result.data[0].rows.length).to.be(2); + expect(result.data[0].rows[0][0]).to.be("server-1"); + expect(result.data[0].rows[0][1]).to.be(1000); + expect(result.data[0].rows[0][2]).to.be(369); - expect(result.data[0].datapoints[1].host).to.be("server-2"); - expect(result.data[0].datapoints[1].Average).to.be(2000); + expect(result.data[0].rows[1][0]).to.be("server-2"); + expect(result.data[0].rows[1][1]).to.be(2000); }); }); @@ -573,10 +572,9 @@ describe('ElasticResponse', function() { }); it('should include field in metric name', function() { - expect(result.data[0].type).to.be('docs'); - expect(result.data[0].datapoints[0].Average).to.be(undefined); - expect(result.data[0].datapoints[0]['Average test']).to.be(1000); - expect(result.data[0].datapoints[0]['Average test2']).to.be(3000); + expect(result.data[0].type).to.be('table'); + expect(result.data[0].rows[0][1]).to.be(1000); + expect(result.data[0].rows[0][2]).to.be(3000); }); }); From c17b5d13066445f9ef33529554d6e3e57bb32c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Aug 2017 12:34:36 +0200 Subject: [PATCH 13/71] table: minor table options improvement --- public/app/plugins/panel/table/editor.html | 10 ++++++++- public/app/plugins/panel/table/editor.ts | 24 +++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/table/editor.html b/public/app/plugins/panel/table/editor.html index 9854ac26dc3..36d78b09dfa 100644 --- a/public/app/plugins/panel/table/editor.html +++ b/public/app/plugins/panel/table/editor.html @@ -17,9 +17,17 @@ {{column.text}}
-
+
+
+ +
diff --git a/public/app/plugins/panel/table/editor.ts b/public/app/plugins/panel/table/editor.ts index 9850d60c9ad..a4fd72ed6f2 100644 --- a/public/app/plugins/panel/table/editor.ts +++ b/public/app/plugins/panel/table/editor.ts @@ -16,6 +16,8 @@ export class TablePanelEditorCtrl { fontSizes: any; addColumnSegment: any; getColumnNames: any; + canSetColumns: boolean; + columnsHelpMessage: string; /** @ngInject */ constructor($scope, private $q, private uiSegmentSrv) { @@ -24,8 +26,27 @@ export class TablePanelEditorCtrl { this.panel = this.panelCtrl.panel; this.transformers = transformers; this.fontSizes = ['80%', '90%', '100%', '110%', '120%', '130%', '150%', '160%', '180%', '200%', '220%', '250%']; - this.addColumnSegment = uiSegmentSrv.newPlusButton(); + this.updateTransformHints(); + } + + updateTransformHints() { + this.canSetColumns = false; + this.columnsHelpMessage = ''; + + switch (this.panel.transform) { + case "timeseries_aggregations": { + this.canSetColumns = true; + break; + } + case "json": { + this.canSetColumns = true; + break; + } + case "table": { + this.columnsHelpMessage = "Columns and their order are determined by the data query"; + } + } } getColumnOptions() { @@ -57,6 +78,7 @@ export class TablePanelEditorCtrl { this.panel.columns.push({text: 'Avg', value: 'avg'}); } + this.updateTransformHints(); this.render(); } From a5d5f3d82fc96eabbe4937117827568c1e826ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Aug 2017 16:15:22 +0200 Subject: [PATCH 14/71] feat: add ad hoc filters directly from table panel cells, kibana 3 style, #8052 --- .../app/features/dashboard/ad_hoc_filters.ts | 6 +-- .../app/features/dashboard/submenu/submenu.ts | 5 +-- public/app/features/templating/editor_ctrl.ts | 10 ++--- .../templating/specs/variable_srv_specs.ts | 5 ++- .../app/features/templating/variable_srv.ts | 44 ++++++++++++++++--- .../elasticsearch/elastic_response.ts | 6 +-- .../specs/elastic_response_specs.ts | 2 +- public/app/plugins/panel/table/module.ts | 18 +++++++- public/app/plugins/panel/table/renderer.ts | 33 ++++++++++---- .../app/plugins/panel/table/transformers.ts | 12 ++++- public/sass/components/_panel_table.scss | 14 ++++++ 11 files changed, 119 insertions(+), 36 deletions(-) diff --git a/public/app/features/dashboard/ad_hoc_filters.ts b/public/app/features/dashboard/ad_hoc_filters.ts index f962f8ca2f4..47babfdd5dd 100644 --- a/public/app/features/dashboard/ad_hoc_filters.ts +++ b/public/app/features/dashboard/ad_hoc_filters.ts @@ -10,9 +10,10 @@ export class AdHocFiltersCtrl { removeTagFilterSegment: any; /** @ngInject */ - constructor(private uiSegmentSrv, private datasourceSrv, private $q, private templateSrv, private $rootScope) { + constructor(private uiSegmentSrv, private datasourceSrv, private $q, private variableSrv, private $scope, private $rootScope) { this.removeTagFilterSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove filter --'}); this.buildSegmentModel(); + this.$rootScope.onAppEvent('template-variable-value-updated', this.buildSegmentModel.bind(this), $scope); } buildSegmentModel() { @@ -141,8 +142,7 @@ export class AdHocFiltersCtrl { } this.variable.setFilters(filters); - this.$rootScope.$emit('template-variable-value-updated'); - this.$rootScope.$broadcast('refresh'); + this.variableSrv.variableUpdated(this.variable, true); } } diff --git a/public/app/features/dashboard/submenu/submenu.ts b/public/app/features/dashboard/submenu/submenu.ts index a925ba97f0d..0082f120b5f 100644 --- a/public/app/features/dashboard/submenu/submenu.ts +++ b/public/app/features/dashboard/submenu/submenu.ts @@ -22,10 +22,7 @@ export class SubmenuCtrl { } variableUpdated(variable) { - this.variableSrv.variableUpdated(variable).then(() => { - this.$rootScope.$emit('template-variable-value-updated'); - this.$rootScope.$broadcast('refresh'); - }); + this.variableSrv.variableUpdated(variable, true); } openEditView(editview) { diff --git a/public/app/features/templating/editor_ctrl.ts b/public/app/features/templating/editor_ctrl.ts index 97274cb30d0..99c235ef24c 100644 --- a/public/app/features/templating/editor_ctrl.ts +++ b/public/app/features/templating/editor_ctrl.ts @@ -55,9 +55,8 @@ export class VariableEditorCtrl { $scope.add = function() { if ($scope.isValid()) { - $scope.variables.push($scope.current); + variableSrv.addVariable($scope.current); $scope.update(); - $scope.dashboard.updateSubmenuVisibility(); } }; @@ -114,9 +113,8 @@ export class VariableEditorCtrl { $scope.duplicate = function(variable) { var clone = _.cloneDeep(variable.getSaveModel()); $scope.current = variableSrv.createVariableFromModel(clone); - $scope.variables.push($scope.current); $scope.current.name = 'copy_of_'+variable.name; - $scope.dashboard.updateSubmenuVisibility(); + $scope.variableSrv.addVariable($scope.current); }; $scope.update = function() { @@ -150,9 +148,7 @@ export class VariableEditorCtrl { }; $scope.removeVariable = function(variable) { - var index = _.indexOf($scope.variables, variable); - $scope.variables.splice(index, 1); - $scope.dashboard.updateSubmenuVisibility(); + variableSrv.removeVariable(variable); }; } } diff --git a/public/app/features/templating/specs/variable_srv_specs.ts b/public/app/features/templating/specs/variable_srv_specs.ts index 85bca8d6068..9afb38056bd 100644 --- a/public/app/features/templating/specs/variable_srv_specs.ts +++ b/public/app/features/templating/specs/variable_srv_specs.ts @@ -22,6 +22,7 @@ describe('VariableSrv', function() { ctx.variableSrv.init({ templating: {list: []}, events: new Emitter(), + updateSubmenuVisibility: sinon.stub(), }); ctx.$rootScope.$digest(); })); @@ -41,7 +42,9 @@ describe('VariableSrv', function() { ctx.datasourceSrv.getMetricSources = sinon.stub().returns(scenario.metricSources); - scenario.variable = ctx.variableSrv.addVariable(scenario.variableModel); + scenario.variable = ctx.variableSrv.createVariableFromModel(scenario.variableModel); + ctx.variableSrv.addVariable(scenario.variable); + ctx.variableSrv.updateOptions(scenario.variable); ctx.$rootScope.$digest(); }); diff --git a/public/app/features/templating/variable_srv.ts b/public/app/features/templating/variable_srv.ts index 022203b9eb9..c147c5b6685 100644 --- a/public/app/features/templating/variable_srv.ts +++ b/public/app/features/templating/variable_srv.ts @@ -90,17 +90,24 @@ export class VariableSrv { return variable; } - addVariable(model) { - var variable = this.createVariableFromModel(model); + addVariable(variable) { this.variables.push(variable); - return variable; + this.templateSrv.updateTemplateData(); + this.dashboard.updateSubmenuVisibility(); + } + + removeVariable(variable) { + var index = _.indexOf(this.variables, variable); + this.variables.splice(index, 1); + this.templateSrv.updateTemplateData(); + this.dashboard.updateSubmenuVisibility(); } updateOptions(variable) { return variable.updateOptions(); } - variableUpdated(variable) { + variableUpdated(variable, emitChangeEvents?) { // if there is a variable lock ignore cascading update because we are in a boot up scenario if (variable.initLock) { return this.$q.when(); @@ -117,7 +124,12 @@ export class VariableSrv { } }); - return this.$q.all(promises); + return this.$q.all(promises).then(() => { + if (emitChangeEvents) { + this.$rootScope.$emit('template-variable-value-updated'); + this.$rootScope.$broadcast('refresh'); + } + }); } selectOptionsForCurrentValue(variable) { @@ -218,6 +230,28 @@ export class VariableSrv { // update url this.$location.search(params); } + + setAdhocFilter(options) { + var variable = _.find(this.variables, {type: 'adhoc', datasource: options.datasource}); + if (!variable) { + variable = this.createVariableFromModel({name: 'Filters', type: 'adhoc', datasource: options.datasource}); + this.addVariable(variable); + } + + let filters = variable.filters; + let filter = _.find(filters, {key: options.key, value: options.value}); + + if (!filter) { + filter = {key: options.key, value: options.value}; + filters.push(filter); + } + + filter.operator = options.operator; + + variable.setFilters(filters); + this.variableUpdated(variable, true); + } + } coreModule.service('variableSrv', VariableSrv); diff --git a/public/app/plugins/datasource/elasticsearch/elastic_response.ts b/public/app/plugins/datasource/elasticsearch/elastic_response.ts index a0416e8d91c..47fa14a99a6 100644 --- a/public/app/plugins/datasource/elasticsearch/elastic_response.ts +++ b/public/app/plugins/datasource/elasticsearch/elastic_response.ts @@ -100,9 +100,9 @@ ElasticResponse.prototype.processAggregationDocs = function(esAgg, aggDef, targe // add columns if (table.columns.length === 0) { for (let propKey of _.keys(props)) { - table.addColumn({text: propKey}); + table.addColumn({text: propKey, filterable: true}); } - table.addColumn({text: aggDef.field}); + table.addColumn({text: aggDef.field, filterable: true}); } // helper func to add values to value array @@ -265,7 +265,7 @@ ElasticResponse.prototype.nameSeries = function(seriesList, target) { }; ElasticResponse.prototype.processHits = function(hits, seriesList) { - var series = {target: 'docs', type: 'docs', datapoints: [], total: hits.total}; + var series = {target: 'docs', type: 'docs', datapoints: [], total: hits.total, filterable: true}; var propName, hit, doc, i; for (i = 0; i < hits.hits.length; i++) { diff --git a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts index ff78a99e10e..fde876af4f7 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/elastic_response_specs.ts @@ -389,7 +389,7 @@ describe('ElasticResponse', function() { it('should return table with byte and count', function() { expect(result.data[0].rows.length).to.be(3); - expect(result.data[0].columns).to.eql([{text: 'bytes'}, {text: 'Count'}]); + expect(result.data[0].columns).to.eql([{text: 'bytes', filterable: true}, {text: 'Count'}]); }); }); diff --git a/public/app/plugins/panel/table/module.ts b/public/app/plugins/panel/table/module.ts index 375d3a0a0ae..ba26e853a29 100644 --- a/public/app/plugins/panel/table/module.ts +++ b/public/app/plugins/panel/table/module.ts @@ -50,8 +50,9 @@ class TablePanelCtrl extends MetricsPanelCtrl { }; /** @ngInject */ - constructor($scope, $injector, templateSrv, private annotationsSrv, private $sanitize) { + constructor($scope, $injector, templateSrv, private annotationsSrv, private $sanitize, private variableSrv) { super($scope, $injector); + this.pageIndex = 0; if (this.panel.styles === void 0) { @@ -223,10 +224,25 @@ class TablePanelCtrl extends MetricsPanelCtrl { selector: '[data-link-tooltip]' }); + function addFilterClicked(e) { + let filterData = $(e.currentTarget).data(); + var options = { + datasource: panel.datasource, + key: data.columns[filterData.column].text, + value: data.rows[filterData.row][filterData.column], + operator: filterData.operator, + }; + + ctrl.variableSrv.setAdhocFilter(options); + console.log('clicked', options); + } + elem.on('click', '.table-panel-page-link', switchPage); + elem.on('click', '.table-panel-filter-link', addFilterClicked); var unbindDestroy = scope.$on('$destroy', function() { elem.off('click', '.table-panel-page-link'); + elem.off('click', '.table-panel-filter-link'); unbindDestroy(); }); diff --git a/public/app/plugins/panel/table/renderer.ts b/public/app/plugins/panel/table/renderer.ts index ef53b1d489b..02f0ceca72c 100644 --- a/public/app/plugins/panel/table/renderer.ts +++ b/public/app/plugins/panel/table/renderer.ts @@ -140,9 +140,12 @@ export class TableRenderer { renderCell(columnIndex, rowIndex, value, addWidthHack = false) { value = this.formatColumnValue(columnIndex, value); + + var column = this.table.columns[columnIndex]; var style = ''; var cellClasses = []; var cellClass = ''; + if (this.colorState.cell) { style = ' style="background-color:' + this.colorState.cell + ';color: white"'; this.colorState.cell = null; @@ -161,26 +164,25 @@ export class TableRenderer { if (value === undefined) { style = ' style="display:none;"'; - this.table.columns[columnIndex].hidden = true; + column.hidden = true; } else { - this.table.columns[columnIndex].hidden = false; + column.hidden = false; } - var columnStyle = this.table.columns[columnIndex].style; - if (columnStyle && columnStyle.preserveFormat) { + if (column.style && column.style.preserveFormat) { cellClasses.push("table-panel-cell-pre"); } - var columnHtml = value + widthHack; + var columnHtml = widthHack + value; - if (columnStyle && columnStyle.link) { + if (column.style && column.style.link) { // Render cell as link var scopedVars = this.renderRowVariables(rowIndex); scopedVars['__cell'] = { value: value }; - var cellLink = this.templateSrv.replace(columnStyle.linkUrl, scopedVars); - var cellLinkTooltip = this.templateSrv.replace(columnStyle.linkTooltip, scopedVars); - var cellTarget = columnStyle.linkTargetBlank ? '_blank' : ''; + var cellLink = this.templateSrv.replace(column.style.linkUrl, scopedVars); + var cellLinkTooltip = this.templateSrv.replace(column.style.linkTooltip, scopedVars); + var cellTarget = column.style.linkTargetBlank ? '_blank' : ''; cellClasses.push("table-panel-cell-link"); columnHtml = ` @@ -190,6 +192,19 @@ export class TableRenderer { `; } + if (column.filterable) { + cellClasses.push("table-panel-cell-filterable"); + columnHtml += ` + + + + + + `; + } + if (cellClasses.length) { cellClass = ' class="' + cellClasses.join(' ') + '"'; } diff --git a/public/app/plugins/panel/table/transformers.ts b/public/app/plugins/panel/table/transformers.ts index 07d183eafa8..cb6d30e627d 100644 --- a/public/app/plugins/panel/table/transformers.ts +++ b/public/app/plugins/panel/table/transformers.ts @@ -185,8 +185,16 @@ transformers['json'] = { }, transform: function(data, panel, model) { var i, y, z; - for (i = 0; i < panel.columns.length; i++) { - model.columns.push({text: panel.columns[i].text}); + + for (let column of panel.columns) { + var tableCol: any = {text: column.text}; + + // if filterable data then set columns to filterable + if (data.length > 0 && data[0].filterable) { + tableCol.filterable = true; + } + + model.columns.push(tableCol); } if (model.columns.length === 0) { diff --git a/public/sass/components/_panel_table.scss b/public/sass/components/_panel_table.scss index 073d8be4abf..cf81dca4465 100644 --- a/public/sass/components/_panel_table.scss +++ b/public/sass/components/_panel_table.scss @@ -91,9 +91,23 @@ &.cell-highlighted:hover { background-color: $tight-form-func-bg; } + + &:hover { + .table-panel-filter-link { + visibility: visible; + } + } } } +.table-panel-filter-link { + visibility: hidden; + color: $text-color-weak; + float: right; + display: block; + padding: 0 5px; +} + .table-panel-header-bg { background: $grafanaListAccent; border-top: 2px solid $body-bg; From b241b98196021fd21d7d7d29588a02fd2cbb0ac8 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 2 Aug 2017 16:40:26 +0200 Subject: [PATCH 15/71] singlestat: fix for variable in the prefix or postfix fields and when adding a gauge. Previously the template variable substition was only done when not rendering the gauge. --- public/app/plugins/panel/singlestat/module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/singlestat/module.ts b/public/app/plugins/panel/singlestat/module.ts index dce82f17ce2..4b7f6e2e90d 100644 --- a/public/app/plugins/panel/singlestat/module.ts +++ b/public/app/plugins/panel/singlestat/module.ts @@ -402,9 +402,9 @@ class SingleStatCtrl extends MetricsPanelCtrl { } function getValueText() { - var result = panel.prefix ? panel.prefix : ''; + var result = panel.prefix ? templateSrv.replace(panel.prefix, data.scopedVars) : ''; result += data.valueFormatted; - result += panel.postfix ? panel.postfix : ''; + result += panel.postfix ? templateSrv.replace(panel.postfix, data.scopedVars) : ''; return result; } From b5017d1e18b97321659b05a998cf0ca1d2c4628d Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Wed, 2 Aug 2017 16:44:43 +0200 Subject: [PATCH 16/71] docs: var in singlestat panel. Fixes #8563 --- docs/sources/features/panels/singlestat.md | 2 +- docs/sources/reference/templating.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/sources/features/panels/singlestat.md b/docs/sources/features/panels/singlestat.md index eb9b2f26ea5..7447f6c9b84 100644 --- a/docs/sources/features/panels/singlestat.md +++ b/docs/sources/features/panels/singlestat.md @@ -34,7 +34,7 @@ The singlestat panel has a normal query editor to allow you define your exact me * `delta` - The total incremental increase (of a counter) in the series. An attempt is made to account for counter resets, but this will only be accurate for single instance metrics. Used to show total counter increase in time series. * `diff` - The difference betwen 'current' (last value) and 'first'. * `range` - The difference between 'min' and 'max'. Useful the show the range of change for a gauge. -4. `Postfixes`: The Postfix fields let you define a custom label and font-size (as a %) to appear *after* the value +4. `Prefix/Postfix`: The Prefix/Postfix fields let you define a custom label and font-size (as a %) to appear *before/after* the value. The `$__name` variable can be used here to use the series name or alias from the metric query. 5. `Units`: Units are appended to the the Singlestat within the panel, and will respect the color and threshold settings for the value. 6. `Decimals`: The Decimal field allows you to override the automatic decimal precision, and set it explicitly. diff --git a/docs/sources/reference/templating.md b/docs/sources/reference/templating.md index b2346903132..341a0ac92fb 100644 --- a/docs/sources/reference/templating.md +++ b/docs/sources/reference/templating.md @@ -177,6 +177,10 @@ This is used in the WHERE clause for the InfluxDB data source. Grafana adds it a The `$__timeFilter` is used in the MySQL data source. +### The $__name Variable + +This variable is only available in the Singlestat panel and can be used in the prefix or suffix fields on the Options tab. The variable will be replaced with the series name or alias. + ## Repeating Panels Template variables can be very useful to dynamically change your queries across a whole dashboard. If you want From a5bdfec0de702fe593c76bfb5559e5b57a712f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Aug 2017 16:52:43 +0200 Subject: [PATCH 17/71] ES: return .raw fields in field lookups, closes #8975 --- .../plugins/datasource/elasticsearch/datasource.js | 11 +++++++++-- .../elasticsearch/specs/datasource_specs.ts | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/datasource.js b/public/app/plugins/datasource/elasticsearch/datasource.js index 5f3ce0fa361..9ea679d5cc5 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.js +++ b/public/app/plugins/datasource/elasticsearch/datasource.js @@ -272,10 +272,17 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes var subObj = obj[key]; // Check mapping field for nested fields - if (subObj.hasOwnProperty('properties')) { + if (_.isObject(subObj.properties)) { fieldNameParts.push(key); getFieldsRecursively(subObj.properties); - } else { + } + + if (_.isObject(subObj.fields)) { + fieldNameParts.push(key); + getFieldsRecursively(subObj.fields); + } + + if (_.isString(subObj.type)) { var fieldName = fieldNameParts.concat(key).join('.'); // Hide meta-fields and check field type diff --git a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts index d0133256982..0838dc33654 100644 --- a/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/elasticsearch/specs/datasource_specs.ts @@ -129,7 +129,10 @@ describe('ElasticDatasource', function() { '@timestamp': {type: 'date'}, beat: { properties: { - name: {type: 'string'}, + name: { + fields: {raw: {type: 'keyword'}}, + type: 'string' + }, hostname: {type: 'string'}, } }, @@ -169,6 +172,7 @@ describe('ElasticDatasource', function() { var fields = _.map(fieldObjects, 'text'); expect(fields).to.eql([ '@timestamp', + 'beat.name.raw', 'beat.name', 'beat.hostname', 'system.cpu.system', From 3dc9d76b38f351a4f3fb9ca35e268431118b67e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 2 Aug 2017 17:33:06 +0200 Subject: [PATCH 18/71] fix: viewer role fix, fixes #8976 --- pkg/api/api.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index ae9b8e7803f..846e3328ddd 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -222,7 +222,8 @@ func (hs *HttpServer) registerRoutes() { // Dashboard r.Group("/dashboards", func() { - r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard) + r.Get("/db/:slug", GetDashboard) + r.Delete("/db/:slug", reqEditorRole, DeleteDashboard) r.Get("/id/:dashboardId/versions", wrap(GetDashboardVersions)) r.Get("/id/:dashboardId/versions/:id", wrap(GetDashboardVersion)) From c7e8b98d144ee492b10b2161c65d3bd09cf3e161 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 2 Aug 2017 21:00:05 +0300 Subject: [PATCH 19/71] heatmap: minor refactor, don't repeat cards stats calculation --- public/app/plugins/panel/heatmap/heatmap_ctrl.ts | 10 ++-------- .../plugins/panel/heatmap/heatmap_data_converter.ts | 12 +++++++++++- .../plugins/panel/heatmap/specs/renderer_specs.ts | 10 ++-------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts index 3b8d1fa9d41..b564339673f 100644 --- a/public/app/plugins/panel/heatmap/heatmap_ctrl.ts +++ b/public/app/plugins/panel/heatmap/heatmap_ctrl.ts @@ -191,20 +191,14 @@ export class HeatmapCtrl extends MetricsPanelCtrl { yBucketSize = 1; } - let cardsData = convertToCards(bucketsData); - let maxCardsValue = _.max(_.map(cardsData, 'count')); - let minCardsValue = _.min(_.map(cardsData, 'count')); - let cardStats = { - max: maxCardsValue, - min: minCardsValue - }; + let {cards, cardStats} = convertToCards(bucketsData); this.data = { buckets: bucketsData, heatmapStats: heatmapStats, xBucketSize: xBucketSize, yBucketSize: yBucketSize, - cards: cardsData, + cards: cards, cardStats: cardStats }; } diff --git a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts index ef405ea5508..993c32c0fca 100644 --- a/public/app/plugins/panel/heatmap/heatmap_data_converter.ts +++ b/public/app/plugins/panel/heatmap/heatmap_data_converter.ts @@ -51,6 +51,7 @@ function elasticHistogramToHeatmap(seriesList) { * @return {Array} Array of "card" objects */ function convertToCards(buckets) { + let min = 0, max = 0; let cards = []; _.forEach(buckets, xBucket => { _.forEach(xBucket.buckets, yBucket=> { @@ -62,10 +63,19 @@ function convertToCards(buckets) { count: yBucket.count, }; cards.push(card); + + if (cards.length === 1) { + min = yBucket.count; + max = yBucket.count; + } + + min = yBucket.count < min ? yBucket.count : min; + max = yBucket.count > max ? yBucket.count : max; }); }); - return cards; + let cardStats = {min, max}; + return {cards, cardStats}; } /** diff --git a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts index 884ee1e81f4..01d09a84228 100644 --- a/public/app/plugins/panel/heatmap/specs/renderer_specs.ts +++ b/public/app/plugins/panel/heatmap/specs/renderer_specs.ts @@ -114,14 +114,8 @@ describe('grafanaHeatmap', function () { let bucketsData = convertToHeatMap(ctx.series, ctx.data.yBucketSize, ctx.data.xBucketSize, logBase); ctx.data.buckets = bucketsData; - let cardsData = convertToCards(bucketsData); - let maxCardsValue = _.max(_.map(cardsData, 'count')); - let minCardsValue = _.min(_.map(cardsData, 'count')); - let cardStats = { - max: maxCardsValue, - min: minCardsValue - }; - ctx.data.cards = cardsData; + let {cards, cardStats} = convertToCards(bucketsData); + ctx.data.cards = cards; ctx.data.cardStats = cardStats; let elemHtml = ` From 77b7f4b3767a447295778b9a0f84401a76e5b62c Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 2 Aug 2017 21:27:10 +0300 Subject: [PATCH 20/71] heatmap: add unit tests for convertToCards() --- .../specs/heatmap_data_converter_specs.ts | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts b/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts index 03adf1f9fa3..a898a14ff10 100644 --- a/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts +++ b/public/app/plugins/panel/heatmap/specs/heatmap_data_converter_specs.ts @@ -3,7 +3,8 @@ import _ from 'lodash'; import { describe, beforeEach, it, sinon, expect, angularMocks } from '../../../../../test/lib/common'; import TimeSeries from 'app/core/time_series2'; -import { convertToHeatMap, elasticHistogramToHeatmap, calculateBucketSize, isHeatmapDataEqual } from '../heatmap_data_converter'; +import {convertToHeatMap, convertToCards, elasticHistogramToHeatmap, + calculateBucketSize, isHeatmapDataEqual} from '../heatmap_data_converter'; describe('isHeatmapDataEqual', () => { let ctx: any = {}; @@ -244,6 +245,47 @@ describe('ES Histogram converter', () => { }); }); +describe('convertToCards', () => { + let buckets = {}; + + beforeEach(() => { + buckets = { + '1422774000000': { + x: 1422774000000, + buckets: { + '1': { y: 1, values: [1], count: 1, bounds: {} }, + '2': { y: 2, values: [2], count: 1, bounds: {} } + } + }, + '1422774060000': { + x: 1422774060000, + buckets: { + '2': { y: 2, values: [2, 3], count: 2, bounds: {} } + } + }, + }; + }); + + it('should build proper cards data', () => { + let expectedCards = [ + {x: 1422774000000, y: 1, count: 1, values: [1], yBounds: {}}, + {x: 1422774000000, y: 2, count: 1, values: [2], yBounds: {}}, + {x: 1422774060000, y: 2, count: 2, values: [2, 3], yBounds: {}} + ]; + let {cards, cardStats} = convertToCards(buckets); + expect(cards).to.eql(expectedCards); + }); + + it('should build proper cards stats', () => { + let expectedStats = { + min: 1, + max: 2 + }; + let {cards, cardStats} = convertToCards(buckets); + expect(cardStats).to.eql(expectedStats); + }); +}); + /** * Compare two numbers with given precision. Suitable for compare float numbers after conversions with precision loss. * @param a From 1372d2e517c9d9f7f0d9ccda263573f5a807a007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 3 Aug 2017 08:56:34 +0200 Subject: [PATCH 21/71] fix: update user permissions validation was not working properly, fixes #8977 --- pkg/api/dtos/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/api/dtos/user.go b/pkg/api/dtos/user.go index 2ffe9d69236..93d3074f66f 100644 --- a/pkg/api/dtos/user.go +++ b/pkg/api/dtos/user.go @@ -31,7 +31,7 @@ type AdminUpdateUserPasswordForm struct { } type AdminUpdateUserPermissionsForm struct { - IsGrafanaAdmin bool `json:"isGrafanaAdmin" binding:"Required"` + IsGrafanaAdmin bool `json:"isGrafanaAdmin"` } type AdminUserListItem struct { From 8f5fbb4254d8bd92d5cbc9302c5bf36aa075b776 Mon Sep 17 00:00:00 2001 From: David Wittman Date: Thu, 3 Aug 2017 01:57:59 -0500 Subject: [PATCH 22/71] Fix typo in PagerDuty notifier options template (#8978) --- pkg/services/alerting/notifiers/pagerduty.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/alerting/notifiers/pagerduty.go b/pkg/services/alerting/notifiers/pagerduty.go index 0c98ab00e20..31c2cdeb679 100644 --- a/pkg/services/alerting/notifiers/pagerduty.go +++ b/pkg/services/alerting/notifiers/pagerduty.go @@ -21,7 +21,7 @@ func init() {

PagerDuty settings

Integration Key - +
Date: Thu, 3 Aug 2017 15:36:26 +0200 Subject: [PATCH 23/71] docs: fixes #8972. Note on clustering and alerting. --- docs/sources/alerting/rules.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index c4a3a012c46..e1db65e8148 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -32,9 +32,7 @@ of core Grafana. Only some data soures are supported right now. They include `Gr ### Clustering -We have not implemented clustering yet. So if you run multiple instances of grafana-server -you have to make sure [execute_alerts]({{< relref "installation/configuration.md#alerting" >}}) -is true on only one instance or otherwise you will get duplicated notifications. +Currently alerting supports a limited form of high availability. Since v4.2.0 of Grafana, alert notifications are deduped when running multiple servers. This means all alerts are executed on every server but no duplicate alert notifications are sent due to the deduping logic. Proper load balancing of alerts will be introduced in the future.
From ef0c90b9ca5d5104c615864755a44c1913f3ca26 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 3 Aug 2017 16:58:31 +0200 Subject: [PATCH 24/71] graph: change tick decimal calculation for y-axis Fixes #8872. Use the automatic calculation by Flot to get the number of decimal places for ticks on the y-axis rather than the number of decimal places for the min value. --- public/app/plugins/panel/graph/graph.ts | 1 - public/app/plugins/panel/graph/specs/graph_specs.ts | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 21358a7e8cc..28da07b88ad 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -588,7 +588,6 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { if (axis.ticks[axis.ticks.length - 1] > axis.max) { axis.max = axis.ticks[axis.ticks.length - 1]; } - axis.tickDecimals = decimalPlaces(min); } else { axis.ticks = [1, 2]; delete axis.min; diff --git a/public/app/plugins/panel/graph/specs/graph_specs.ts b/public/app/plugins/panel/graph/specs/graph_specs.ts index 40cfdb2fcdd..b7f5d2865d1 100644 --- a/public/app/plugins/panel/graph/specs/graph_specs.ts +++ b/public/app/plugins/panel/graph/specs/graph_specs.ts @@ -139,8 +139,6 @@ describe('grafanaGraph', function() { expect(axisAutoscale.ticks.length).to.be(8); expect(axisAutoscale.ticks[0]).to.be(0.001); expect(axisAutoscale.ticks[7]).to.be(10000); - expect(axisAutoscale.tickDecimals).to.be(3); - var axisFixedscale = ctx.plotOptions.yaxes[1]; expect(axisFixedscale.min).to.be(0.05); @@ -148,8 +146,6 @@ describe('grafanaGraph', function() { expect(axisFixedscale.ticks.length).to.be(5); expect(axisFixedscale.ticks[0]).to.be(0.1); expect(axisFixedscale.ticks[4]).to.be(1000); - expect(axisFixedscale.tickDecimals).to.be(1); - }); }); @@ -172,7 +168,6 @@ describe('grafanaGraph', function() { expect(axisAutoscale.ticks.length).to.be(2); expect(axisAutoscale.ticks[0]).to.be(1); expect(axisAutoscale.ticks[1]).to.be(2); - expect(axisAutoscale.tickDecimals).to.be(undefined); }); }); @@ -189,7 +184,7 @@ describe('grafanaGraph', function() { data[0].yaxis = 1; }); - it('should set min to 0.1 and add a tick for 0.1 and tickDecimals to be 0', function() { + it('should set min to 0.1 and add a tick for 0.1', function() { var axisAutoscale = ctx.plotOptions.yaxes[0]; expect(axisAutoscale.transform(100)).to.be(2); expect(axisAutoscale.inverseTransform(-3)).to.be(0.001); @@ -198,7 +193,6 @@ describe('grafanaGraph', function() { expect(axisAutoscale.ticks.length).to.be(6); expect(axisAutoscale.ticks[0]).to.be(0.1); expect(axisAutoscale.ticks[5]).to.be(10000); - expect(axisAutoscale.tickDecimals).to.be(0); }); }); @@ -222,7 +216,6 @@ describe('grafanaGraph', function() { expect(axisAutoscale.ticks[0]).to.be(0.1); expect(axisAutoscale.ticks[7]).to.be(262144); expect(axisAutoscale.max).to.be(262144); - expect(axisAutoscale.tickDecimals).to.be(0); }); it('should set axis max to be max tick value', function() { From 61313478063b0e0721f204847f3c0298ea8f593f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Thu, 3 Aug 2017 17:57:04 +0200 Subject: [PATCH 25/71] graph: adds decimals option for y-axis Fixes #8187 --- .../app/plugins/panel/graph/axes_editor.html | 20 +++++++++++-------- public/app/plugins/panel/graph/graph.ts | 2 ++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/public/app/plugins/panel/graph/axes_editor.html b/public/app/plugins/panel/graph/axes_editor.html index b0ab759bf18..704f3b550f9 100644 --- a/public/app/plugins/panel/graph/axes_editor.html +++ b/public/app/plugins/panel/graph/axes_editor.html @@ -4,34 +4,38 @@
Left Y
Right Y
- +
- -
+ +
- -
+ +
- +
- +
+
+
+ +
- +
diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 28da07b88ad..6b70ad36e4e 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -498,6 +498,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { logBase: panel.yaxes[0].logBase || 1, min: panel.yaxes[0].min ? _.toNumber(panel.yaxes[0].min) : null, max: panel.yaxes[0].max ? _.toNumber(panel.yaxes[0].max) : null, + tickDecimals: panel.yaxes[0].decimals !== null ? _.toNumber(panel.yaxes[0].decimals): null }; options.yaxes.push(defaults); @@ -510,6 +511,7 @@ coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) { secondY.position = 'right'; secondY.min = panel.yaxes[1].min ? _.toNumber(panel.yaxes[1].min) : null; secondY.max = panel.yaxes[1].max ? _.toNumber(panel.yaxes[1].max) : null; + secondY.tickDecimals = panel.yaxes[1].decimals !== null ? _.toNumber(panel.yaxes[1].decimals): null; options.yaxes.push(secondY); applyLogScale(options.yaxes[1], data); From 8e618bc1697f37746f1c89b2e6a68c785b48d8d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Fievet?= <_@sebastien-fievet.fr> Date: Fri, 4 Aug 2017 11:08:41 +0200 Subject: [PATCH 26/71] Fix some typos in alerting documentation (#8986) --- docs/sources/alerting/rules.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/sources/alerting/rules.md b/docs/sources/alerting/rules.md index e1db65e8148..ead457066e3 100644 --- a/docs/sources/alerting/rules.md +++ b/docs/sources/alerting/rules.md @@ -27,7 +27,7 @@ and the conditions that need to be met for the alert to change state and trigger ## Execution The alert rules are evaluated in the Grafana backend in a scheduler and query execution engine that is part -of core Grafana. Only some data soures are supported right now. They include `Graphite`, `Prometheus`, +of core Grafana. Only some data sources are supported right now. They include `Graphite`, `Prometheus`, `InfluxDB` and `OpenTSDB`. ### Clustering @@ -59,8 +59,8 @@ specify a query letter, time range and an aggregation function. avg() OF query(A, 5m, now) IS BELOW 14 ``` -- `avg()` Controls how the values for **each** serie should be reduced to a value that can be compared against the threshold. Click on the function to change it to another aggregation function. -- `query(A, 5m, now)` The letter defines what query to execute from the **Metrics** tab. The second two parameters defines the time range, `5m, now` means 5 minutes from now to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes from now to 2 minutes from now. This is useful if you want to ignore the last 2 minutes of data. +- `avg()` Controls how the values for **each** series should be reduced to a value that can be compared against the threshold. Click on the function to change it to another aggregation function. +- `query(A, 5m, now)` The letter defines what query to execute from the **Metrics** tab. The second two parameters define the time range, `5m, now` means 5 minutes from now to now. You can also do `10m, now-2m` to define a time range that will be 10 minutes from now to 2 minutes from now. This is useful if you want to ignore the last 2 minutes of data. - `IS BELOW 14` Defines the type of threshold and the threshold value. You can click on `IS BELOW` to change the type of threshold. The query used in an alert rule cannot contain any template variables. Currently we only support `AND` and `OR` operators between conditions and they are executed serially. @@ -74,7 +74,7 @@ of another alert in your conditions, and `Time Of Day`. #### Multiple Series If a query returns multiple series then the aggregation function and threshold check will be evaluated for each series. -What Grafana does not do currently is track alert rule state **per series**. This has implications that is exemplified +What Grafana does not do currently is track alert rule state **per series**. This has implications that are detailed in the scenario below. - Alert condition with query that returns 2 series: **server1** and **server2** @@ -89,8 +89,7 @@ we plan to track state **per series** in a future release. ### No Data / Null values -Below you condition you can configure how the rule evaluation engine should handle queries that return no data or only null valued -data. +Below your conditions you can configure how the rule evaluation engine should handle queries that return no data or only null values. No Data Option | Description ------------ | ------------- @@ -100,23 +99,23 @@ Keep Last State | Keep the current alert rule state, what ever it is. ### Execution errors or timeouts -The last option is how to handle execution or timeout errors. +The last option tells how to handle execution or timeout errors. Error or timeout option | Description ------------ | ------------- Alerting | Set alert rule state to `Alerting` Keep Last State | Keep the current alert rule state, what ever it is. -If you an unreliable time series store that where queries sometime timeout or fail randomly you can set this option -t `Keep Last State` to basically ignore them. +If you have an unreliable time series store from which queries sometime timeout or fail randomly you can set this option +to `Keep Last State` in order to basically ignore them. ## Notifications In alert tab you can also specify alert rule notifications along with a detailed messsage about the alert rule. -The message can contain anything, information about how you might solve the issue, link to runbook etc. +The message can contain anything, information about how you might solve the issue, link to runbook, etc. The actual notifications are configured and shared between multiple alerts. Read the -[Notifications]({{< relref "notifications.md" >}}) guide for how to configure and setup notifications. +[notifications]({{< relref "notifications.md" >}}) guide for how to configure and setup notifications. ## Alert State History & Annotations @@ -129,7 +128,7 @@ submenu in the alert tab to view & clear state history. {{< imgbox max-width="40%" img="/img/docs/v4/alert_test_rule.png" caption="Test Rule" >}} First level of troubleshooting you can do is hit the **Test Rule** button. You will get result back that you can expand -to the point where you can see the raw data that was returned form your query. +to the point where you can see the raw data that was returned from your query. Further troubleshooting can also be done by inspecting the grafana-server log. If it's not an error or for some reason the log does not say anything you can enable debug logging for some relevant components. This is done From c0b0a54a8f47bf93e5543bd27398dde3e0849ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Aug 2017 14:01:09 +0200 Subject: [PATCH 27/71] fix: search bug where search was hidden when you click starred or tags filter links, fixes #8981 --- public/app/core/components/grafana_app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/core/components/grafana_app.ts b/public/app/core/components/grafana_app.ts index f910e124a49..ff5751f33dc 100644 --- a/public/app/core/components/grafana_app.ts +++ b/public/app/core/components/grafana_app.ts @@ -192,7 +192,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv) { // hide search if (body.find('.search-container').length > 0) { - if (target.parents('.search-results-container').length === 0) { + if (target.parents('.search-results-container, .search-field-wrapper').length === 0) { scope.$apply(function() { scope.appEvent('hide-dash-search'); }); From 6e0f767af7a17fff27ae6c741e294ee2d5204cd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 4 Aug 2017 14:04:16 +0200 Subject: [PATCH 28/71] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a97b1a77144..db205f32c0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ * **GitHub OAuth**: Support for GitHub organizations with 100+ teams. [#8846](https://github.com/grafana/grafana/issues/8846), thx [@skwashd](https://github.com/skwashd) * **Graphite**: Calls to Graphite api /metrics/find now include panel or dashboad time range (from & until) in most cases, [#8055](https://github.com/grafana/grafana/issues/8055) +# 4.4.3 (unreleased) + +## Bug Fixes + +* **Search**: Fix for issue that casued search view to hide when you clicked starred or tags filters, fixes [#8981](https://github.com/grafana/grafana/issues/8981) + # 4.4.2 (2017-08-01) ## Bug Fixes From f547c93a4f6ac9594251e7fa6051338d900ce3f7 Mon Sep 17 00:00:00 2001 From: Jesse White Date: Mon, 7 Aug 2017 03:23:33 -0400 Subject: [PATCH 29/71] fix: hide modals when pressing Esc, fixes #8988 (#8994) --- public/app/core/services/keybindingSrv.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index 46d108b43df..f03ca8c8003 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -214,12 +214,8 @@ export class KeybindingSrv { if (popups.length > 0) { return; } - // close modals - var modalData = $(".modal").data(); - if (modalData && modalData.$scope && modalData.$scope.dismiss) { - modalData.$scope.dismiss(); - } + scope.appEvent('hide-modal'); scope.appEvent('hide-dash-editor'); scope.appEvent('panel-change-view', {fullscreen: false, edit: false}); }); From 97d1676fe822d66afaee1374f3995049bef791d8 Mon Sep 17 00:00:00 2001 From: Louis Ventre Date: Mon, 7 Aug 2017 09:26:32 +0200 Subject: [PATCH 30/71] Add time extremity with InfluxDB (#8722) --- public/app/plugins/datasource/influxdb/datasource.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index dc9b83223fa..10f6d47f0b9 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -263,10 +263,10 @@ export default class InfluxDatasource { var fromIsAbsolute = from[from.length-1] === 'ms'; if (until === 'now()' && !fromIsAbsolute) { - return 'time > ' + from; + return 'time >= ' + from; } - return 'time > ' + from + ' and time < ' + until; + return 'time >= ' + from + ' and time <= ' + until; } getInfluxTime(date, roundUp) { @@ -287,4 +287,3 @@ export default class InfluxDatasource { return date.valueOf() + 'ms'; } } - From 8930e04f2b94a687ff4bd7b3e188bbb70eb07de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 7 Aug 2017 09:29:54 +0200 Subject: [PATCH 31/71] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db205f32c0f..22bbf454956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ * **GitHub OAuth**: Support for GitHub organizations with 100+ teams. [#8846](https://github.com/grafana/grafana/issues/8846), thx [@skwashd](https://github.com/skwashd) * **Graphite**: Calls to Graphite api /metrics/find now include panel or dashboad time range (from & until) in most cases, [#8055](https://github.com/grafana/grafana/issues/8055) +## Changes + +* **InfluxDB**: Change time range filter for absolute time ranges to be inclusive instead of exclusive [#8319](https://github.com/grafana/grafana/issues/8319, thx [@Oxydros](https://github.com/Oxydros) + # 4.4.3 (unreleased) ## Bug Fixes From ee297487a5872a2a30cb453e8697c7d77dc37c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 7 Aug 2017 09:31:04 +0200 Subject: [PATCH 32/71] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22bbf454956..0166901b845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ ## Changes -* **InfluxDB**: Change time range filter for absolute time ranges to be inclusive instead of exclusive [#8319](https://github.com/grafana/grafana/issues/8319, thx [@Oxydros](https://github.com/Oxydros) +* **InfluxDB**: Change time range filter for absolute time ranges to be inclusive instead of exclusive [#8319(https://github.com/grafana/grafana/issues/8319, thx [@Oxydros](https://github.com/Oxydros) # 4.4.3 (unreleased) From 0bd03098eafc34e3cffa317cf4afa31be440afc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 7 Aug 2017 09:31:31 +0200 Subject: [PATCH 33/71] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0166901b845..cb3ebd59615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ ## Changes -* **InfluxDB**: Change time range filter for absolute time ranges to be inclusive instead of exclusive [#8319(https://github.com/grafana/grafana/issues/8319, thx [@Oxydros](https://github.com/Oxydros) +* **InfluxDB**: Change time range filter for absolute time ranges to be inclusive instead of exclusive [#8319](https://github.com/grafana/grafana/issues/8319), thx [@Oxydros](https://github.com/Oxydros) # 4.4.3 (unreleased) From 4f9cfcd63291913dfa6624cc2bcbbc1e62ef3f55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 7 Aug 2017 09:35:32 +0200 Subject: [PATCH 34/71] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3ebd59615..56fc9454610 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ## Bug Fixes * **Search**: Fix for issue that casued search view to hide when you clicked starred or tags filters, fixes [#8981](https://github.com/grafana/grafana/issues/8981) +* **Modals**: ESC key now closes modal again, fixes [#8981](https://github.com/grafana/grafana/issues/8988), thx [@j-white](https://github.com/j-white) # 4.4.2 (2017-08-01) From c082372ffee4731f56257330250cc59d374e06f4 Mon Sep 17 00:00:00 2001 From: Patrick Tescher Date: Mon, 7 Aug 2017 00:39:36 -0700 Subject: [PATCH 35/71] Add a Safari Pinned Tab Icon (#8983) Icon adopted from grafana_icon.svg Reference document: https://developer.apple.com/library/content/documentation/AppleApplicati ons/Reference/SafariWebContent/pinnedTabs/pinnedTabs.html --- public/img/grafana_mask_icon.svg | 12 ++++++++++++ public/views/index.html | 1 + 2 files changed, 13 insertions(+) create mode 100644 public/img/grafana_mask_icon.svg diff --git a/public/img/grafana_mask_icon.svg b/public/img/grafana_mask_icon.svg new file mode 100644 index 00000000000..619a218d44e --- /dev/null +++ b/public/img/grafana_mask_icon.svg @@ -0,0 +1,12 @@ + + + + grafana_mask_icon + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/public/views/index.html b/public/views/index.html index 6d5d65e5201..b3c504740da 100644 --- a/public/views/index.html +++ b/public/views/index.html @@ -17,6 +17,7 @@ [[end]] + From 1e5778174c01aa3f5f3320ae800f8ee6112b336f Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 7 Aug 2017 10:00:29 +0200 Subject: [PATCH 36/71] login: regenerates session id on login --- pkg/api/login.go | 1 + pkg/middleware/session.go | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/pkg/api/login.go b/pkg/api/login.go index baec5f5f6c0..a9fcbee8e8e 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -143,6 +143,7 @@ func loginUserWithUser(user *m.User, c *middleware.Context) { c.SetSuperSecureCookie(user.Rands+user.Password, setting.CookieRememberName, user.Login, days, setting.AppSubUrl+"/") } + c.Session.RegenerateId(c) c.Session.Set(middleware.SESS_KEY_USERID, user.Id) } diff --git a/pkg/middleware/session.go b/pkg/middleware/session.go index a6af63d18de..4de111ff3d2 100644 --- a/pkg/middleware/session.go +++ b/pkg/middleware/session.go @@ -103,6 +103,8 @@ type SessionStore interface { Destory(*Context) error // init Start(*Context) error + // RegenerateId regenerates the session id + RegenerateId(*Context) error } type SessionWrapper struct { @@ -116,6 +118,12 @@ func (s *SessionWrapper) Start(c *Context) error { return err } +func (s *SessionWrapper) RegenerateId(c *Context) error { + var err error + s.session, err = s.manager.RegenerateId(c.Context) + return err +} + func (s *SessionWrapper) Set(k interface{}, v interface{}) error { if s.session != nil { return s.session.Set(k, v) From 132cd36b0cc6ce4bd732d16550b2671c12a72a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 7 Aug 2017 10:10:37 +0200 Subject: [PATCH 37/71] Delete fav16.png fixes #8989 --- public/img/fav16.png | Bin 3607 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 public/img/fav16.png diff --git a/public/img/fav16.png b/public/img/fav16.png deleted file mode 100644 index 6f5f809d9cb809a0fc56a130ac6c60a496368ccd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3607 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 From d285045ff6f0d6fe3dbb061211eefe06567a6061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 7 Aug 2017 10:11:39 +0200 Subject: [PATCH 38/71] removed unused images --- public/img/fav_dark_16.png | Bin 4305 -> 0 bytes public/img/fav_dark_32.png | Bin 3204 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 public/img/fav_dark_16.png delete mode 100644 public/img/fav_dark_32.png diff --git a/public/img/fav_dark_16.png b/public/img/fav_dark_16.png deleted file mode 100644 index f22fb86de1f5fac6009350518ea039209bda6759..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4305 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 Date: Mon, 7 Aug 2017 10:20:46 +0200 Subject: [PATCH 39/71] tests: fix after interface change --- pkg/middleware/auth_proxy_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/middleware/auth_proxy_test.go b/pkg/middleware/auth_proxy_test.go index bd587bc0c7f..4da0f52bbcf 100644 --- a/pkg/middleware/auth_proxy_test.go +++ b/pkg/middleware/auth_proxy_test.go @@ -106,6 +106,10 @@ func (s *mockSession) Destory(c *Context) error { return nil } +func (s *mockSession) RegenerateId(c *Context) error { + return nil +} + type mockLdapAuthenticator struct { syncSignedInUserCalled bool } From 0d25357367b66ee6ed5b977d56f6ad1779aef05e Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 7 Aug 2017 10:46:37 +0200 Subject: [PATCH 40/71] build: remove downloaded pkg files after publish --- packaging/publish/publish_both.sh | 2 ++ packaging/publish/publish_testing.sh | 1 + 2 files changed, 3 insertions(+) diff --git a/packaging/publish/publish_both.sh b/packaging/publish/publish_both.sh index 8fd48038ace..38d1b89720a 100755 --- a/packaging/publish/publish_both.sh +++ b/packaging/publish/publish_both.sh @@ -16,3 +16,5 @@ package_cloud push grafana/testing/el/7 grafana-${version}-1.x86_64.rpm package_cloud push grafana/stable/el/7 grafana-${version}-1.x86_64.rpm package_cloud push grafana/stable/el/6 grafana-${version}-1.x86_64.rpm + +rm grafana*.{deb,rpm} diff --git a/packaging/publish/publish_testing.sh b/packaging/publish/publish_testing.sh index 8d27a35b826..3c9e516b7f9 100755 --- a/packaging/publish/publish_testing.sh +++ b/packaging/publish/publish_testing.sh @@ -12,3 +12,4 @@ wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-${rpm_v package_cloud push grafana/testing/el/6 grafana-${rpm_ver}.x86_64.rpm package_cloud push grafana/testing/el/7 grafana-${rpm_ver}.x86_64.rpm +rm grafana*.{deb,rpm} From 54c79c564870bf4b36ce630e93513f485e598ab8 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 7 Aug 2017 10:50:18 +0200 Subject: [PATCH 41/71] updated version to v4.4.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 52de1007d0b..423f8df3349 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "company": "Coding Instinct AB" }, "name": "grafana", - "version": "4.4.2", + "version": "4.4.3", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git" From def45f55da0e34775a31c6ec2951741f8f30e2d9 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Mon, 7 Aug 2017 13:11:57 +0200 Subject: [PATCH 42/71] docs: update installation for version 4.4.3 --- docs/sources/installation/debian.md | 6 +++--- docs/sources/installation/rpm.md | 10 +++++----- docs/sources/installation/windows.md | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index fb21f76b599..193fe4d596c 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -15,7 +15,7 @@ weight = 1 Description | Download ------------ | ------------- -Stable for Debian-based Linux | [grafana_4.4.0_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.4.0_amd64.deb) +Stable for Debian-based Linux | [grafana_4.4.3_amd64.deb](https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.4.3_amd64.deb) Read [Upgrading Grafana]({{< relref "installation/upgrading.md" >}}) for tips and guidance on updating an existing installation. @@ -23,9 +23,9 @@ installation. ## Install Stable ```bash -wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.4.0_amd64.deb +wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.4.3_amd64.deb sudo apt-get install -y adduser libfontconfig -sudo dpkg -i grafana_4.4.0_amd64.deb +sudo dpkg -i grafana_4.4.3_amd64.deb ```