Browse Source

feat(tablepanel): lots of work on table panel

Torkel Ödegaard 10 years ago
parent
commit
90cca93951

+ 17 - 7
public/app/core/directives/dropdown_typeahead.js

@@ -45,16 +45,25 @@ function (_, $, coreModule) {
         }
 
         var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) {
-          _.each(value.submenu, function(item, subIndex) {
-            item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
-            memo.push(value.text + ' ' + item.text);
-          });
+          if (!value.submenu) {
+            value.click = 'menuItemSelected(' + index + ')';
+            memo.push(value.text);
+          } else {
+            _.each(value.submenu, function(item, subIndex) {
+              item.click = 'menuItemSelected(' + index + ',' + subIndex + ')';
+              memo.push(value.text + ' ' + item.text);
+            });
+          }
           return memo;
         }, []);
 
         $scope.menuItemSelected = function(index, subIndex) {
-          var item = $scope.menuItems[index];
-          $scope.dropdownTypeaheadOnSelect({$item: item, $subItem: item.submenu[subIndex]});
+          var menuItem = $scope.menuItems[index];
+          var payload = {$item: menuItem};
+          if (menuItem.submenu && subIndex !== void 0) {
+            payload.$subItem = menuItem.submenu[subIndex];
+          }
+          $scope.dropdownTypeaheadOnSelect(payload);
         };
 
         $input.attr('data-provide', 'typeahead');
