浏览代码

Merge pull request #11094 from infernix:value-to-text-upstream

Marcus Efraimsson 7 年之前
父节点
当前提交
95a4d61d10

+ 53 - 1
public/app/plugins/panel/table/column_options.html

@@ -69,7 +69,59 @@
       </div>
     </div>
 
-    <div class="section gf-form-group" ng-if="style.type === 'number'">
+    <div class="section gf-form-group" ng-if="style.type === 'string'">
+      <h5 class="section-heading">Value Mappings</h5>
+      <div class="editor-row">
+        <div class="gf-form-group">
+          <div class="gf-form">
+            <span class="gf-form-label">
+              Type
+            </span>
+            <div class="gf-form-select-wrapper">
+              <select class="gf-form-input" ng-model="style.mappingType"
+                      ng-options="c.value as c.text for c in editor.mappingTypes" ng-change="editor.render()"></select>
+            </div>
+          </div>
+          <div class="gf-form-group" ng-if="style.mappingType==1">
+            <div class="gf-form" ng-repeat="map in style.valueMaps">
+              <span class="gf-form-label">
+                <i class="fa fa-remove pointer" ng-click="editor.removeValueMap(style, $index)"></i>
+              </span>
+              <input type="text" class="gf-form-input max-width-6" ng-model="map.value" placeholder="Value" ng-blur="editor.render()">
+              <label class="gf-form-label">
+                <i class="fa fa-arrow-right"></i>
+              </label>
+              <input type="text" class="gf-form-input max-width-8" ng-model="map.text" placeholder="Text" ng-blur="editor.render()">
+            </div>
+            <div class="gf-form">
+              <label class="gf-form-label">
+                <a class="pointer" ng-click="editor.addValueMap(style)"><i class="fa fa-plus"></i></a>
+              </label>
+            </div>
+          </div>
+          <div class="gf-form-group" ng-if="style.mappingType==2">
+            <div class="gf-form" ng-repeat="rangeMap in style.rangeMaps">
+              <span class="gf-form-label">
+                <i class="fa fa-remove pointer" ng-click="editor.removeRangeMap(style, $index)"></i>
+              </span>
+              <span class="gf-form-label">From</span>
+              <input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="editor.render()">
+              <span class="gf-form-label">To</span>
+              <input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="editor.render()">
+              <span class="gf-form-label">Text</span>
+              <input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="editor.render()">
+            </div>
+            <div class="gf-form">
+              <label class="gf-form-label">
+                <a class="pointer" ng-click="editor.addRangeMap(style)"><i class="fa fa-plus"></i></a>
+              </label>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="section gf-form-group" ng-if="['number', 'string'].indexOf(style.type) !== -1">
       <h5 class="section-heading">Thresholds</h5>
       <div class="gf-form">
         <label class="gf-form-label width-8">Thresholds

+ 29 - 0
public/app/plugins/panel/table/column_options.ts

