Updated code to support Druid Sum/Count Query
Code has been updated to Support Druid Count/Sum Query in 0.3.1.
Author: senthilkumar <senthikumar@ebay.com>
Closes #276 from senthilec566/EAGLE-396.
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 27faf07..8beba0d 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,27 +195,36 @@
// 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 ========================
+ function _checkGroupName(entity) {
+ if(common.array.find(entity.name, $scope.dashboard.groups, "name")) {
+ return "Group name conflict";
+ }
+ }
+
$scope.newGroup = function() {
if($scope.lock) return;
- UI.createConfirm("Group", {}, [{field: "name"}], function(entity) {
- if(common.array.find(entity.name, $scope.dashboard.groups, "name")) {
- return "Group name conflict";
- }
- }).then(null, null, function(holder) {
+ UI.createConfirm("Group", {}, [{field: "name"}], _checkGroupName).then(null, null, function(holder) {
$scope.dashboard.groups.push({
name: holder.entity.name,
charts: []
@@ -194,6 +237,14 @@
});
};
+ function renameGroup() {
+ var group = $scope.tabHolder.selectedPane.data;
+ UI.updateConfirm("Group", {}, [{field: "name", name: "New Name"}], _checkGroupName).then(null, null, function(holder) {
+ group.name = holder.entity.name;
+ holder.closeFunc();
+ });
+ }
+
function deleteGroup() {
var group = $scope.tabHolder.selectedPane.data;
UI.deleteConfirm(group.name).then(null, null, function(holder) {
@@ -215,6 +266,7 @@
$scope.menu = Authorization.isRole('ROLE_ADMIN') ? [
{icon: "cog", title: "Configuration", list: [
_menu_newChart,
+ {icon: "pencil", title: "Rename Group", func: renameGroup},
{icon: "trash", title: "Delete Group", danger: true, func: deleteGroup}
]},
{icon: "plus", title: "New Group", func: $scope.newGroup}
@@ -227,8 +279,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 +317,8 @@
// ======================= Chart ========================
$scope.configTargetChart = null;
$scope.configPreviewChart = null;
+ $scope.metricForConfigChart = false;
+ $scope.viewChart = null;
$scope.chartConfig = {
xType: "time"
@@ -279,38 +333,53 @@
$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();
};
$scope.configPreviewChartMinimumCheck = function() {
$scope.configPreviewChart.min = $scope.configPreviewChart.min === 0 ? undefined : 0;
- window.ccc1 = $scope.getChartConfig($scope.configPreviewChart);
};
- $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;
@@ -323,6 +392,9 @@
$scope.configChart = function(chart) {
$scope.configTargetChart = chart;
$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() {
@@ -332,72 +404,168 @@
$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();
+ $scope.refreshAllChart(false, true);
});
};
- $scope.chartRefresh = function(forceRefresh) {
+ $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) chart._holder.refresh();
- });
- } else {
- if(chart._holder) chart._holder.refresh();
- }
+ $scope.refreshChart(chart, forceRefresh, refreshAll);
});
});
+
+ $(window).resize();
}, 0);
};
+ $scope.chartSwitchRefresh = function(source, target) {
+ var _oriSize = source.size;
+ source.size = target.size;
+ target.size = _oriSize;
+
+ if(source._holder) source._holder.refreshAll();
+ if(target._holder) target._holder.refreshAll();
+
+ };
+
_refreshInterval = setInterval(function() {
if(!$scope.dashboardReady) return;
- $scope.chartRefresh(true);
+ $scope.refreshAllChart(true);
}, 1000 * 30);
+ // > Chart UI
+ $scope.configChartSize = function(chart, sizeOffset) {
+ chart.size = (chart.size || 6) + sizeOffset;
+ if(chart.size <= 0) chart.size = 1;
+ if(chart.size > 12) chart.size = 12;
+ setTimeout(function() {
+ $(window).resize();
+ }, 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);
});
});
-})();
\ No newline at end of file
+})();
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 df85c10..128be6c 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
@@ -55,16 +55,21 @@
</div>
</div>
-<div tabs menu="menu" holder="tabHolder" ng-show="dashboard.groups.length">
+<div tabs menu="menu" holder="tabHolder" ng-show="dashboard.groups.length" data-sortable-model="Auth.isRole('ROLE_ADMIN') ? dashboard.groups : null">
<pane ng-repeat="group in dashboard.groups" data-data="group" data-title="{{group.name}}">
- <div class="row">
- <div ng-repeat="chart in group.charts track by $index" class="col-md-6">
+ <div uie-sortable ng-model="group.charts" class="row narrow" sortable-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.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-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>
@@ -72,13 +77,103 @@
<p ng-if="!group.charts.length">
Empty group.
- <span ng-if="Auth.isRole('ROLE_ADMIN')">Click <a ng-click="newChart()">here</a> to add metric.</span>
+ <span ng-if="Auth.isRole('ROLE_ADMIN')">
+ Click
+ <span ng-hide="dataSourceListReady" class="fa fa-refresh fa-spin"></span>
+ <a ng-show="dataSourceListReady" ng-click="newChart()">here</a>
+ to add metric.
+ </span>
</p>
</pane>
</div>
+<!-- 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">
@@ -131,71 +226,25 @@
-<!-- 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.metric}}" data-holder="configPreviewChart._holder"
- 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">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>
-</div>
\ No newline at end of file
+</div>