@@ -65,9 +74,10 @@ function (_, $, coreModule) {
           updater: function (value) {
             var result = {};
             _.each($scope.menuItems, function(menuItem) {
+              result.$item = menuItem;
+
               _.each(menuItem.submenu, function(submenuItem) {
                 if (value === (menuItem.text + ' ' + submenuItem.text)) {
-                  result.$item = menuItem;
                   result.$subItem = submenuItem;
                 }
               });

+ 2 - 2
public/app/features/panel/panel_helper.js

@@ -32,9 +32,9 @@ function (angular, _, $, kbn, dateMath, rangeUtil) {
       scope.timing.renderEnd = new Date().getTime();
     };
 
-    this.broadcastRender = function(scope, data) {
+    this.broadcastRender = function(scope, arg1, arg2) {
       this.setTimeRenderStart(scope);
-      scope.$broadcast('render', data);
+      scope.$broadcast('render', arg1, arg2);
       this.setTimeRenderEnd(scope);
 
       if ($rootScope.profilingEnabled) {

+ 2 - 41
public/app/panels/table/controller.ts

@@ -3,24 +3,15 @@
 import angular = require('angular');
 import _ = require('lodash');
 import moment = require('moment');
-import kbn = require('app/core/utils/kbn');
 import PanelMeta = require('app/features/panel/panel_meta');
 
 import {TableModel} from './table_model';
-import {transformers} from './transformers';
 
 export class TablePanelCtrl {
 
   constructor($scope, $rootScope, $q, panelSrv, panelHelper) {
     $scope.ctrl = this;
-    $scope.transformers = transformers;
     $scope.pageIndex = 0;
-    $scope.unitFormats = kbn.getUnitFormats();
-    $scope.colorModes = {
-      'cell': {text: 'Cell'},
-      'value': {text: 'Value'},
-      'row': {text: 'Row'},
-    };
 
     $scope.panelMeta = new PanelMeta({
       panelName: 'Table',
@@ -38,23 +29,18 @@ export class TablePanelCtrl {
       pageSize: 50,
       showHeader: true,
       columns: [],
+      fields: []
     };
 
     $scope.init = function() {
       _.defaults($scope.panel, panelDefaults);
 
       if ($scope.panel.columns.length === 0) {
-        $scope.addColumnStyle();
       }
 
       panelSrv.init($scope);
     };
 
-    $scope.setUnitFormat = function(column, subItem) {
-      column.unit = subItem.value;
-      $scope.render();
-    };
-
     $scope.refreshData = function(datasource) {
       panelHelper.updateTimeRange($scope);
 
@@ -73,32 +59,7 @@ export class TablePanelCtrl {
 
     $scope.render = function() {
       $scope.table = TableModel.transform($scope.dataRaw, $scope.panel);
-      panelHelper.broadcastRender($scope, $scope.table);
-    };
-
-    $scope.getColumnNames = function() {
-      if (!$scope.table) {
-        return [];
-      }
-      return _.map($scope.table.columns, function(col: any) {
-        return col.text;
-      });
-    };
-
-    $scope.addColumnStyle = function() {
-      var columnStyleDefaults = {
-        unit: 'short',
-        decimals: 2,
-        colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
-        pattern: '/.*/',
-        colorMode: 'value',
-      };
-
-      $scope.panel.columns.push(angular.copy(columnStyleDefaults));
-    };
-
-    $scope.removeColumnStyle = function(col) {
-      $scope.panel.columns = _.without($scope.panel.columns, col);
+      panelHelper.broadcastRender($scope, $scope.table, $scope.dataRaw);
     };
 
     $scope.init();

+ 167 - 0
public/app/panels/table/editor.html

@@ -0,0 +1,167 @@
+<div class="editor-row">
+	<div class="section">
+		<h5>Data</h5>
+		<div class="tight-form-container">
+			<div class="tight-form">
+				<ul class="tight-form-list">
+					<li class="tight-form-item" style="width: 140px">
+						To Table Transform
+					</li>
+					<li>
+						<select class="input-large tight-form-input"
+							ng-model="panel.transform"
+							ng-options="k as v.description for (k, v) in transformers"
+							ng-change="render()"></select>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+			<div class="tight-form" ng-if="panel.transform === 'json'">
+				<ul class="tight-form-list">
+					<li class="tight-form-item" style="width: 140px">
+						Fields
+					</li>
+					<li class="tight-form-item" ng-repeat="field in panel.fields">
+						<i class="pointer fa fa-remove" ng-click="removeJsonField(field)"></i>
+						<span>
+							{{field.name}}
+						</span>
+					</li>
+					<li class="dropdown" dropdown-typeahead="jsonFieldsMenu" dropdown-typeahead-on-select="addJsonField($item, $subItem)">
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+		</div>
+	</div>
+
+	<div class="section">
+		<h5>Table Display</h5>
+		<div class="tight-form-container">
+			<div class="tight-form">
+				<ul class="tight-form-list">
+					<li class="tight-form-item">
+						Pagination (Page size)
+					</li>
+					<li>
+						<input type="text" class="input-small tight-form-input" placeholder="50"
+						empty-to-null ng-model="panel.pageSize" ng-change="render()" ng-model-onblur>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+		</div>
+	</div>
+</div>
+
+<div class="editor-row" style="margin-top: 20px">
+	<h5>Column Styles</h5>
+
+	<div class="tight-form-container">
+		<div ng-repeat="column in panel.columns">
+			<div class="tight-form">
+				<ul class="tight-form-list">
+					<li class="tight-form-item">
+						<i class="fa fa-remove pointer" ng-click="removeColumnStyle(column)"></i>
+					</li>
+					<li class="tight-form-item">
+						Name or regex
+					</li>
+					<li>
+						<input type="text" ng-model="column.pattern" bs-typeahead="getColumnNames" ng-blur="render()" data-min-length=0 data-items=100 class="input-medium tight-form-input">
+					</li>
+					<li class="tight-form-item" style="width: 86px">
+						Type
+					</li>
+					<li>
+						<select class="input-small tight-form-input"
+							ng-model="column.type"
+							ng-options="k as v.text for (k, v) in columnTypes"
+							ng-change="render()"
+							style="width: 150px"
+							></select>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+			<div class="tight-form" ng-if="column.type === 'date'">
+				<ul class="tight-form-list">
+					<li class="tight-form-item">
+						<i class="fa fa-remove pointer invisible"></i>
+					</li>
+					<li class="tight-form-item text-right" style="width: 93px">
+						Format
+					</li>
+					<li>
+						<input type="text" class="input-medium tight-form-input" ng-model="column.dateFormat" ng-change="render()" ng-model-onblur>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+			<div class="tight-form" ng-if="column.type === 'number'">
+				<ul class="tight-form-list">
+					<li class="tight-form-item">
+						<i class="fa fa-remove pointer invisible"></i>
+					</li>
+					<li class="tight-form-item text-right" style="width: 93px">
+						Coloring
+					</li>
+					<li>
+						<select class="input-small tight-form-input"
+							ng-model="column.colorMode"
+							ng-options="k as v.text for (k, v) in colorModes"
+							ng-change="render()"
+							style="width: 150px"
+							></select>
+					</li>
+					<li class="tight-form-item">
+						Thresholds<tip>Comma seperated values</tip>
+					</li>
+					<li>
+						<input type="text" class="input-small tight-form-input" style="width: 150px" ng-model="column.thresholds" ng-blur="render()" placeholder="0,50,80"></input>
+					</li>
+					<li class="tight-form-item" style="width: 60px">
+						Colors
+					</li>
+					<li class="tight-form-item">
+						<spectrum-picker ng-model="column.colors[0]" ng-change="render()" ></spectrum-picker>
+						<spectrum-picker ng-model="column.colors[1]" ng-change="render()" ></spectrum-picker>
+						<spectrum-picker ng-model="column.colors[2]" ng-change="render()" ></spectrum-picker>
+					</li>
+					<li class="tight-form-item last">
+						<a class="pointer" ng-click="invertColorOrder()">invert order</a>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+			<div class="tight-form" ng-if="column.type === 'number'">
+				<ul class="tight-form-list">
+					<li class="tight-form-item">
+						<i class="fa fa-remove pointer invisible"></i>
+					</li>
+					<li class="tight-form-item text-right" style="width: 93px">
+						Unit
+					</li>
+					<li class="dropdown" style="width: 150px"
+						ng-model="column.unit"
+						dropdown-typeahead="unitFormats"
+						dropdown-typeahead-on-select="setUnitFormat(column, $subItem)">
+					</li>
+					<li class="tight-form-item" style="width: 86px">
+						Decimals
+					</li>
+					<li style="width: 105px">
+						<input type="number" class="input-mini tight-form-input" ng-model="column.decimals" ng-change="render()" ng-model-onblur>
+					</li>
+				</ul>
+				<div class="clearfix"></div>
+			</div>
+
+		</div>
+	</div>
+
+	<button class="btn btn-inverse" style="margin-top: 20px" ng-click="addColumnStyle()">
+		Add column display rule
+	</button>
+</div>
+

+ 106 - 0
public/app/panels/table/editor.ts

@@ -0,0 +1,106 @@
+
+///<reference path="../../headers/common.d.ts" />
+
+import angular = require('angular');
+import $ = require('jquery');
+import _ = require('lodash');
+import kbn = require('app/core/utils/kbn');
+import moment = require('moment');
+
+import {transformers} from './transformers';
+
+export function tablePanelEditor() {
+  'use strict';
+  return {
+    restrict: 'E',
+    scope: true,
+    templateUrl: 'app/panels/table/editor.html',
+    link: function(scope, elem) {
+      scope.transformers = transformers;
+      scope.unitFormats = kbn.getUnitFormats();
+      scope.colorModes = {
+        'cell': {text: 'Cell'},
+        'value': {text: 'Value'},
+        'row': {text: 'Row'},
+      };
+      scope.columnTypes = {
+        'number': {text: 'Number'},
+        'string': {text: 'String'},
+        'date': {text: 'Date'},
+      };
+
+      scope.updateJsonFieldsMenu = function(data) {
+        scope.jsonFieldsMenu = [];
+        if (!data || data.length === 0) {
+          return;
+        }
+
+        var names =  {};
+        for (var i = 0; i < data.length; i++) {
+          var series = data[i];
+          if (series.type !== 'docs') {
+            continue;
+          }
+
+          for (var y = 0; y < series.datapoints.length; y++) {
+            var doc = series.datapoints[y];
+            for (var propName in doc) {
+              names[propName] = true;
+            }
+          }
+        }
+
+        _.each(names, function(value, key) {
+          scope.jsonFieldsMenu.push({text: key});
+        });
+      };
+
+      scope.updateJsonFieldsMenu(scope.dataRaw);
+
+      scope.$on('render', function(event, table, rawData) {
+        scope.updateJsonFieldsMenu(rawData);
+      });
+
+      scope.addJsonField = function(menuItem) {
+        scope.panel.fields.push({name: menuItem.text});
+      };
+
+      scope.removeJsonField = function(field) {
+        scope.panel.fields = _.without(scope.panel.fields, field);
+      };
+
+      scope.setUnitFormat = function(column, subItem) {
+        column.unit = subItem.value;
+        scope.render();
+      };
+
+      scope.addColumnStyle = function() {
+        var columnStyleDefaults = {
+          unit: 'short',
+          type: 'number',
+          decimals: 2,
+          colors: ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
+          colorMode: 'value',
+          pattern: '/.*/',
+        };
+
+        scope.panel.columns.push(angular.copy(columnStyleDefaults));
+      };
+
+      scope.removeColumnStyle = function(col) {
+        scope.panel.columns = _.without(scope.panel.columns, col);
+      };
+
+      scope.getColumnNames = function() {
+        if (!scope.table) {
+          return [];
+        }
+        return _.map(scope.table.columns, function(col: any) {
+          return col.text;
+        });
+      };
+
+    }
+  };
+}
+

+ 10 - 3
public/app/panels/table/module.ts

@@ -4,10 +4,12 @@ import angular = require('angular');
 import $ = require('jquery');
 import _ = require('lodash');
 import kbn = require('app/core/utils/kbn');
+import moment = require('moment');
 
 import {TablePanelCtrl} from './controller';
+import {tablePanelEditor} from './editor';
 
-export function tablePanelDirective() {
+export function tablePanel() {
   'use strict';
   return {
     restrict: 'E',
@@ -45,9 +47,13 @@ export function tablePanelDirective() {
           if (v === null || v === void 0) {
             return '-';
           }
-          if (_.isString(v) || style) {
+          if (_.isString(v) || !style) {
             return v;
           }
+          if (style.type === 'date') {
+            var date = moment(v);
+            return date.format(style.dateFormat);
+          }
           let valueFormater = kbn.valueFormats[style.unit];
           return valueFormater(v, style.decimals);
         };
@@ -136,4 +142,5 @@ export function tablePanelDirective() {
   };
 }
 
-angular.module('grafana.directives').directive('grafanaPanelTable', tablePanelDirective);
+angular.module('grafana.directives').directive('grafanaPanelTable', tablePanel);
+angular.module('grafana.directives').directive('grafanaPanelTableEditor', tablePanelEditor);

+ 2 - 117
public/app/panels/table/options.html

@@ -1,117 +1,2 @@
-<div class="editor-row">
-	<div class="section">
-		<h5>Data</h5>
-		<div class="tight-form-container">
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item" style="width: 170px">
-						To Table Transform
-					</li>
-					<li>
-						<select class="input-xlarge tight-form-input"
-							ng-model="panel.transform"
-							ng-options="k as v.description for (k, v) in transformers"
-							ng-change="render()"></select>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-		</div>
-	</div>
-
-	<div class="section">
-		<h5>Table Display</h5>
-		<div class="tight-form-container">
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item">
-						Pagination (Page size)
-					</li>
-					<li>
-						<input type="text" class="input-small tight-form-input" placeholder="50"
-						empty-to-null ng-model="panel.pageSize" ng-change="render()" ng-model-onblur>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-		</div>
-	</div>
-</div>
-
-<div class="editor-row" style="margin-top: 20px">
-	<h5>Column Styles</h5>
-
-	<div class="tight-form-container">
-		<div ng-repeat="column in panel.columns">
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item">
-						<i class="fa fa-remove pointer" ng-click="removeColumnStyle(column)"></i>
-					</li>
-					<li class="tight-form-item">
-						Name or regex
-					</li>
-					<li>
-						<input type="text" ng-model="column.pattern" bs-typeahead="getColumnNames" ng-blur="render()" data-min-length=0 data-items=100 class="input-medium tight-form-input">
-					</li>
-					<li class="tight-form-item" style="width: 86px">
-						Unit
-					</li>
-					<li class="dropdown" style="width: 150px"
-						ng-model="column.unit"
-						dropdown-typeahead="unitFormats"
-						dropdown-typeahead-on-select="setUnitFormat(column, $subItem)">
-					</li>
-					<li class="tight-form-item" style="width: 60px">
-						Decimals
-					</li>
-					<li style="width: 105px">
-						<input type="number" class="input-mini tight-form-input" ng-model="column.decimals" ng-change="render()" ng-model-onblur>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-			<div class="tight-form">
-				<ul class="tight-form-list">
-					<li class="tight-form-item">
-						<i class="fa fa-remove pointer invisible"></i>
-					</li>
-					<li class="tight-form-item text-right" style="width: 93px">
-						Coloring
-					</li>
-					<li>
-						<select class="input-small tight-form-input"
-							ng-model="column.colorMode"
-							ng-options="k as v.text for (k, v) in colorModes"
-							ng-change="render()"
-							style="width: 150px"
-							></select>
-					</li>
-					<li class="tight-form-item">
-						Thresholds<tip>Comma seperated values</tip>
-					</li>
-					<li>
-						<input type="text" class="input-small tight-form-input" style="width: 150px" ng-model="column.thresholds" ng-blur="render()" placeholder="0,50,80"></input>
-					</li>
-					<li class="tight-form-item" style="width: 60px">
-						Colors
-					</li>
-					<li class="tight-form-item">
-						<spectrum-picker ng-model="column.colors[0]" ng-change="render()" ></spectrum-picker>
-						<spectrum-picker ng-model="column.colors[1]" ng-change="render()" ></spectrum-picker>
-						<spectrum-picker ng-model="column.colors[2]" ng-change="render()" ></spectrum-picker>
-					</li>
-					<li class="tight-form-item last">
-						<a class="pointer" ng-click="invertColorOrder()">invert order</a>
-					</li>
-				</ul>
-				<div class="clearfix"></div>
-			</div>
-		</div>
-	</div>
-
-	<button class="btn btn-inverse" style="margin-top: 20px" ng-click="addColumnStyle()">
-		Add column display rule
-	</button>
-</div>
-
+<grafana-panel-table-editor>
+</grafana-panel-table-editor>

+ 35 - 0
public/app/panels/table/specs/transformers_specs.ts

@@ -67,6 +67,41 @@ describe('when transforming time series table', () => {
         expect(table.rows[1][2]).to.be(undefined);
       });
     });
+
+    describe('JSON Data', () => {
+      var panel = {
+        transform: 'json',
+        fields: [{name: 'timestamp'}, {name: 'message'}]
+      };
+      var rawData = [
+        {
+          type: 'docs',
+          datapoints: [
+            {
+              timestamp: 'time',
+              message: 'message'
+            }
+          ]
+        }
+      ];
+
+      beforeEach(() => {
+        table = TableModel.transform(rawData, panel);
+      });
+
+      it ('should return 2 columns', () => {
+        expect(table.columns.length).to.be(2);
+        expect(table.columns[0].text).to.be('timestamp');
+        expect(table.columns[1].text).to.be('message');
+      });
+
+      it ('should return 2 rows', () => {
+        expect(table.rows.length).to.be(2);
+        expect(table.rows[0][0]).to.be('time');
+        expect(table.rows[0][1]).to.be('message');
+      });
+
+    });
   });
 });
 

+ 24 - 12
public/app/panels/table/transformers.ts

@@ -9,7 +9,7 @@ transformers['timeseries_to_rows'] = {
   description: 'Time series to rows',
   transform: function(data, panel, model) {
     model.columns = [
-      {text: 'Time'},
+      {text: 'Time', type: 'date'},
       {text: 'Series'},
       {text: 'Value'},
     ];
@@ -18,9 +18,7 @@ transformers['timeseries_to_rows'] = {
       var series = data[i];
       for (var y = 0; y < series.datapoints.length; y++) {
         var dp = series.datapoints[y];
-        var time = moment(dp[1]).format('LLL');
-        var value = dp[0];
-        model.rows.push([time, series.target, value]);
+        model.rows.push([dp[1], series.target, dp[0]]);
       }
     }
   },
@@ -29,7 +27,7 @@ transformers['timeseries_to_rows'] = {
 transformers['timeseries_to_columns'] = {
   description: 'Time series to columns',
   transform: function(data, panel, model) {
-    model.columns.push({text: 'Time'});
+    model.columns.push({text: 'Time', type: 'date'});
 
     // group by time
     var points = {};
@@ -54,7 +52,7 @@ transformers['timeseries_to_columns'] = {
 
     for (var time in points) {
       var point = points[time];
-      var values = [moment(point.time).format('LLL')];
+      var values = [point.time];
 
       for (var i = 0; i < data.length; i++) {
         var value = point[i];
@@ -71,17 +69,31 @@ transformers['annotations'] = {
 };
 
 transformers['json'] = {
-  description: 'JSON',
+  description: 'JSON Data',
   transform: function(data, panel, model) {
-    model.columns.push({text: 'JSON'});
-    debugger;
+    var i, y, z;
+    for (i = 0; i < panel.fields.length; i++) {
+      model.columns.push({text: panel.fields[i].name});
+    }
 
-    for (var i = 0; i < data.length; i++) {
+    if (model.columns.length === 0) {
+      model.columns.push({text: 'JSON'});
+    }
+
+    for (i = 0; i < data.length; i++) {
       var series = data[i];
 
-      for (var y = 0; y < series.datapoints.length; y++) {
+      for (y = 0; y < series.datapoints.length; y++) {
         var dp = series.datapoints[y];
-        model.rows.push([JSON.stringify(dp)]);
+        var values = [];
+        for (z = 0; z < panel.fields.length; z++) {
+          values.push(dp[panel.fields[z].name]);
+        }
+
+        if (values.length === 0) {
+          values.push([JSON.stringify(dp)]);
+        }
+        model.rows.push(values);
       }
     }
   }