@@ -13,6 +13,7 @@ export class ColumnOptionsCtrl {
   unitFormats: any;
   getColumnNames: any;
   activeStyleIndex: number;
+  mappingTypes: any;
 
   /** @ngInject */
   constructor($scope) {
@@ -41,6 +42,7 @@ export class ColumnOptionsCtrl {
       { text: 'MM/DD/YY h:mm:ss a', value: 'MM/DD/YY h:mm:ss a' },
       { text: 'MMMM D, YYYY LT', value: 'MMMM D, YYYY LT' },
     ];
+    this.mappingTypes = [{ text: 'Value to text', value: 1 }, { text: 'Range to text', value: 2 }];
 
     this.getColumnNames = () => {
       if (!this.panelCtrl.table) {
@@ -74,6 +76,7 @@ export class ColumnOptionsCtrl {
       pattern: '',
       dateFormat: 'YYYY-MM-DD HH:mm:ss',
       thresholds: [],
+      mappingType: 1,
     };
 
     var styles = this.panel.styles;
@@ -110,6 +113,32 @@ export class ColumnOptionsCtrl {
       this.render();
     };
   }
+
+  addValueMap(style) {
+    if (!style.valueMaps) {
+      style.valueMaps = [];
+    }
+    style.valueMaps.push({ value: '', text: '' });
+    this.panelCtrl.render();
+  }
+
+  removeValueMap(style, index) {
+    style.valueMaps.splice(index, 1);
+    this.panelCtrl.render();
+  }
+
+  addRangeMap(style) {
+    if (!style.rangeMaps) {
+      style.rangeMaps = [];
+    }
+    style.rangeMaps.push({ from: '', to: '', text: '' });
+    this.panelCtrl.render();
+  }
+
+  removeRangeMap(style, index) {
+    style.rangeMaps.splice(index, 1);
+    this.panelCtrl.render();
+  }
 }
 
 /** @ngInject */

+ 72 - 5
public/app/plugins/panel/table/renderer.ts

@@ -47,7 +47,6 @@ export class TableRenderer {
     if (!style.thresholds) {
       return null;
     }
-
     for (var i = style.thresholds.length; i > 0; i--) {
       if (value >= style.thresholds[i - 1]) {
         return style.colors[i];
@@ -100,6 +99,60 @@ export class TableRenderer {
       };
     }
 
+    if (column.style.type === 'string') {
+      return v => {
+        if (_.isArray(v)) {
+          v = v.join(', ');
+        }
+
+        const mappingType = column.style.mappingType || 0;
+
+        if (mappingType === 1 && column.style.valueMaps) {
+          for (let i = 0; i < column.style.valueMaps.length; i++) {
+            const map = column.style.valueMaps[i];
+
+            if (v === null) {
+              if (map.value === 'null') {
+                return map.text;
+              }
+              continue;
+            }
+
+            // Allow both numeric and string values to be mapped
+            if ((!_.isString(v) && Number(map.value) === Number(v)) || map.value === v) {
+              this.setColorState(v, column.style);
+              return this.defaultCellFormatter(map.text, column.style);
+            }
+          }
+        }
+
+        if (mappingType === 2 && column.style.rangeMaps) {
+          for (let i = 0; i < column.style.rangeMaps.length; i++) {
+            const map = column.style.rangeMaps[i];
+
+            if (v === null) {
+              if (map.from === 'null' && map.to === 'null') {
+                return map.text;
+              }
+              continue;
+            }
+
+            if (Number(map.from) <= Number(v) && Number(map.to) >= Number(v)) {
+              this.setColorState(v, column.style);
+              return this.defaultCellFormatter(map.text, column.style);
+            }
+          }
+        }
+
+        if (v === null || v === void 0) {
+          return '-';
+        }
+
+        this.setColorState(v, column.style);
+        return this.defaultCellFormatter(v, column.style);
+      };
+    }
+
     if (column.style.type === 'number') {
       let valueFormatter = kbn.valueFormats[column.unit || column.style.unit];
 
@@ -112,10 +165,7 @@ export class TableRenderer {
           return this.defaultCellFormatter(v, column.style);
         }
 
-        if (column.style.colorMode) {
-          this.colorState[column.style.colorMode] = this.getColorForValue(v, column.style);
-        }
-
+        this.setColorState(v, column.style);
         return valueFormatter(v, column.style.decimals, null);
       };
     }
@@ -125,6 +175,23 @@ export class TableRenderer {
     };
   }
 
+  setColorState(value, style) {
+    if (!style.colorMode) {
+      return;
+    }
+
+    if (value === null || value === void 0 || _.isArray(value)) {
+      return;
+    }
+
+    var numericValue = Number(value);
+    if (numericValue === NaN) {
+      return;
+    }
+
+    this.colorState[style.colorMode] = this.getColorForValue(numericValue, style);
+  }
+
   renderRowVariables(rowIndex) {
     let scopedVars = {};
     let cell_variable;

+ 170 - 2
public/app/plugins/panel/table/specs/renderer.jest.ts

@@ -3,7 +3,7 @@ import TableModel from 'app/core/table_model';
 import { TableRenderer } from '../renderer';
 
 describe('when rendering table', () => {
-  describe('given 2 columns', () => {
+  describe('given 13 columns', () => {
     var table = new TableModel();
     table.columns = [
       { text: 'Time' },
@@ -15,8 +15,14 @@ describe('when rendering table', () => {
       { text: 'Sanitized' },
       { text: 'Link' },
       { text: 'Array' },
+      { text: 'Mapping' },
+      { text: 'RangeMapping' },
+      { text: 'MappingColored' },
+      { text: 'RangeMappingColored' },
+    ];
+    table.rows = [
+      [1388556366666, 1230, 40, undefined, '', '', 'my.host.com', 'host1', ['value1', 'value2'], 1, 2, 1, 2],
     ];
-    table.rows = [[1388556366666, 1230, 40, undefined, '', '', 'my.host.com', 'host1', ['value1', 'value2']]];
 
     var panel = {
       pageSize: 10,
@@ -47,6 +53,10 @@ describe('when rendering table', () => {
           pattern: 'String',
           type: 'string',
         },
+        {
+          pattern: 'String',
+          type: 'string',
+        },
         {
           pattern: 'United',
           type: 'number',
@@ -72,6 +82,84 @@ describe('when rendering table', () => {
           unit: 'ms',
           decimals: 3,
         },
+        {
+          pattern: 'Mapping',
+          type: 'string',
+          mappingType: 1,
+          valueMaps: [
+            {
+              value: '1',
+              text: 'on',
+            },
+            {
+              value: '0',
+              text: 'off',
+            },
+            {
+              value: 'HELLO WORLD',
+              text: 'HELLO GRAFANA',
+            },
+            {
+              value: 'value1, value2',
+              text: 'value3, value4',
+            },
+          ],
+        },
+        {
+          pattern: 'RangeMapping',
+          type: 'string',
+          mappingType: 2,
+          rangeMaps: [
+            {
+              from: '1',
+              to: '3',
+              text: 'on',
+            },
+            {
+              from: '3',
+              to: '6',
+              text: 'off',
+            },
+          ],
+        },
+        {
+          pattern: 'MappingColored',
+          type: 'string',
+          mappingType: 1,
+          valueMaps: [
+            {
+              value: '1',
+              text: 'on',
+            },
+            {
+              value: '0',
+              text: 'off',
+            },
+          ],
+          colorMode: 'value',
+          thresholds: [1, 2],
+          colors: ['green', 'orange', 'red'],
+        },
+        {
+          pattern: 'RangeMappingColored',
+          type: 'string',
+          mappingType: 2,
+          rangeMaps: [
+            {
+              from: '1',
+              to: '3',
+              text: 'on',
+            },
+            {
+              from: '3',
+              to: '6',
+              text: 'off',
+            },
+          ],
+          colorMode: 'value',
+          thresholds: [2, 5],
+          colors: ['green', 'orange', 'red'],
+        },
       ],
     };
 
@@ -192,6 +280,86 @@ describe('when rendering table', () => {
       var html = renderer.renderCell(8, 0, ['value1', 'value2']);
       expect(html).toBe('<td>value1, value2</td>');
     });
+
+    it('numeric value should be mapped to text', () => {
+      var html = renderer.renderCell(9, 0, 1);
+      expect(html).toBe('<td>on</td>');
+    });
+
+    it('string numeric value should be mapped to text', () => {
+      var html = renderer.renderCell(9, 0, '0');
+      expect(html).toBe('<td>off</td>');
+    });
+
+    it('string value should be mapped to text', () => {
+      var html = renderer.renderCell(9, 0, 'HELLO WORLD');
+      expect(html).toBe('<td>HELLO GRAFANA</td>');
+    });
+
+    it('array column value should be mapped to text', () => {
+      var html = renderer.renderCell(9, 0, ['value1', 'value2']);
+      expect(html).toBe('<td>value3, value4</td>');
+    });
+
+    it('value should be mapped to text (range)', () => {
+      var html = renderer.renderCell(10, 0, 2);
+      expect(html).toBe('<td>on</td>');
+    });
+
+    it('value should be mapped to text (range)', () => {
+      var html = renderer.renderCell(10, 0, 5);
+      expect(html).toBe('<td>off</td>');
+    });
+
+    it('array column value should not be mapped to text', () => {
+      var html = renderer.renderCell(10, 0, ['value1', 'value2']);
+      expect(html).toBe('<td>value1, value2</td>');
+    });
+
+    it('value should be mapped to text and colored cell should have style', () => {
+      var html = renderer.renderCell(11, 0, 1);
+      expect(html).toBe('<td style="color:orange">on</td>');
+    });
+
+    it('value should be mapped to text and colored cell should have style', () => {
+      var html = renderer.renderCell(11, 0, '1');
+      expect(html).toBe('<td style="color:orange">on</td>');
+    });
+
+    it('value should be mapped to text and colored cell should have style', () => {
+      var html = renderer.renderCell(11, 0, 0);
+      expect(html).toBe('<td style="color:green">off</td>');
+    });
+
+    it('value should be mapped to text and colored cell should have style', () => {
+      var html = renderer.renderCell(11, 0, '0');
+      expect(html).toBe('<td style="color:green">off</td>');
+    });
+
+    it('value should be mapped to text and colored cell should have style', () => {
+      var html = renderer.renderCell(11, 0, '2.1');
+      expect(html).toBe('<td style="color:red">2.1</td>');
+    });
+
+    it('value should be mapped to text (range) and colored cell should have style', () => {
+      var html = renderer.renderCell(12, 0, 0);
+      expect(html).toBe('<td style="color:green">0</td>');
+    });
+
+    it('value should be mapped to text (range) and colored cell should have style', () => {
+      var html = renderer.renderCell(12, 0, 1);
+      expect(html).toBe('<td style="color:green">on</td>');
+    });
+
+    it('value should be mapped to text (range) and colored cell should have style', () => {
+      var html = renderer.renderCell(12, 0, 4);
+      expect(html).toBe('<td style="color:orange">off</td>');
+    });
+
+    it('value should be mapped to text (range) and colored cell should have style', () => {
+      var html = renderer.renderCell(12, 0, '7.1');
+      expect(html).toBe('<td style="color:red">7.1</td>');
+    });
   });
 });