Browse Source

graphite: datasource refactor

Alexander Zobnin 8 years ago
parent
commit
199d0d152e

+ 274 - 0
public/app/plugins/datasource/graphite/graphite_query.ts

@@ -0,0 +1,274 @@
+import _ from 'lodash';
+import gfunc from './gfunc';
+import {Parser} from './parser';
+
+export default class GraphiteQuery {
+  target: any;
+  functions: any[];
+  segments: any[];
+  tags: any[];
+  error: any;
+  seriesByTagUsed: boolean;
+  checkOtherSegmentsIndex: number;
+  removeTagValue: string;
+  templateSrv: any;
+  scopedVars: any;
+
+  /** @ngInject */
+  constructor(target, templateSrv?, scopedVars?) {
+    this.target = target;
+    this.parseTarget();
+
+    this.removeTagValue = '-- remove tag --';
+  }
+
+  parseTarget() {
+    this.functions = [];
+    this.segments = [];
+    this.error = null;
+
+    if (this.target.textEditor) {
+      return;
+    }
+
+    var parser = new Parser(this.target.target);
+    var astNode = parser.getAst();
+    if (astNode === null) {
+      this.checkOtherSegmentsIndex = 0;
+      return;
+    }
+
+    if (astNode.type === 'error') {
+      this.error = astNode.message + " at position: " + astNode.pos;
+      this.target.textEditor = true;
+      return;
+    }
+
+    try {
+      this.parseTargetRecursive(astNode, null, 0);
+    } catch (err) {
+      console.log('error parsing target:', err.message);
+      this.error = err.message;
+      this.target.textEditor = true;
+    }
+
+    this.checkOtherSegmentsIndex = this.segments.length - 1;
+    this.checkForSeriesByTag();
+  }
+
+  checkForSeriesByTag() {
+    let seriesByTagFunc = _.find(this.functions, (func) => func.def.name === 'seriesByTag');
+    if (seriesByTagFunc) {
+      this.seriesByTagUsed = true;
+      seriesByTagFunc.hidden = true;
+      let tags = this.splitSeriesByTagParams(seriesByTagFunc);
+      this.tags = tags;
+    }
+  }
+
+  getSegmentPathUpTo(index) {
+    var arr = this.segments.slice(0, index);
+
+    return _.reduce(arr, function(result, segment) {
+      return result ? (result + "." + segment.value) : segment.value;
+    }, "");
+  }
+
+  parseTargetRecursive(astNode, func, index) {
+    if (astNode === null) {
+      return null;
+    }
+
+    switch (astNode.type) {
+      case 'function':
+        var innerFunc = gfunc.createFuncInstance(astNode.name, { withDefaultParams: false });
+        _.each(astNode.params, (param, index) => {
+          this.parseTargetRecursive(param, innerFunc, index);
+        });
+
+        innerFunc.updateText();
+        this.functions.push(innerFunc);
+        break;
+      case 'series-ref':
+        this.addFunctionParameter(func, astNode.value, index, this.segments.length > 0);
+        break;
+      case 'bool':
+      case 'string':
+      case 'number':
+        if ((index-1) >= func.def.params.length) {
+          throw { message: 'invalid number of parameters to method ' + func.def.name };
+        }
+        var shiftBack = this.isShiftParamsBack(func);
+        this.addFunctionParameter(func, astNode.value, index, shiftBack);
+      break;
+      case 'metric':
+        if (this.segments.length > 0) {
+        if (astNode.segments.length !== 1) {
+          throw { message: 'Multiple metric params not supported, use text editor.' };
+        }
+        this.addFunctionParameter(func, astNode.segments[0].value, index, true);
+        break;
+      }
+
+      this.segments = astNode.segments;
+    }
+  }
+
+  isShiftParamsBack(func) {
+    return func.def.name !== 'seriesByTag';
+  }
+
+  updateSegmentValue(segment, index) {
+    this.segments[index].value = segment.value;
+  }
+
+  addSelectMetricSegment() {
+    this.segments.push({value: "select metric"});
+  }
+
+  addFunction(newFunc) {
+    this.functions.push(newFunc);
+    this.moveAliasFuncLast();
+  }
+
+  moveAliasFuncLast() {
+    var aliasFunc = _.find(this.functions, function(func) {
+      return func.def.name === 'alias' ||
+        func.def.name === 'aliasByNode' ||
+        func.def.name === 'aliasByMetric';
+    });
+
+    if (aliasFunc) {
+      this.functions = _.without(this.functions, aliasFunc);
+      this.functions.push(aliasFunc);
+    }
+  }
+
+  addFunctionParameter(func, value, index, shiftBack) {
+    if (shiftBack) {
+      index = Math.max(index - 1, 0);
+    }
+    func.params[index] = value;
+  }
+
+  removeFunction(func) {
+    this.functions = _.without(this.functions, func);
+  }
+
+  updateModelTarget(targets) {
+    // render query
+    if (!this.target.textEditor) {
+      var metricPath = this.getSegmentPathUpTo(this.segments.length);
+      this.target.target = _.reduce(this.functions, wrapFunction, metricPath);
+    }
+
+    this.updateRenderedTarget(this.target, targets);
+
+    // loop through other queries and update targetFull as needed
+    for (const target of targets || []) {
+      if (target.refId !== this.target.refId) {
+        this.updateRenderedTarget(target, targets);
+      }
+    }
+  }
+
+  updateRenderedTarget(target, targets) {
+    // render nested query
+    var targetsByRefId = _.keyBy(targets, 'refId');
+
+    // no references to self
+    delete targetsByRefId[target.refId];
+
+    var nestedSeriesRefRegex = /\#([A-Z])/g;
+    var targetWithNestedQueries = target.target;
+
+    // Keep interpolating until there are no query references
+    // The reason for the loop is that the referenced query might contain another reference to another query
+    while (targetWithNestedQueries.match(nestedSeriesRefRegex)) {
+      var updated = targetWithNestedQueries.replace(nestedSeriesRefRegex, (match, g1) => {
+        var t = targetsByRefId[g1];
+        if (!t) {
+          return match;
+        }
+
+        // no circular references
+        delete targetsByRefId[g1];
+        return t.target;
+      });
+
+      if (updated === targetWithNestedQueries) {
+        break;
+      }
+
+      targetWithNestedQueries = updated;
+    }
+
+    delete target.targetFull;
+    if (target.target !== targetWithNestedQueries) {
+      target.targetFull = targetWithNestedQueries;
+    }
+  }
+
+  splitSeriesByTagParams(func) {
+    const tagPattern = /([^\!=~]+)([\!=~]+)([^\!=~]+)/;
+    return _.flatten(_.map(func.params, (param: string) => {
+      let matches = tagPattern.exec(param);
+      if (matches) {
+        let tag = matches.slice(1);
+        if (tag.length === 3) {
+          return {
+            key: tag[0],
+            operator: tag[1],
+            value: tag[2]
+          };
+        }
+      }
+      return [];
+    }));
+  }
+
+  getSeriesByTagFuncIndex() {
+    return _.findIndex(this.functions, (func) => func.def.name === 'seriesByTag');
+  }
+
+  getSeriesByTagFunc() {
+    let seriesByTagFuncIndex = this.getSeriesByTagFuncIndex();
+    if (seriesByTagFuncIndex >= 0) {
+      return this.functions[seriesByTagFuncIndex];
+    } else {
+      return undefined;
+    }
+  }
+
+  addTag(tag) {
+    let newTagParam = renderTagString(tag);
+    this.getSeriesByTagFunc().params.push(newTagParam);
+    this.tags.push(tag);
+  }
+
+  removeTag(index) {
+    this.getSeriesByTagFunc().params.splice(index, 1);
+    this.tags.splice(index, 1);
+  }
+
+  updateTag(tag, tagIndex) {
+    this.error = null;
+
+    if (tag.key === this.removeTagValue) {
+      this.removeTag(tagIndex);
+      return;
+    }
+
+    let newTagParam = renderTagString(tag);
+    this.getSeriesByTagFunc().params[tagIndex] = newTagParam;
+    this.tags[tagIndex] = tag;
+  }
+}
+
+function wrapFunction(target, func) {
+  return func.render(target);
+}
+
+function renderTagString(tag) {
+  return tag.key + tag.operator + tag.value;
+}

