Просмотр исходного кода

Small progress on influxdb 0.9 query editor, #1525

Torkel Ödegaard 10 лет назад
Родитель
Сommit
da833cbc58

+ 3 - 1
src/app/directives/graphiteSegment.js

@@ -37,12 +37,14 @@ function (angular, app, _, $) {
               if (selected) {
                 segment.value = selected.value;
                 segment.html = selected.html;
+                segment.fake = false;
                 segment.expandable = selected.expandable;
               }
               else {
                 segment.value = value;
                 segment.html = $sce.trustAsHtml(value);
                 segment.expandable = true;
+                segment.fake = false;
               }
               $scope.segmentValueChanged(segment, $scope.$index);
             });
@@ -71,7 +73,7 @@ function (angular, app, _, $) {
                 options = _.map($scope.altSegments, function(alt) { return alt.value; });
 
                 // add custom values
-                if (segment.value !== 'select metric' &&  _.indexOf(options, segment.value) === -1) {
+                if (!segment.fake && _.indexOf(options, segment.value) === -1) {
                   options.unshift(segment.value);
                 }
 

+ 9 - 10
src/app/plugins/datasource/graphite/queryCtrl.js

@@ -113,7 +113,7 @@ function (angular, _, config, gfunc, Parser) {
 
     function checkOtherSegments(fromIndex) {
       if (fromIndex === 0) {
-        $scope.segments.push(new MetricSegment('select metric'));
+        $scope.segments.push(MetricSegment.newSelectMetric());
         return;
       }
 
@@ -123,13 +123,13 @@ function (angular, _, config, gfunc, Parser) {
           if (segments.length === 0) {
             if (path !== '') {
               $scope.segments = $scope.segments.splice(0, fromIndex);
-              $scope.segments.push(new MetricSegment('select metric'));
+              $scope.segments.push(MetricSegment.newSelectMetric());
             }
             return;
           }
           if (segments[0].expandable) {
             if ($scope.segments.length === fromIndex) {
-              $scope.segments.push(new MetricSegment('select metric'));
+              $scope.segments.push(MetricSegment.newSelectMetric());
             }
             else {
               return checkOtherSegments(fromIndex + 1);
@@ -238,7 +238,7 @@ function (angular, _, config, gfunc, Parser) {
       $scope.moveAliasFuncLast();
       $scope.smartlyHandleNewAliasByNode(newFunc);
 
-      if ($scope.segments.length === 1 && $scope.segments[0].value === 'select metric') {
+      if ($scope.segments.length === 1 && $scope.segments[0].fake) {
         $scope.segments = [];
       }
 
@@ -298,18 +298,17 @@ function (angular, _, config, gfunc, Parser) {
         return;
       }
 
-      if (_.isString(options)) {
-        this.value = options;
-        this.html = $sce.trustAsHtml(this.value);
-        return;
-      }
-
+      this.fake = options.fake;
       this.value = options.value;
       this.type = options.type;
       this.expandable = options.expandable;
       this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
     }
 
+    MetricSegment.newSelectMetric = function() {
+      return new MetricSegment({value: 'select metric', fake: true});
+    };
+
   });
 
   module.directive('focusMe', function($timeout, $parse) {

+ 26 - 48
src/app/plugins/datasource/influxdb/datasource.js

@@ -36,7 +36,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
       var timeFilter = getTimeFilter(options);
 
       var promises = _.map(options.targets, function(target) {
-        if (target.hide || !((target.series && target.column) || target.query)) {
+        if (target.hide || !target.query) {
           return [];
         }
 
@@ -73,40 +73,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
       });
     };
 
-    InfluxDatasource.prototype.listColumns = function(seriesName) {
-      seriesName = templateSrv.replace(seriesName);
-
-      if(!seriesName.match('^/.*/') && !seriesName.match(/^merge\(.*\)/)) {
-        seriesName = '"' + seriesName+ '"';
-      }
-
-      return this._seriesQuery('select * from ' + seriesName + ' limit 1').then(function(data) {
-        if (!data) {
-          return [];
-        }
-        return data[0].columns.map(function(item) {
-          return /^\w+$/.test(item) ? item : ('"' + item + '"');
-        });
-      });
-    };
-
-    InfluxDatasource.prototype.listSeries = function(query) {
-      // wrap in regex
-      if (query && query.length > 0 && query[0] !== '/')  {
-        query = '/' + query + '/';
-      }
-
-      return this._seriesQuery('SHOW MEASUREMENTS').then(function(data) {
-        if (!data || data.length === 0) {
-          return [];
-        }
-        return _.map(data[0].points, function(point) {
-          return point[1];
-        });
-      });
-    };
-
-    InfluxDatasource.prototype.metricFindQuery = function (query) {
+    InfluxDatasource.prototype.metricFindQuery = function (query, queryType) {
       var interpolated;
       try {
         interpolated = templateSrv.replace(query);
@@ -115,17 +82,30 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
         return $q.reject(err);
       }
 
-      return this._seriesQuery(interpolated)
-        .then(function (results) {
-          if (!results || results.length === 0) { return []; }
+      console.log('metricFindQuery called with: ' + [query, queryType].join(', '));
 
-          return _.map(results[0].points, function (metric) {
-            return {
-              text: metric[1],
-              expandable: false
-            };
-          });
-        });
+      return this._seriesQuery(interpolated, queryType).then(function (results) {
+        if (!results || results.results.length === 0) { return []; }
+
+        var influxResults = results.results[0];
+        if (!influxResults.series) {
+          return [];
+        }
+
+        console.log('metric find query response', results);
+        var series = influxResults.series[0];
+
+        switch (queryType) {
+        case 'MEASUREMENTS':
+          return _.map(series.values, function(value) { return { text: value[0], expandable: true }; });
+        case 'TAG_KEYS':
+          var tagKeys = _.flatten(series.values);
+          return _.map(tagKeys, function(tagKey) { return { text: tagKey, expandable: true }; });
+        case 'TAG_VALUES':
+          var tagValues = _.flatten(series.values);
+          return _.map(tagValues, function(tagValue) { return { text: tagValue, expandable: true }; });
+        }
+      });
     };
 
     function retry(deferred, callback, delay) {
@@ -143,9 +123,7 @@ function (angular, _, kbn, InfluxSeries, InfluxQueryBuilder) {
     }
 
     InfluxDatasource.prototype._seriesQuery = function(query) {
-      return this._influxRequest('GET', '/query', {
-        q: query,
-      });
+      return this._influxRequest('GET', '/query', {q: query});
     };
 
     InfluxDatasource.prototype._influxRequest = function(method, url, data) {

+ 62 - 224
src/app/plugins/datasource/influxdb/partials/query.editor.html

@@ -1,18 +1,47 @@
 <div class="editor-row">
-	<div ng-repeat="target in panel.targets" ng-controller="InfluxQueryCtrl" ng-init="init()" ng-class="{'tight-form-disabled': target.hide}" class="tight-form-container">
-		<div class="tight-form">
-			<ul class="tight-form-list pull-right">
-				<li class="tight-form-item">
+
+	<div  ng-repeat="target in panel.targets"
+        class="tight-form"
+        ng-class="{'tight-form-disabled': target.hide}"
+        ng-controller="InfluxQueryCtrl"
+        ng-init="init()">
+      <ul class="tight-form-list pull-right">
+        <li ng-show="parserError" class="tight-form-item">
+          <a bs-tooltip="parserError" style="color: rgb(229, 189, 28)" role="menuitem">
+            <i class="fa fa-warning"></i>
+          </a>
+        </li>
+        <li class="tight-form-item">
+          <a class="pointer" tabindex="1" ng-click="toggleQueryMode()">
+            <i class="fa fa-pencil"></i>
+          </a>
+        </li>
+        <li class="tight-form-item">
 					<div class="dropdown">
-						<a class="pointer dropdown-toggle"
+						<a  class="pointer dropdown-toggle"
 							data-toggle="dropdown"
 							tabindex="1">
 							<i class="fa fa-bars"></i>
 						</a>
 						<ul class="dropdown-menu pull-right" role="menu">
-							<li role="menuitem"><a tabindex="1" ng-click="duplicate()">Duplicate</a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index-1)">Move up </a></li>
-							<li role="menuitem"><a tabindex="1" ng-click="moveMetricQuery($index, $index+1)">Move down</a></li>
+							<li role="menuitem">
+								<a  tabindex="1"
+									ng-click="duplicate()">
+									Duplicate
+								</a>
+							</li>
+							<li role="menuitem">
+								<a  tabindex="1"
+									ng-click="moveMetricQuery($index, $index-1)">
+									Move up
+								</a>
+							</li>
+							<li role="menuitem">
+								<a  tabindex="1"
+									ng-click="moveMetricQuery($index, $index+1)">
+									Move down
+								</a>
+							</li>
 						</ul>
 					</div>
 				</li>
@@ -24,231 +53,40 @@
 			</ul>
 
 			<ul class="tight-form-list">
+				<li class="tight-form-item" style="min-width: 15px; text-align: center">
+					{{targetLetters[$index]}}
+				</li>
 				<li>
-					<a class="tight-form-item" ng-click="target.hide = !target.hide; get_data();" role="menuitem">
+					<a  class="tight-form-item"
+						ng-click="target.hide = !target.hide; get_data();"
+						role="menuitem">
 						<i class="fa fa-eye"></i>
 					</a>
 				</li>
 			</ul>
 
-			<!-- Raw Query mode  -->
-			<ul class="tight-form-list" ng-show="target.rawQuery">
-				<li>
-					<input type="text"
-					class="tight-form-input span10"
-					ng-model="target.query"
-					placeholder="select ..."
-					focus-me="target.rawQuery"
-					spellcheck='false'
-					data-min-length=0 data-items=100
-					ng-model-onblur
-					ng-blur="get_data()">
-				</li>
-			</ul>
-
-			<!-- Query editor mode -->
-			<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
-				<li class="tight-form-item">
-					series
-				</li>
-				<li>
-					<input type="text"
-					class="tight-form-input span8"
-					ng-model="target.series"
-					spellcheck='false'
-					bs-typeahead="listSeries"
-					match-all="true"
-					min-length="3"
-					placeholder="series name"
-					data-min-length=0 data-items=100
-					ng-blur="seriesBlur()">
-				</li>
-
-				<li class="tight-form-item">
-					alias
-				</li>
-
-				<li>
-					<input type="text" class="input-medium tight-form-input" ng-model="target.alias"
-					spellcheck='false' placeholder="alias" ng-blur="get_data()">
-				</li>
-
-			</ul>
-
-			<div class="clearfix"></div>
-		</div>
-
-		<div class="tight-form">
-			<!-- Raw Query mode  -->
-			<ul class="tight-form-list" ng-show="target.rawQuery">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
-				<li class="tight-form-item">
-					alias
-				</li>
-				<li>
-					<input type="text"
-					class="input-medium tight-form-input"
-					ng-model="target.alias"
-					spellcheck='false'
-					placeholder="alias"
-					ng-blur="get_data()">
-				</li>
-				<li class="tight-form-item">
-					group by time
-				</li>
-				<li>
-					<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
-					spellcheck='false' placeholder="{{interval}}" data-placement="right"
-					bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
-					ng-model-onblur ng-change="get_data()" >
-				</li>
-			</ul>
-
-			<!-- Query editor mode -->
-			<ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
-				<li class="tight-form-item">
-					<i class="fa fa-eye invisible"></i>
-				</li>
-				<li class="tight-form-item">
-					select
-				</li>
-				<li class="dropdown">
-					<span influxdb-func-editor class="tight-form-item tight-form-func">
-					</span>
-				</li>
-
-				<li class="tight-form-item">
-					where
-				</li>
-				<li>
-					<input type="text" class="input-medium tight-form-input" ng-model="target.condition"
-					bs-tooltip="'Add a where clause'" data-placement="right" spellcheck='false' placeholder="column ~= value" ng-blur="get_data()">
-				</li>
-
-				<li class="tight-form-item">
-					group by time
-				</li>
-				<li>
-					<input type="text" class="input-mini tight-form-input" ng-model="target.interval"
-					spellcheck='false' placeholder="{{interval}}" data-placement="right"
-					bs-tooltip="'Leave blank for auto handling based on time range and panel width'"
-					ng-model-onblur ng-change="get_data()" >
-				</li>
-
-				<li class="tight-form-item">
-					and
-				</li>
-
-				<li>
-					<input type="text" class="input-small tight-form-input" ng-model="target.groupby_field" bs-tooltip="'Add a group by column or leave blank'"
-					placeholder="column" spellcheck="false" bs-typeahead="listColumns" data-min-length=0 ng-blur="get_data()">
-				</li>
-
-				<li class="dropdown">
-					<a class="tight-form-item pointer" data-toggle="dropdown" bs-tooltip="'Insert missing values, important when stacking'" data-placement="right">
-						<span ng-show="target.fill">
-							fill ({{target.fill}})
-						</span>
-						<span ng-show="!target.fill">
-							no fill
-						</span>
-					</a>
-					<ul class="dropdown-menu">
-						<li><a ng-click="target.fill = ''">no fill</a></li>
-						<li><a ng-click="target.fill = 'null'">fill (null)</a></li>
-						<li><a ng-click="target.fill = '0'">fill (0)</a></li>
-					</ul>
-				</li>
-			</ul>
-			<div class="clearfix"></div>
-		</div>
-	</div>
+			<input  type="text"
+			        class="tight-form-clear-input span10"
+              ng-model="target.query"
+              focus-me="target.rawQuery"
+              spellcheck='false'
+              ng-model-onblur ng-change="targetTextChanged()"
+              ng-show="target.rawQuery" />
+
+      <ul class="tight-form-list" role="menu" ng-hide="target.rawQuery">
+        <li ng-repeat="segment in segments" role="menuitem" graphite-segment></li>
+				<li ng-repeat="func in functions">
+          <span graphite-func-editor class="tight-form-item tight-form-func">
+          </span>
+        </li>
+        <li class="dropdown" graphite-add-func>
+        </li>
+      </ul>
+      <div class="clearfix"></div>
+    </div>
 </div>
 
-<section class="grafana-metric-options">
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item tight-form-item-icon">
-				<i class="fa fa-wrench"></i>
-			</li>
-			<li class="tight-form-item">
-				group by time
-			</li>
-			<li>
-				<input type="text" class="input-medium tight-form-input" ng-model="panel.interval" ng-blur="get_data();"
-				spellcheck='false' placeholder="example: >10s">
-			</li>
-			<li class="tight-form-item">
-				<i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i>
-			</li>
-		</ul>
-		<div class="clearfix"></div>
-	</div>
-
-	<div class="tight-form">
-		<ul class="tight-form-list">
-			<li class="tight-form-item tight-form-item-icon">
-				<i class="fa fa-info-circle"></i>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					alias patterns
-				</a>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					stacking &amp; and fill
-				</a>
-			</li>
-			<li class="tight-form-item">
-				<a ng-click="toggleEditorHelp(3)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
-					group by time
-				</a>
-			</li>
-		</ul>
-		<div class="clearfix"></div>
-	</div>
-</section>
-
 <div class="editor-row">
 	<div class="pull-left" style="margin-top: 30px;">
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 1">
-			<h5>Alias patterns</h5>
-			<ul>
-				<li>$s = series name</li>
-				<li>$g = group by</li>
-				<li>$[0-9] part of series name for series names seperated by dots.</li>
-			</ul>
-		</div>
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 2">
-			<h5>Stacking and fill</h5>
-			<ul>
-				<li>When stacking is enabled it important that points align</li>
-				<li>If there are missing points for one series it can cause gaps or missing bars</li>
-				<li>You must use fill(0), and select a group by time low limit</li>
-				<li>Use the group by time option below your queries and specify for example &gt;10s if your metrics are written every 10 seconds</li>
-				<li>This will insert zeros for series that are missing measurements and will make stacking work properly</li>
-			</ul>
-		</div>
-
-		<div class="grafana-info-box span6" ng-if="editorHelpIndex === 3">
-			<h5>Group by time</h5>
-			<ul>
-				<li>Group by time is important, otherwise the query could return many thousands of datapoints that will slow down Grafana</li>
-				<li>Leave the group by time field empty for each query and it will be calculated based on time range and pixel width of the graph</li>
-				<li>If you use fill(0) or fill(null) set a low limit for the auto group by time interval</li>
-				<li>The low limit can only be set in the group by time option below your queries</li>
-				<li>You set a low limit by adding a greater sign before the interval</li>
-				<li>Example: &gt;60s if you write metrics to InfluxDB every 60 seconds</li>
-			</ul>
-		</div>
-
-
 	</div>
 </div>
-
-

+ 116 - 65
src/app/plugins/datasource/influxdb/queryCtrl.js

@@ -7,102 +7,153 @@ function (angular, _) {
 
   var module = angular.module('grafana.controllers');
 
-  var seriesList = null;
-
-  module.controller('InfluxQueryCtrl', function($scope, $timeout) {
+  module.controller('InfluxQueryCtrl', function($scope, $timeout, $sce, templateSrv, $q) {
 
     $scope.init = function() {
-      var target = $scope.target;
-
-      target.function = target.function || 'mean';
-      target.column = target.column || 'value';
-
-      // backward compatible correction of schema
-      if (target.condition_value) {
-        target.condition = target.condition_key + ' ' + target.condition_op + ' ' + target.condition_value;
-        delete target.condition_key;
-        delete target.condition_op;
-        delete target.condition_value;
-      }
-
-      if (target.groupby_field_add === false) {
-        target.groupby_field = '';
-        delete target.groupby_field_add;
-      }
+      $scope.segments = $scope.target.segments || [];
 
-      $scope.rawQuery = true;
-
-      $scope.functions = [
+      $scope.functionsSelect = [
         'count', 'mean', 'sum', 'min',
         'max', 'mode', 'distinct', 'median',
         'derivative', 'stddev', 'first', 'last',
         'difference'
       ];
 
-      $scope.operators = ['=', '=~', '>', '<', '!~', '<>'];
-      $scope.oldSeries = target.series;
-      $scope.$on('typeahead-updated', function() {
-        $timeout($scope.get_data);
-      });
+      checkOtherSegments(0);
     };
 
-    $scope.showQuery = function () {
-      $scope.target.rawQuery = true;
+    $scope.toggleQueryMode = function () {
+      $scope.target.rawQuery = !$scope.target.rawQuery;
     };
 
-    $scope.hideQuery = function () {
-      $scope.target.rawQuery = false;
+    $scope.moveMetricQuery = function(fromIndex, toIndex) {
+      _.move($scope.panel.targets, fromIndex, toIndex);
     };
 
-    // Cannot use typeahead and ng-change on blur at the same time
-    $scope.seriesBlur = function() {
-      if ($scope.oldSeries !== $scope.target.series) {
-        $scope.oldSeries = $scope.target.series;
-        $scope.columnList = null;
-        $scope.get_data();
-      }
+    $scope.duplicate = function() {
+      var clone = angular.copy($scope.target);
+      $scope.panel.targets.push(clone);
     };
 
-    $scope.changeFunction = function(func) {
-      $scope.target.function = func;
-      $scope.get_data();
+    $scope.getAltSegments = function (index) {
+      $scope.altSegments = [];
+
+      var measurement = $scope.segments[0].value;
+      var queryType, query;
+      if (index === 0) {
+        queryType = 'MEASUREMENTS';
+        query = 'SHOW MEASUREMENTS';
+      } else if (index % 2 === 1) {
+        queryType = 'TAG_KEYS';
+        query = 'SHOW TAG KEYS FROM ' + measurement;
+      } else {
+        queryType = 'TAG_VALUES';
+        query = "SHOW TAG VALUES FROM " + measurement + " WITH KEY = " + $scope.segments[$scope.segments.length - 2].value;
+      }
+
+      console.log('getAltSegments: query' , query);
+
+      return $scope.datasource.metricFindQuery(query, queryType).then(function(results) {
+        console.log('get alt segments: response', results);
+        $scope.altSegments = _.map(results, function(segment) {
+          return new MetricSegment({ value: segment.text, expandable: segment.expandable });
+        });
+
+        _.each(templateSrv.variables, function(variable) {
+          $scope.altSegments.unshift(new MetricSegment({
+            type: 'template',
+            value: '$' + variable.name,
+            expandable: true,
+          }));
+        });
+      }, function(err) {
+        $scope.parserError = err.message || 'Failed to issue metric query';
+      });
     };
 
-    // called outside of digest
-    $scope.listColumns = function(query, callback) {
-      if (!$scope.columnList) {
-        $scope.$apply(function() {
-          $scope.datasource.listColumns($scope.target.series).then(function(columns) {
-            $scope.columnList = columns;
-            callback(columns);
-          });
+    $scope.segmentValueChanged = function (segment, segmentIndex) {
+      delete $scope.parserError;
+
+      if (segment.expandable) {
+        return checkOtherSegments(segmentIndex + 1).then(function () {
+          setSegmentFocus(segmentIndex + 1);
+          $scope.targetChanged();
         });
       }
       else {
-        return $scope.columnList;
+        $scope.segments = $scope.segments.splice(0, segmentIndex + 1);
       }
+
+      setSegmentFocus(segmentIndex + 1);
+      $scope.targetChanged();
     };
 
-    $scope.listSeries = function(query, callback) {
-      if (query !== '') {
-        seriesList = [];
-        $scope.datasource.listSeries(query).then(function(series) {
-          seriesList = series;
-          callback(seriesList);
-        });
+    $scope.targetChanged = function() {
+      if ($scope.parserError) {
+        return;
       }
-      else {
-        return seriesList;
+
+      $scope.$parent.get_data();
+    };
+
+    function checkOtherSegments(fromIndex) {
+      if (fromIndex === 0) {
+        $scope.segments.push(MetricSegment.newSelectMetric());
+        return;
+      }
+
+      if ($scope.segments.length === 0) {
+        throw('should always have a scope segment?');
+      }
+
+      if (_.last($scope.segments).fake) {
+        return $q.when([]);
+      } else if ($scope.segments.length % 2 === 1) {
+        $scope.segments.push(MetricSegment.newSelectTag());
+        return $q.when([]);
+      } else {
+        $scope.segments.push(MetricSegment.newSelectTagValue());
+        return $q.when([]);
+      }
+    }
+
+    function setSegmentFocus(segmentIndex) {
+      _.each($scope.segments, function(segment, index) {
+        segment.focus = segmentIndex === index;
+      });
+    }
+
+    function MetricSegment(options) {
+      if (options === '*' || options.value === '*') {
+        this.value = '*';
+        this.html = $sce.trustAsHtml('<i class="fa fa-asterisk"><i>');
+        this.expandable = true;
+        return;
+      }
+
+      if (_.isString(options)) {
+        this.value = options;
+        this.html = $sce.trustAsHtml(this.value);
+        return;
       }
+
+      this.fake = options.fake;
+      this.value = options.value;
+      this.type = options.type;
+      this.expandable = options.expandable;
+      this.html = $sce.trustAsHtml(templateSrv.highlightVariablesAsHtml(this.value));
+    }
+
+    MetricSegment.newSelectMetric = function() {
+      return new MetricSegment({value: 'select metric', fake: true});
     };
 
-    $scope.moveMetricQuery = function(fromIndex, toIndex) {
-      _.move($scope.panel.targets, fromIndex, toIndex);
+    MetricSegment.newSelectTag = function() {
+      return new MetricSegment({value: 'select tag', fake: true});
     };
 
-    $scope.duplicate = function() {
-      var clone = angular.copy($scope.target);
-      $scope.panel.targets.push(clone);
+    MetricSegment.newSelectTagValue = function() {
+      return new MetricSegment({value: 'select tag value', fake: true});
     };
 
   });