Repository: incubator-eagle Updated Branches: refs/heads/master 60f4fcf5c -> 46ad97843
EAGLE-205 Metric dashboard support multi metrics Support multi metric display in one chart[D[D[D[D[D[D[D[D[D[D[D[D[D[D[D[D[D[D[D[D[D & series[2~ [2~ [3~[3~[3in one chart https://issues.apache.org/jira/browse/EAGLE-205 Author: @zombiej Reviewer: @qingwen220 Closes #129 Project: http://git-wip-us.apache.org/repos/asf/incubator-eagle/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-eagle/commit/46ad9784 Tree: http://git-wip-us.apache.org/repos/asf/incubator-eagle/tree/46ad9784 Diff: http://git-wip-us.apache.org/repos/asf/incubator-eagle/diff/46ad9784 Branch: refs/heads/master Commit: 46ad97843b8ed54b88a813040568aa23f0c5f73a Parents: 60f4fcf Author: jiljiang <jilji...@ebay.com> Authored: Thu Mar 24 15:25:54 2016 +0800 Committer: jiljiang <jilji...@ebay.com> Committed: Thu Mar 24 15:26:22 2016 +0800 ---------------------------------------------------------------------- .../src/main/webapp/app/public/css/main.css | 33 +++ .../app/public/feature/metrics/controller.js | 262 ++++++++++++++----- .../public/feature/metrics/page/dashboard.html | 160 ++++++----- .../webapp/app/public/js/components/nvd3.js | 2 + eagle-webservice/src/main/webapp/package.json | 2 +- 5 files changed, 328 insertions(+), 131 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/46ad9784/eagle-webservice/src/main/webapp/app/public/css/main.css ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/app/public/css/main.css b/eagle-webservice/src/main/webapp/app/public/css/main.css index a68b1fa..8bd6b7d 100644 --- a/eagle-webservice/src/main/webapp/app/public/css/main.css +++ b/eagle-webservice/src/main/webapp/app/public/css/main.css @@ -535,6 +535,10 @@ ul.tree.tree-bordered > li > ul > li > a { height: 200px; } +.nvd3-chart-cntr.lg > svg.nvd3-svg { + height: 400px; +} + /* Tab */ body .tab-content>.tab-pane { display: block; @@ -567,6 +571,35 @@ body .modal-body .nav-stacked > li:last-child { margin-top: 0; } +.box.inner-box { + border: none; + box-shadow: none; + padding: 5px 10px; + margin: 0; + border-bottom: 1px solid #f4f4f4; + position: relative; + border-radius: 0; +} + +.box.inner-box .box-title { + margin: 0 5px 5px 0; + padding: 0; + font-size: 16px; + font-weight: bolder; + display: inline-block; + word-break: break-all; +} + +.box.inner-box .box-tools { + position: absolute; + top: 0; + right: 0; +} + +.box.inner-box:last-child { + border-bottom: none; +} + /* Navigation Tab */ .nav-tabs-custom { position: relative; http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/46ad9784/eagle-webservice/src/main/webapp/app/public/feature/metrics/controller.js ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/app/public/feature/metrics/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/metrics/controller.js index 95e6638..043cdfb 100644 --- a/eagle-webservice/src/main/webapp/app/public/feature/metrics/controller.js +++ b/eagle-webservice/src/main/webapp/app/public/feature/metrics/controller.js @@ -27,13 +27,47 @@ // ============================================================== // ============================================================== + // = Function = + // ============================================================== + // Format dashboard unit. Will adjust format with old version and add miss attributes. + feature.service("DashboardFormatter", function() { + return { + parse: function(unit) { + unit = unit || {}; + unit.groups = unit.groups || []; + + $.each(unit.groups, function (i, group) { + group.charts = group.charts || []; + $.each(group.charts, function (i, chart) { + if (!chart.metrics && chart.metric) { + chart.metrics = [{ + aggregations: chart.aggregations, + dataSource: chart.dataSource, + metric: chart.metric + }]; + + delete chart.aggregations; + delete chart.dataSource; + delete chart.metric; + } else if (!chart.metrics) { + chart.metrics = []; + } + }); + }); + + return unit; + } + }; + }); + + // ============================================================== // = Controller = // ============================================================== // ========================= Dashboard ========================== feature.navItem("dashboard", "Metrics", "line-chart"); - feature.controller('dashboard', function(PageConfig, $scope, $http, $q, UI, Site, Authorization, Application, Entities) { + feature.controller('dashboard', function(PageConfig, $scope, $http, $q, UI, Site, Authorization, Application, Entities, DashboardFormatter) { var _siteApp = Site.currentSiteApplication(); var _druidConfig = _siteApp.configObj.druid; var _refreshInterval; @@ -80,7 +114,7 @@ // ====================== Function ====================== $scope.setAuthRefresh = function(item) { $scope.autoRefreshSelect = item; - $scope.chartRefresh(true); + $scope.refreshAllChart(true); }; $scope.refreshTimeDisplay = function() { @@ -161,16 +195,23 @@ // Confirm new metric $scope.confirmSelectMetric = function() { var group = $scope.tabHolder.selectedPane.data; - $("#metricMDL").modal('hide'); - - group.charts.push({ - chart: "line", + var metric = { dataSource: $scope._newMetricDataSrc.dataSource, metric: $scope._newMetricDataMetric, aggregations: ["max"] - }); + }; + $("#metricMDL").modal('hide'); - $scope.chartRefresh(); + if($scope.metricForConfigChart) { + $scope.configPreviewChart.metrics.push(metric); + $scope.refreshChart($scope.configPreviewChart, true, true); + } else { + group.charts.push({ + chart: "line", + metrics: [metric] + }); + $scope.refreshAllChart(); + } }; // ======================== Menu ======================== @@ -227,8 +268,8 @@ }); $scope.dashboardList._promise.then(function(list) { $scope.dashboardEntity = list[0]; - $scope.dashboard = $scope.dashboardEntity ? common.parseJSON($scope.dashboardEntity.value) : {groups: []}; - $scope.chartRefresh(); + $scope.dashboard = DashboardFormatter.parse(common.parseJSON($scope.dashboardEntity.value)); + $scope.refreshAllChart(); }).finally(function() { $scope.dashboardReady = true; }); @@ -265,6 +306,8 @@ // ======================= Chart ======================== $scope.configTargetChart = null; $scope.configPreviewChart = null; + $scope.metricForConfigChart = false; + $scope.viewChart = null; $scope.chartConfig = { xType: "time" @@ -279,10 +322,14 @@ $scope.chartSeriesList = [ {name: "Min", series: "min"}, - {name: "Max", series: "max"} + {name: "Max", series: "max"}, + {name: "Avg", series: "avg"}, + {name: "Count", series: "count"}, + {name: "Sum", series: "sum"} ]; $scope.newChart = function() { + $scope.metricForConfigChart = false; $("#metricMDL").modal(); }; @@ -290,26 +337,38 @@ $scope.configPreviewChart.min = $scope.configPreviewChart.min === 0 ? undefined : 0; }; - $scope.seriesChecked = function(chart, series) { - if(!chart) return false; - return $.inArray(series, chart.aggregations || []) !== -1; + $scope.seriesChecked = function(metric, series) { + if(!metric) return false; + return $.inArray(series, metric.aggregations || []) !== -1; }; - $scope.seriesCheckClick = function(chart, series) { - if(!chart) return; - if($scope.seriesChecked(chart, series)) { - common.array.remove(series, chart.aggregations); + $scope.seriesCheckClick = function(metric, series, chart) { + if(!metric || !chart) return; + if($scope.seriesChecked(metric, series)) { + common.array.remove(series, metric.aggregations); } else { - chart.aggregations.push(series); + metric.aggregations.push(series); } $scope.chartSeriesUpdate(chart); }; $scope.chartSeriesUpdate = function(chart) { - chart._data = $.map(chart._oriData, function(series) { - if($.inArray(series.key, chart.aggregations) !== -1) return series; + chart._data = $.map(chart._oriData, function(groupData, i) { + var metric = chart.metrics[i]; + return $.map(groupData, function(series) { + if($.inArray(series._key, metric.aggregations) !== -1) return series; + }); }); }; + $scope.configAddMetric = function() { + $scope.metricForConfigChart = true; + $("#metricMDL").modal(); + }; + + $scope.configRemoveMetric = function(metric) { + common.array.remove(metric, $scope.configPreviewChart.metrics); + }; + $scope.getChartConfig = function(chart) { if(!chart) return null; @@ -321,7 +380,10 @@ $scope.configChart = function(chart) { $scope.configTargetChart = chart; - $scope.configPreviewChart = $.extend({}, chart, {aggregations: (chart.aggregations || []).slice()}); + $scope.configPreviewChart = $.extend({}, chart); + $scope.configPreviewChart.metrics = $.map(chart.metrics, function(metric) { + return $.extend({}, metric, {aggregations: (metric.aggregations || []).slice()}); + }); delete $scope.configPreviewChart._config; $("#chartMDL").modal(); setTimeout(function() { @@ -331,71 +393,122 @@ $scope.confirmUpdateChart = function() { $("#chartMDL").modal('hide'); - common.extend($scope.configTargetChart, $scope.configPreviewChart); + $.extend($scope.configTargetChart, $scope.configPreviewChart); $scope.chartSeriesUpdate($scope.configTargetChart); if($scope.configTargetChart._holder) $scope.configTargetChart._holder.refreshAll(); + $scope.configPreviewChart = null; }; $scope.deleteChart = function(group, chart) { UI.deleteConfirm(chart.metric).then(null, null, function(holder) { common.array.remove(chart, group.charts); holder.closeFunc(); - $scope.chartRefresh(false, true); + $scope.refreshAllChart(false, true); }); }; - $scope.chartRefresh = function(forceRefresh, refreshAll) { + $scope.showChart = function(chart) { + $scope.viewChart = chart; + $("#chartViewMDL").modal(); + setTimeout(function() { + $(window).resize(); + }, 200); + }; + + $scope.refreshChart = function(chart, forceRefresh, refreshAll) { + var _intervals = $scope.startTime.toISOString() + "/" + $scope.endTime.toISOString(); + + function _refreshChart() { + if (chart._holder) { + if (refreshAll) { + chart._holder.refreshAll(); + } else { + chart._holder.refresh(); + } + } + } + + var _tmpData, _metricPromiseList; + + if (chart._data && !forceRefresh) { + // Refresh chart without reload + _refreshChart(); + } else { + // Refresh chart with reload + _tmpData = []; + _metricPromiseList = $.map(chart.metrics, function (metric, k) { + // Each Metric + var _query = JSON.stringify({ + "queryType": "groupBy", + "dataSource": metric.dataSource, + "granularity": $scope.autoRefreshSelect.timeDes, + "dimensions": ["metric"], + "filter": {"type": "selector", "dimension": "metric", "value": metric.metric}, + "aggregations": [ + { + "type": "max", + "name": "max", + "fieldName": "maxValue" + }, + { + "type": "min", + "name": "min", + "fieldName": "maxValue" + }, + { + "type": "count", + "name": "count", + "fieldName": "maxValue" + }, + { + "type": "longSum", + "name": "sum", + "fieldName": "maxValue" + } + ], + "postAggregations" : [ + { + "type": "javascript", + "name": "avg", + "fieldNames": ["sum", "count"], + "function": "function(sum, cnt) { return sum / cnt;}" + } + ], + "intervals": [_intervals] + }); + + return $http.post(_druidConfig.broker + "/druid/v2", _query, {withCredentials: false}).then(function (response) { + var _data = nvd3.convert.druid([response.data]); + _tmpData[k] = _data; + + // Process series name + $.each(_data, function(i, series) { + series._key = series.key; + if(chart.metrics.length > 1) { + series.key = metric.metric.replace(/^.*\./, "") + "-" +series._key; + } + }); + }); + }); + + $q.all(_metricPromiseList).then(function() { + chart._oriData = _tmpData; + $scope.chartSeriesUpdate(chart); + _refreshChart(); + }); + } + }; + + $scope.refreshAllChart = function(forceRefresh, refreshAll) { setTimeout(function() { $scope.endTime = app.time.now(); $scope.startTime = $scope.autoRefreshSelect.getStartTime($scope.endTime); - var _intervals = $scope.startTime.toISOString() + "/" + $scope.endTime.toISOString(); $scope.refreshTimeDisplay(); $.each($scope.dashboard.groups, function (i, group) { $.each(group.charts, function (j, chart) { - var _data = JSON.stringify({ - "queryType": "groupBy", - "dataSource": chart.dataSource, - "granularity": $scope.autoRefreshSelect.timeDes, - "dimensions": ["metric"], - "filter": {"type": "selector", "dimension": "metric", "value": chart.metric}, - "aggregations": [ - { - "type": "max", - "name": "max", - "fieldName": "maxValue" - }, - { - "type": "min", - "name": "min", - "fieldName": "maxValue" - } - ], - "intervals": [_intervals] - }); - - if (!chart._data || forceRefresh) { - $http.post(_druidConfig.broker + "/druid/v2", _data, {withCredentials: false}).then(function (response) { - chart._oriData = nvd3.convert.druid([response.data]); - $scope.chartSeriesUpdate(chart); - if(chart._holder) { - if(refreshAll) { - chart._holder.refreshAll(); - } else { - chart._holder.refresh(); - } - } - }); - } else { - if(chart._holder) { - if(refreshAll) { - chart._holder.refreshAll(); - } else { - chart._holder.refresh(); - } - } - } + $scope.refreshChart(chart, forceRefresh, refreshAll); }); }); @@ -415,7 +528,7 @@ _refreshInterval = setInterval(function() { if(!$scope.dashboardReady) return; - $scope.chartRefresh(true); + $scope.refreshAllChart(true); }, 1000 * 30); // > Chart UI @@ -428,6 +541,17 @@ }, 1); }; + // ========================= UI ========================= + $("#metricMDL").on('hidden.bs.modal', function () { + if($(".modal-backdrop").length) { + $("body").addClass("modal-open"); + } + }); + + $("#chartViewMDL").on('hidden.bs.modal', function () { + $scope.viewChart = null; + }); + // ====================== Clean Up ====================== $scope.$on('$destroy', function() { clearInterval(_refreshInterval); http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/46ad9784/eagle-webservice/src/main/webapp/app/public/feature/metrics/page/dashboard.html ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/app/public/feature/metrics/page/dashboard.html b/eagle-webservice/src/main/webapp/app/public/feature/metrics/page/dashboard.html index 48fe738..4e4f3a9 100644 --- a/eagle-webservice/src/main/webapp/app/public/feature/metrics/page/dashboard.html +++ b/eagle-webservice/src/main/webapp/app/public/feature/metrics/page/dashboard.html @@ -60,13 +60,16 @@ <div sortable class="row narrow" ng-model="group.charts" update-func="chartSwitchRefresh" ng-show="group.charts.length"> <div ng-repeat="chart in group.charts track by $index" class="col-md-{{chart.size || 6}}"> <div class="nvd3-chart-wrapper"> - <div nvd3="chart._data" data-holder="chart._holder" data-title="{{chart.title || chart.metric}}" data-watching="false" + <div nvd3="chart._data" data-holder="chart._holder" data-title="{{chart.title || chart.metrics[0].metric}}" data-watching="false" data-chart="{{chart.chart || 'line'}}" data-config="getChartConfig(chart)" class="nvd3-chart-cntr"></div> - <div class="nvd3-chart-config" ng-if="Auth.isRole('ROLE_ADMIN')"> - <a class="fa fa-minus" ng-click="configChartSize(chart, -1)"></a> - <a class="fa fa-plus" ng-click="configChartSize(chart, 1)"></a> - <a class="fa fa-cog" ng-click="configChart(chart)"></a> - <a class="fa fa-trash" ng-click="deleteChart(group, chart)"></a> + <div class="nvd3-chart-config"> + <a class="fa fa-expand" ng-click="showChart(chart, -1)"></a> + <span ng-if="Auth.isRole('ROLE_ADMIN')"> + <a class="fa fa-minus" ng-click="configChartSize(chart, -1)"></a> + <a class="fa fa-plus" ng-click="configChartSize(chart, 1)"></a> + <a class="fa fa-cog" ng-click="configChart(chart)"></a> + <a class="fa fa-trash" ng-click="deleteChart(group, chart)"></a> + </span> </div> </div> </div> @@ -86,6 +89,91 @@ +<!-- Modal: Chart configuration --> +<div class="modal fade" id="chartMDL" tabindex="-1" role="dialog"> + <div class="modal-dialog modal-lg" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + <h4 class="modal-title">Chart Configuration</h4> + </div> + <div class="modal-body"> + <div class="row"> + <div class="col-md-6"> + <div class="nvd3-chart-wrapper"> + <div nvd3="configPreviewChart._data" data-title="{{configPreviewChart.title || configPreviewChart.metrics[0].metric}}" + data-watching="true" data-chart="{{configPreviewChart.chart || 'line'}}" data-config="getChartConfig(configPreviewChart)" class="nvd3-chart-cntr"></div> + </div> + </div> + <div class="col-md-6"> + <!-- Chart Configuration --> + <table class="table"> + <tbody> + <tr> + <th width="100">Name</th> + <td><input type="text" class="form-control input-xs" ng-model="configPreviewChart.title" placeholder="Default: {{configPreviewChart.metrics[0].metric}}" /></td> + </tr> + <tr> + <th>Chart Type</th> + <td> + <div class="btn-group" data-toggle="buttons"> + <label class="btn btn-default btn-xs" ng-class="{active: (configPreviewChart.chart || 'line') === type.chart}" + ng-repeat="type in chartTypeList track by $index" ng-click="configPreviewChart.chart = type.chart;"> + <input type="radio" name="chartType" autocomplete="off" + ng-checked="(configPreviewChart.chart || 'line') === type.chart"> + <span class="fa fa-{{type.icon}}"></span> + </label> + </div> + </td> + </tr> + <tr> + <th>Minimum</th> + <td><input type="checkbox" ng-checked="configPreviewChart.min === 0" ng-disabled="configPreviewChart.chart === 'area' || configPreviewChart.chart === 'pie'" + ng-click="configPreviewChartMinimumCheck()" /></td> + </tr> + <tr> + <th>Metrics</th> + <td> + <div ng-repeat="metric in configPreviewChart.metrics" class="box inner-box"> + <div class="box-tools"> + <button class="btn btn-box-tool" ng-click="configRemoveMetric(metric)"> + <span class="fa fa-times"></span> + </button> + </div> + + <h3 class="box-title">{{metric.metric}}</h3> + <div class="checkbox noMargin" ng-repeat="series in chartSeriesList track by $index"> + <label> + <input type="checkbox" ng-checked="seriesChecked(metric, series.series)" + ng-click="seriesCheckClick(metric, series.series, configPreviewChart)" /> + {{series.name}} + </label> + </div> + </div> + <a ng-click="configAddMetric()">+ Add Metric</a> + </td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal"> + Close + </button> + <button type="button" class="btn btn-primary" ng-click="confirmUpdateChart()"> + Confirm + </button> + </div> + </div> + </div> +</div> + + + <!-- Modal: Metric selector --> <div class="modal fade" id="metricMDL" tabindex="-1" role="dialog"> <div class="modal-dialog modal-lg" role="document"> @@ -138,74 +226,24 @@ -<!-- Modal: Chart configuration --> -<div class="modal fade" id="chartMDL" tabindex="-1" role="dialog"> +<!-- Modal: Chart View --> +<div class="modal fade" id="chartViewMDL" tabindex="-1" role="dialog"> <div class="modal-dialog modal-lg" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> - <h4 class="modal-title">Chart Configuration</h4> + <h4 class="modal-title">{{viewChart.title || viewChart.metrics[0].metric}}</h4> </div> <div class="modal-body"> - <div class="row"> - <div class="col-md-6"> - <div class="nvd3-chart-wrapper"> - <div nvd3="configPreviewChart._data" data-title="{{configPreviewChart.title || configPreviewChart.metric}}" - data-watching="true" data-chart="{{configPreviewChart.chart || 'line'}}" data-config="getChartConfig(configPreviewChart)" class="nvd3-chart-cntr"></div> - </div> - </div> - <div class="col-md-6"> - <!-- Chart Configuration --> - <table class="table"> - <tbody> - <tr> - <th width="100">Name</th> - <td><input type="text" class="form-control input-xs" ng-model="configPreviewChart.title" placeholder="Default: {{configPreviewChart.metric}}" /></td> - </tr> - <tr> - <th>Chart Type</th> - <td> - <div class="btn-group" data-toggle="buttons"> - <label class="btn btn-default btn-xs" ng-class="{active: (configPreviewChart.chart || 'line') === type.chart}" - ng-repeat="type in chartTypeList track by $index" ng-click="configPreviewChart.chart = type.chart;"> - <input type="radio" name="chartType" autocomplete="off" - ng-checked="(configPreviewChart.chart || 'line') === type.chart"> - <span class="fa fa-{{type.icon}}"></span> - </label> - </div> - </td> - </tr> - <tr> - <th>Minimum</th> - <td><input type="checkbox" ng-checked="configPreviewChart.min === 0" ng-disabled="configPreviewChart.chart === 'area' || configPreviewChart.chart === 'pie'" - ng-click="configPreviewChartMinimumCheck()" /></td> - </tr> - <tr> - <th>Series</th> - <td> - <div class="checkbox noMargin" ng-repeat="series in chartSeriesList track by $index"> - <label> - <input type="checkbox" ng-checked="seriesChecked(configPreviewChart, series.series)" - ng-click="seriesCheckClick(configPreviewChart, series.series)" /> - {{series.name}} - </label> - </div> - </td> - </tr> - </tbody> - </table> - </div> - </div> + <div nvd3="viewChart._data" data-title="{{viewChart.title || viewChart.metrics[0].metric}}" + data-watching="true" data-chart="{{viewChart.chart || 'line'}}" data-config="getChartConfig(viewChart)" class="nvd3-chart-cntr lg"></div> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal"> Close </button> - <button type="button" class="btn btn-primary" ng-click="confirmUpdateChart()"> - Confirm - </button> </div> </div> </div> http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/46ad9784/eagle-webservice/src/main/webapp/app/public/js/components/nvd3.js ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/nvd3.js b/eagle-webservice/src/main/webapp/app/public/js/components/nvd3.js index 24c71d1..1f3c13a 100644 --- a/eagle-webservice/src/main/webapp/app/public/js/components/nvd3.js +++ b/eagle-webservice/src/main/webapp/app/public/js/components/nvd3.js @@ -220,6 +220,8 @@ eagleComponents.directive('nvd3', function(nvd3) { if(!_chart) return; var _axis = _chart[axis + "Axis"]; + if(!_axis) return; + switch(type) { case "decimal": case "decimals": http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/46ad9784/eagle-webservice/src/main/webapp/package.json ---------------------------------------------------------------------- diff --git a/eagle-webservice/src/main/webapp/package.json b/eagle-webservice/src/main/webapp/package.json index a85bf4a..f849091 100644 --- a/eagle-webservice/src/main/webapp/package.json +++ b/eagle-webservice/src/main/webapp/package.json @@ -22,7 +22,7 @@ "angular-ui-bootstrap" : "1.1.2", "angular-ui-router" : "~0.2.18", "d3" : "3.5.16", - "zombiej-nvd3" : "1.8.2-1", + "zombiej-nvd3" : "1.8.2-3", "jquery-slimscroll" :"1.3.6", "zombiej-bootstrap-components" : "1.1.1" },