+ 5 - 5
public/app/plugins/datasource/graphite/partials/query.editor.html

@@ -6,11 +6,11 @@
 
   <div ng-hide="ctrl.target.textEditor">
 		<div class="gf-form-inline">
-      <div class="gf-form" ng-if="ctrl.seriesByTagUsed">
+      <div class="gf-form" ng-if="ctrl.queryModel.seriesByTagUsed">
         <label class="gf-form-label query-keyword">seriesByTag</label>
       </div>
 
-      <div ng-repeat="tag in ctrl.tags" class="gf-form">
+      <div ng-repeat="tag in ctrl.queryModel.tags" class="gf-form">
         <gf-form-dropdown model="tag.key" lookup-text="false" allow-custom="false" label-mode="true" css-class="query-segment-key"
           get-options="ctrl.getTags()"
           on-change="ctrl.tagChanged(tag, $index)">
@@ -25,7 +25,7 @@
         </gf-form-dropdown>
         <label class="gf-form-label query-keyword" ng-if="ctrl.showDelimiter($index)">,</label>
       </div>
-      <div ng-if="ctrl.seriesByTagUsed" ng-repeat="segment in ctrl.addTagSegments" role="menuitem" class="gf-form">
+      <div ng-if="ctrl.queryModel.seriesByTagUsed" ng-repeat="segment in ctrl.addTagSegments" role="menuitem" class="gf-form">
         <metric-segment segment="segment"
           get-options="ctrl.getTagsAsSegments()"
           on-change="ctrl.addNewTag(segment)">
@@ -36,8 +36,8 @@
         <metric-segment segment="segment" get-options="ctrl.getAltSegments($index)" on-change="ctrl.segmentValueChanged(segment, $index)"></metric-segment>
       </div>
 
-      <div ng-repeat="func in ctrl.functions" class="gf-form">
-        <span graphite-func-editor class="gf-form-label query-part" ng-hide="ctrl.getSeriesByTagFuncIndex() === $index"></span>
+      <div ng-repeat="func in ctrl.queryModel.functions" class="gf-form">
+        <span graphite-func-editor class="gf-form-label query-part" ng-hide="func.hidden"></span>
       </div>
 
       <div class="gf-form dropdown">

+ 54 - 246
public/app/plugins/datasource/graphite/query_ctrl.ts

@@ -3,7 +3,7 @@ import './func_editor';
 
 import _ from 'lodash';
 import gfunc from './gfunc';
-import {Parser} from './parser';
+import GraphiteQuery from './graphite_query';
 import {QueryCtrl} from 'app/plugins/sdk';
 import appEvents from 'app/core/app_events';
 
@@ -12,11 +12,9 @@ const GRAPHITE_TAG_OPERATORS = ['=', '!=', '=~', '!=~'];
 export class GraphiteQueryCtrl extends QueryCtrl {
   static templateUrl = 'partials/query.editor.html';
 
-  functions: any[];
+  queryModel: GraphiteQuery;
   segments: any[];
   addTagSegments: any[];
-  tags: any[];
-  seriesByTagUsed: boolean;
   removeTagValue: string;
 
   /** @ngInject **/
@@ -25,119 +23,47 @@ export class GraphiteQueryCtrl extends QueryCtrl {
 
     if (this.target) {
       this.target.target = this.target.target || '';
-      this.parseTarget();
+      this.queryModel = new GraphiteQuery(this.target, templateSrv);
+      this.buildSegments();
     }
 
     this.removeTagValue = '-- remove tag --';
   }
 
-  toggleEditorMode() {
-    this.target.textEditor = !this.target.textEditor;
-    this.parseTarget();
-  }
-
   parseTarget() {
-    this.functions = [];
-    this.segments = [];
-    this.error = null;
-
-    if (this.target.textEditor) {
-      return;
-    }
-
-    var parser = new Parser(this.target.target);
-    var astNode = parser.getAst();
-    if (astNode === null) {
-      this.checkOtherSegments(0);
-      return;
-    }
-
-    if (astNode.type === 'error') {
-      this.error = astNode.message + " at position: " + astNode.pos;
-      this.target.textEditor = true;
-      return;
-    }
-
-    try {
-      this.parseTargetRecursive(astNode, null, 0);
-    } catch (err) {
-      console.log('error parsing target:', err.message);
-      this.error = err.message;
-      this.target.textEditor = true;
-    }
-
-    this.checkOtherSegments(this.segments.length - 1);
-    this.checkForSeriesByTag();
+    this.queryModel.parseTarget();
+    this.buildSegments();
   }
 
-  addFunctionParameter(func, value, index, shiftBack) {
-    if (shiftBack) {
-      index = Math.max(index - 1, 0);
-    }
-    func.params[index] = value;
+  toggleEditorMode() {
+    this.target.textEditor = !this.target.textEditor;
+    this.parseTarget();
   }
 
-  parseTargetRecursive(astNode, func, index) {
-    if (astNode === null) {
-      return null;
-    }
-
-    switch (astNode.type) {
-      case 'function':
-        var innerFunc = gfunc.createFuncInstance(astNode.name, { withDefaultParams: false });
-        _.each(astNode.params, (param, index) => {
-          this.parseTargetRecursive(param, innerFunc, index);
-        });
-
-        innerFunc.updateText();
-        this.functions.push(innerFunc);
-        break;
-      case 'series-ref':
-        this.addFunctionParameter(func, astNode.value, index, this.segments.length > 0);
-        break;
-      case 'bool':
-      case 'string':
-      case 'number':
-        if ((index-1) >= func.def.params.length) {
-          throw { message: 'invalid number of parameters to method ' + func.def.name };
-        }
-        var shiftBack = this.isShiftParamsBack(func);
-        this.addFunctionParameter(func, astNode.value, index, shiftBack);
-      break;
-      case 'metric':
-        if (this.segments.length > 0) {
-        if (astNode.segments.length !== 1) {
-          throw { message: 'Multiple metric params not supported, use text editor.' };
-        }
-        this.addFunctionParameter(func, astNode.segments[0].value, index, true);
-        break;
-      }
+  buildSegments() {
+    this.segments = _.map(this.queryModel.segments, segment => {
+      return this.uiSegmentSrv.newSegment(segment);
+    });
+    let checkOtherSegmentsIndex = this.queryModel.checkOtherSegmentsIndex || 0;
+    this.checkOtherSegments(checkOtherSegmentsIndex);
 
-      this.segments = _.map(astNode.segments, segment => {
-        return this.uiSegmentSrv.newSegment(segment);
-      });
+    if (this.queryModel.seriesByTagUsed) {
+      this.fixTagSegments();
     }
   }
 
-  isShiftParamsBack(func) {
-    return func.def.name !== 'seriesByTag';
-  }
-
-  getSegmentPathUpTo(index) {
-    var arr = this.segments.slice(0, index);
-
-    return _.reduce(arr, function(result, segment) {
-      return result ? (result + "." + segment.value) : segment.value;
-    }, "");
+  addSelectMetricSegment() {
+    this.queryModel.addSelectMetricSegment();
+    this.segments.push(this.uiSegmentSrv.newSelectMetric());
   }
 
   checkOtherSegments(fromIndex) {
     if (fromIndex === 0) {
-      this.segments.push(this.uiSegmentSrv.newSelectMetric());
+      this.addSelectMetricSegment();
       return;
     }
 
-    var path = this.getSegmentPathUpTo(fromIndex + 1);
+    var path = this.queryModel.getSegmentPathUpTo(fromIndex + 1);
     if (path === "") {
       return Promise.resolve();
     }
@@ -145,12 +71,13 @@ export class GraphiteQueryCtrl extends QueryCtrl {
     return this.datasource.metricFindQuery(path).then(segments => {
       if (segments.length === 0) {
         if (path !== '') {
+          this.queryModel.segments = this.queryModel.segments.splice(0, fromIndex);
           this.segments = this.segments.splice(0, fromIndex);
-          this.segments.push(this.uiSegmentSrv.newSelectMetric());
+          this.addSelectMetricSegment();
         }
       } else if (segments[0].expandable) {
         if (this.segments.length === fromIndex) {
-          this.segments.push(this.uiSegmentSrv.newSelectMetric());
+          this.addSelectMetricSegment();
         } else {
           return this.checkOtherSegments(fromIndex + 1);
         }
@@ -166,12 +93,8 @@ export class GraphiteQueryCtrl extends QueryCtrl {
     });
   }
 
-  wrapFunction(target, func) {
-    return func.render(target);
-  }
-
   getAltSegments(index) {
-    var query = index === 0 ?  '*' : this.getSegmentPathUpTo(index) + '.*';
+    var query = index === 0 ?  '*' : this.queryModel.getSegmentPathUpTo(index) + '.*';
     var options = {range: this.panelCtrl.range, requestId: "get-alt-segments"};
 
     return this.datasource.metricFindQuery(query, options).then(segments => {
@@ -200,9 +123,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
 
   segmentValueChanged(segment, segmentIndex) {
     this.error = null;
+    this.queryModel.updateSegmentValue(segment, segmentIndex);
 
-    if (this.functions.length > 0 && this.functions[0].def.fake) {
-      this.functions = [];
+    if (this.queryModel.functions.length > 0 && this.queryModel.functions[0].def.fake) {
+      this.queryModel.functions = [];
     }
 
     if (segment.expandable) {
@@ -211,81 +135,41 @@ export class GraphiteQueryCtrl extends QueryCtrl {
         this.targetChanged();
       });
     } else {
-      this.segments = this.segments.splice(0, segmentIndex + 1);
+      this.spliceSegments(segmentIndex + 1);
     }
 
     this.setSegmentFocus(segmentIndex + 1);
     this.targetChanged();
   }
 
+  spliceSegments(index) {
+    this.segments = this.segments.splice(0, index);
+    this.queryModel.segments = this.queryModel.segments.splice(0, index);
+  }
+
+  emptySegments() {
+    this.queryModel.segments = [];
+    this.segments = [];
+  }
+
   targetTextChanged() {
     this.updateModelTarget();
     this.refresh();
   }
 
   updateModelTarget() {
-    // render query
-    if (!this.target.textEditor) {
-      var metricPath = this.getSegmentPathUpTo(this.segments.length);
-      this.target.target = _.reduce(this.functions, this.wrapFunction, metricPath);
-    }
-
-    this.updateRenderedTarget(this.target);
-
-    // loop through other queries and update targetFull as needed
-    for (const target of this.panelCtrl.panel.targets || []) {
-      if (target.refId !== this.target.refId) {
-        this.updateRenderedTarget(target);
-      }
-    }
-  }
-
-  updateRenderedTarget(target) {
-    // render nested query
-    var targetsByRefId = _.keyBy(this.panelCtrl.panel.targets, 'refId');
-
-    // no references to self
-    delete targetsByRefId[target.refId];
-
-    var nestedSeriesRefRegex = /\#([A-Z])/g;
-    var targetWithNestedQueries = target.target;
-
-    // Keep interpolating until there are no query references
-    // The reason for the loop is that the referenced query might contain another reference to another query
-    while (targetWithNestedQueries.match(nestedSeriesRefRegex)) {
-      var updated = targetWithNestedQueries.replace(nestedSeriesRefRegex, (match, g1) => {
-        var t = targetsByRefId[g1];
-        if (!t) {
-          return match;
-        }
-
-        // no circular references
-        delete targetsByRefId[g1];
-        return t.target;
-      });
-
-      if (updated === targetWithNestedQueries) {
-        break;
-      }
-
-      targetWithNestedQueries = updated;
-    }
-
-    delete target.targetFull;
-    if (target.target !== targetWithNestedQueries) {
-      target.targetFull = targetWithNestedQueries;
-    }
+    this.queryModel.updateModelTarget(this.panelCtrl.panel.targets);
   }
 
   targetChanged() {
-    if (this.error) {
+    if (this.queryModel.error) {
       return;
     }
 
-    var oldTarget = this.target.target;
+    var oldTarget = this.queryModel.target.target;
     this.updateModelTarget();
 
-    if (this.target.target !== oldTarget) {
+    if (this.queryModel.target !== oldTarget) {
       var lastSegment = this.segments.length > 0 ? this.segments[this.segments.length - 1] : {};
       if (lastSegment.value !== 'select metric') {
         this.panelCtrl.refresh();
@@ -293,21 +177,14 @@ export class GraphiteQueryCtrl extends QueryCtrl {
     }
   }
 
-  removeFunction(func) {
-    this.functions = _.without(this.functions, func);
-    this.targetChanged();
-  }
-
   addFunction(funcDef) {
     var newFunc = gfunc.createFuncInstance(funcDef, { withDefaultParams: true });
     newFunc.added = true;
-    this.functions.push(newFunc);
-
-    this.moveAliasFuncLast();
+    this.queryModel.addFunction(newFunc);
     this.smartlyHandleNewAliasByNode(newFunc);
 
     if (this.segments.length === 1 && this.segments[0].fake) {
-      this.segments = [];
+      this.emptySegments();
     }
 
     if (!newFunc.params.length && newFunc.added) {
@@ -319,17 +196,9 @@ export class GraphiteQueryCtrl extends QueryCtrl {
     }
   }
 
-  moveAliasFuncLast() {
-    var aliasFunc = _.find(this.functions, function(func) {
-      return func.def.name === 'alias' ||
-        func.def.name === 'aliasByNode' ||
-        func.def.name === 'aliasByMetric';
-    });
-
-    if (aliasFunc) {
-      this.functions = _.without(this.functions, aliasFunc);
-      this.functions.push(aliasFunc);
-    }
+  removeFunction(func) {
+    this.queryModel.removeFunction(func);
+    this.targetChanged();
   }
 
   smartlyHandleNewAliasByNode(func) {
@@ -338,7 +207,7 @@ export class GraphiteQueryCtrl extends QueryCtrl {
     }
 
     for (var i = 0; i < this.segments.length; i++) {
-      if (this.segments[i].value.indexOf('*') >= 0)  {
+      if (this.segments[i].value.indexOf('*') >= 0) {
         func.params[0] = i;
         func.added = false;
         this.targetChanged();
@@ -347,38 +216,6 @@ export class GraphiteQueryCtrl extends QueryCtrl {
     }
   }
 
-  //////////////////////////////////
-  // Graphite seriesByTag support //
-  //////////////////////////////////
-
-  checkForSeriesByTag() {
-    let seriesByTagFunc = _.find(this.functions, (func) => func.def.name === 'seriesByTag');
-    if (seriesByTagFunc) {
-      this.seriesByTagUsed = true;
-      let tags = this.splitSeriesByTagParams(seriesByTagFunc);
-      this.tags = tags;
-      this.fixTagSegments();
-    }
-  }
-
-  splitSeriesByTagParams(func) {
-    const tagPattern = /([^\!=~]+)([\!=~]+)([^\!=~]+)/;
-    return _.flatten(_.map(func.params, (param: string) => {
-      let matches = tagPattern.exec(param);
-      if (matches) {
-        let tag = matches.slice(1);
-        if (tag.length === 3) {
-          return {
-            key: tag[0],
-            operator: tag[1],
-            value: tag[2]
-          }
-        }
-      }
-      return [];
-    }));
-  }
-
   getTags() {
     return this.datasource.getTags().then((values) => {
       let altTags = _.map(values, 'text');
@@ -408,45 +245,20 @@ export class GraphiteQueryCtrl extends QueryCtrl {
   }
 
   tagChanged(tag, tagIndex) {
-    this.error = null;
-
-    if (tag.key === this.removeTagValue) {
-      this.removeTag(tagIndex);
-      return;
-    }
-
-    let newTagParam = renderTagString(tag);
-    this.getSeriesByTagFunc().params[tagIndex] = newTagParam;
-    this.tags[tagIndex] = tag;
+    this.queryModel.updateTag(tag, tagIndex);
     this.targetChanged();
   }
 
-  getSeriesByTagFuncIndex() {
-    return _.findIndex(this.functions, (func) => func.def.name === 'seriesByTag');
-  }
-
-  getSeriesByTagFunc() {
-    let seriesByTagFuncIndex = this.getSeriesByTagFuncIndex();
-    if (seriesByTagFuncIndex >= 0) {
-      return this.functions[seriesByTagFuncIndex];
-    } else {
-      return undefined;
-    }
-  }
-
   addNewTag(segment) {
     let newTagKey = segment.value;
     let newTag = {key: newTagKey, operator: '=', value: 'select tag value'};
-    let newTagParam = renderTagString(newTag);
-    this.getSeriesByTagFunc().params.push(newTagParam);
-    this.tags.push(newTag);
+    this.queryModel.addTag(newTag);
     this.targetChanged();
     this.fixTagSegments();
   }
 
   removeTag(index) {
-    this.getSeriesByTagFunc().params.splice(index, 1);
-    this.tags.splice(index, 1);
+    this.queryModel.removeTag(index);
     this.targetChanged();
   }
 
@@ -456,14 +268,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
   }
 
   showDelimiter(index) {
-    return index !== this.tags.length - 1;
+    return index !== this.queryModel.tags.length - 1;
   }
 }
 
-function renderTagString(tag) {
-  return tag.key + tag.operator + tag.value;
-}
-
 function mapToDropdownOptions(results) {
   return _.map(results, (value) => {
     return {text: value, value: value};

+ 11 - 11
public/app/plugins/datasource/graphite/specs/query_ctrl_specs.ts

@@ -48,7 +48,7 @@ describe('GraphiteQueryCtrl', function() {
     });
 
     it('should parse expression and build function model', function() {
-      expect(ctx.ctrl.functions.length).to.be(2);
+      expect(ctx.ctrl.queryModel.functions.length).to.be(2);
     });
   });
 
@@ -61,7 +61,7 @@ describe('GraphiteQueryCtrl', function() {
     });
 
     it('should add function with correct node number', function() {
-      expect(ctx.ctrl.functions[0].params[0]).to.be(2);
+      expect(ctx.ctrl.queryModel.functions[0].params[0]).to.be(2);
     });
 
     it('should update target', function() {
@@ -99,7 +99,7 @@ describe('GraphiteQueryCtrl', function() {
     });
 
     it('should add both series refs as params', function() {
-      expect(ctx.ctrl.functions[0].params.length).to.be(2);
+      expect(ctx.ctrl.queryModel.functions[0].params.length).to.be(2);
     });
   });
 
@@ -115,7 +115,7 @@ describe('GraphiteQueryCtrl', function() {
     });
 
     it('should add function param', function() {
-      expect(ctx.ctrl.functions[0].params.length).to.be(1);
+      expect(ctx.ctrl.queryModel.functions[0].params.length).to.be(1);
     });
   });
 
@@ -131,7 +131,7 @@ describe('GraphiteQueryCtrl', function() {
     });
 
     it('should have correct func params', function() {
-      expect(ctx.ctrl.functions[0].params.length).to.be(1);
+      expect(ctx.ctrl.queryModel.functions[0].params.length).to.be(1);
     });
   });
 
@@ -219,11 +219,11 @@ describe('GraphiteQueryCtrl', function() {
     });
 
     it('should update functions', function() {
-      expect(ctx.ctrl.getSeriesByTagFuncIndex()).to.be(0);
+      expect(ctx.ctrl.queryModel.getSeriesByTagFuncIndex()).to.be(0);
     });
 
     it('should update seriesByTagUsed flag', function() {
-      expect(ctx.ctrl.seriesByTagUsed).to.be(true);
+      expect(ctx.ctrl.queryModel.seriesByTagUsed).to.be(true);
     });
 
     it('should update target', function() {
@@ -247,7 +247,7 @@ describe('GraphiteQueryCtrl', function() {
         {key: 'tag1', operator: '=', value: 'value1'},
         {key: 'tag2', operator: '!=~', value: 'value2'}
       ];
-      expect(ctx.ctrl.tags).to.eql(expected);
+      expect(ctx.ctrl.queryModel.tags).to.eql(expected);
     });
 
     it('should add plus button', function() {
@@ -267,7 +267,7 @@ describe('GraphiteQueryCtrl', function() {
       const expected = [
         {key: 'tag1', operator: '=', value: 'select tag value'}
       ];
-      expect(ctx.ctrl.tags).to.eql(expected);
+      expect(ctx.ctrl.queryModel.tags).to.eql(expected);
     });
 
     it('should update target', function() {
@@ -289,7 +289,7 @@ describe('GraphiteQueryCtrl', function() {
         {key: 'tag1', operator: '=', value: 'new_value'},
         {key: 'tag2', operator: '!=~', value: 'value2'}
       ];
-      expect(ctx.ctrl.tags).to.eql(expected);
+      expect(ctx.ctrl.queryModel.tags).to.eql(expected);
     });
 
     it('should update target', function() {
@@ -310,7 +310,7 @@ describe('GraphiteQueryCtrl', function() {
       const expected = [
         {key: 'tag2', operator: '!=~', value: 'value2'}
       ];
-      expect(ctx.ctrl.tags).to.eql(expected);
+      expect(ctx.ctrl.queryModel.tags).to.eql(expected);
     });
 
     it('should update target', function() {