mirror of https://github.com/grafana/grafana.git
				
				
				
			DataLinks: enable access to labels & field names (#18918)
* POC: trying to see if there is a way to support objects in template interpolations * Added support for nested objects, and arrays * Added accessor cache * fixed unit tests * First take * Use links supplier in graph * Add field's index to cache items * Get field index from field cache * CHange FiledCacheItem to FieldWithIndex * Add refId to TimeSeries class * Make field link supplier work with _series, _field and _value vars * use field link supplier in graph * Fix yaxis settings * Update dashboard schema version and add migration for data links variables * Update snapshots * Update build in data link variables * FieldCache - idx -> index * Add current query results to panel editor * WIP Updated data links dropdown to display new variables * Fix build * Update variables syntac in field display, update migration * Field links supplier: review updates * Add data frame view and field name to TimeSeries for later inspection * Retrieve data frame from TimeSeries when clicking on plot graph * Use data frame's index instead of view * Retrieve data frame by index instead of view on TimeSeries * Update data links prism regex * Fix typecheck * Add value variables to suggestions list * UI update * Rename field to config in DisplayProcessorOptions * Proces single value of a field instead of entire data frame * Updated font size from 10px to 12px for auto complete * Replace fieldName with fieldIndex in TimeSeries * Don't use .entries() for iterating in field cache * Don't use FieldCache when retrieving field for datalinks in graph * Add value calculation variable to data links (#19031) * Add support for labels with dots in the name (#19033) * Docs update * Use field name instead of removed series.fieldName * Add test dashboard * Typos fix * Make visualization tab subscribe to query results * Added tags to dashboard so it shows up in lists * minor docs fix * Update singlestat-ish variables suggestions to contain series variables * Decrease suggestions update debounce * Enable whitespace characters(new line, space) in links and strip them when processing the data link * minor data links UI update * DataLinks: Add __from and __to variables suggestions to data links (#19093) * Add from and to variables suggestions to data links * Update docs * UI update and added info text * Change ESC global bind to bind (doesn't capture ESC on input) * Close datalinks suggestions on ESC * Remove unnecessary fragment
This commit is contained in:
		
							parent
							
								
									fc10bd7b8e
								
							
						
					
					
						commit
						fd21e0ba14
					
				| 
						 | 
					@ -0,0 +1,510 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "annotations": {
 | 
				
			||||||
 | 
					    "list": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "builtIn": 1,
 | 
				
			||||||
 | 
					        "datasource": "-- Grafana --",
 | 
				
			||||||
 | 
					        "enable": true,
 | 
				
			||||||
 | 
					        "hide": true,
 | 
				
			||||||
 | 
					        "iconColor": "rgba(0, 211, 255, 1)",
 | 
				
			||||||
 | 
					        "name": "Annotations & Alerts",
 | 
				
			||||||
 | 
					        "type": "dashboard"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "editable": true,
 | 
				
			||||||
 | 
					  "gnetId": null,
 | 
				
			||||||
 | 
					  "graphTooltip": 0,
 | 
				
			||||||
 | 
					  "iteration": 1568372030444,
 | 
				
			||||||
 | 
					  "links": [],
 | 
				
			||||||
 | 
					  "panels": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "content": "## Data link variables overview\n\nThis dashboard presents variables that one can use when creating *data links*. All links redirect to this dashboard and this panel represents the values that were interpolated in the link that was clicked.\n\n\n#### Series variables\n1. **Name:** <span style=\"color: orange;\">$seriesName</span>\n2. **label.datacenter:** <span style=\"color: orange;\">$labelDatacenter</span>\n3. **label.datacenter.region:** <span style=\"color: orange;\">$labelDatacenterRegion</span>\n\n#### Field variables\n1. **Name:** <span style=\"color: orange;\">$fieldName</span>\n\n#### Value variables\n1. **Time:** <span style=\"color: orange;\">$valueTime</span>\n2. **Numeric:** <span style=\"color: orange;\">$valueNumeric</span>\n3. **Text:** <span style=\"color: orange;\">$valueText</span>\n4. **Calc:** <span style=\"color: orange;\">$valueCalc</span>\n\n",
 | 
				
			||||||
 | 
					      "gridPos": {
 | 
				
			||||||
 | 
					        "h": 16,
 | 
				
			||||||
 | 
					        "w": 6,
 | 
				
			||||||
 | 
					        "x": 0,
 | 
				
			||||||
 | 
					        "y": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "id": 8,
 | 
				
			||||||
 | 
					      "mode": "markdown",
 | 
				
			||||||
 | 
					      "options": {},
 | 
				
			||||||
 | 
					      "timeFrom": null,
 | 
				
			||||||
 | 
					      "timeShift": null,
 | 
				
			||||||
 | 
					      "title": "",
 | 
				
			||||||
 | 
					      "transparent": true,
 | 
				
			||||||
 | 
					      "type": "text"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "aliasColors": {},
 | 
				
			||||||
 | 
					      "bars": false,
 | 
				
			||||||
 | 
					      "dashLength": 10,
 | 
				
			||||||
 | 
					      "dashes": false,
 | 
				
			||||||
 | 
					      "fill": 1,
 | 
				
			||||||
 | 
					      "fillGradient": 0,
 | 
				
			||||||
 | 
					      "gridPos": {
 | 
				
			||||||
 | 
					        "h": 8,
 | 
				
			||||||
 | 
					        "w": 9,
 | 
				
			||||||
 | 
					        "x": 6,
 | 
				
			||||||
 | 
					        "y": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "id": 2,
 | 
				
			||||||
 | 
					      "legend": {
 | 
				
			||||||
 | 
					        "avg": false,
 | 
				
			||||||
 | 
					        "current": false,
 | 
				
			||||||
 | 
					        "max": false,
 | 
				
			||||||
 | 
					        "min": false,
 | 
				
			||||||
 | 
					        "show": true,
 | 
				
			||||||
 | 
					        "total": false,
 | 
				
			||||||
 | 
					        "values": false
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "lines": true,
 | 
				
			||||||
 | 
					      "linewidth": 1,
 | 
				
			||||||
 | 
					      "nullPointMode": "null",
 | 
				
			||||||
 | 
					      "options": {
 | 
				
			||||||
 | 
					        "dataLinks": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "targetBlank": false,
 | 
				
			||||||
 | 
					            "title": "Drill it down",
 | 
				
			||||||
 | 
					            "url": "http://localhost:3000/d/wfTJJL5Wz/datalinks-source?var-seriesName=${__series.name}&var-labelDatacenter=${__series.labels.datacenter}&var-labelDatacenterRegion=${__series.labels[\"datacenter.region\"]}&var-valueTime=${__value.time}&var-valueNumeric=${__value.numeric}&var-valueText=${__value.text}"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "percentage": false,
 | 
				
			||||||
 | 
					      "pointradius": 2,
 | 
				
			||||||
 | 
					      "points": false,
 | 
				
			||||||
 | 
					      "renderer": "flot",
 | 
				
			||||||
 | 
					      "seriesOverrides": [],
 | 
				
			||||||
 | 
					      "spaceLength": 10,
 | 
				
			||||||
 | 
					      "stack": false,
 | 
				
			||||||
 | 
					      "steppedLine": false,
 | 
				
			||||||
 | 
					      "targets": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "alias": "Foo datacenter",
 | 
				
			||||||
 | 
					          "labels": "datacenter=foo,datacenter.region=us-east-1",
 | 
				
			||||||
 | 
					          "refId": "A",
 | 
				
			||||||
 | 
					          "scenarioId": "random_walk"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "alias": "Bar datacenter",
 | 
				
			||||||
 | 
					          "labels": "datacenter=bar,datacenter.region=us-east-2",
 | 
				
			||||||
 | 
					          "refId": "B",
 | 
				
			||||||
 | 
					          "scenarioId": "random_walk"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "thresholds": [],
 | 
				
			||||||
 | 
					      "timeFrom": null,
 | 
				
			||||||
 | 
					      "timeRegions": [],
 | 
				
			||||||
 | 
					      "timeShift": null,
 | 
				
			||||||
 | 
					      "title": "Multiple series",
 | 
				
			||||||
 | 
					      "tooltip": {
 | 
				
			||||||
 | 
					        "shared": true,
 | 
				
			||||||
 | 
					        "sort": 0,
 | 
				
			||||||
 | 
					        "value_type": "individual"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "type": "graph",
 | 
				
			||||||
 | 
					      "xaxis": {
 | 
				
			||||||
 | 
					        "buckets": null,
 | 
				
			||||||
 | 
					        "mode": "time",
 | 
				
			||||||
 | 
					        "name": null,
 | 
				
			||||||
 | 
					        "show": true,
 | 
				
			||||||
 | 
					        "values": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "yaxes": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "format": "short",
 | 
				
			||||||
 | 
					          "label": null,
 | 
				
			||||||
 | 
					          "logBase": 1,
 | 
				
			||||||
 | 
					          "max": null,
 | 
				
			||||||
 | 
					          "min": null,
 | 
				
			||||||
 | 
					          "show": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "format": "short",
 | 
				
			||||||
 | 
					          "label": null,
 | 
				
			||||||
 | 
					          "logBase": 1,
 | 
				
			||||||
 | 
					          "max": null,
 | 
				
			||||||
 | 
					          "min": null,
 | 
				
			||||||
 | 
					          "show": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "yaxis": {
 | 
				
			||||||
 | 
					        "align": false,
 | 
				
			||||||
 | 
					        "alignLevel": null
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "aliasColors": {},
 | 
				
			||||||
 | 
					      "bars": false,
 | 
				
			||||||
 | 
					      "dashLength": 10,
 | 
				
			||||||
 | 
					      "dashes": false,
 | 
				
			||||||
 | 
					      "fill": 1,
 | 
				
			||||||
 | 
					      "fillGradient": 0,
 | 
				
			||||||
 | 
					      "gridPos": {
 | 
				
			||||||
 | 
					        "h": 8,
 | 
				
			||||||
 | 
					        "w": 9,
 | 
				
			||||||
 | 
					        "x": 15,
 | 
				
			||||||
 | 
					        "y": 0
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "id": 9,
 | 
				
			||||||
 | 
					      "legend": {
 | 
				
			||||||
 | 
					        "avg": false,
 | 
				
			||||||
 | 
					        "current": false,
 | 
				
			||||||
 | 
					        "max": false,
 | 
				
			||||||
 | 
					        "min": false,
 | 
				
			||||||
 | 
					        "show": true,
 | 
				
			||||||
 | 
					        "total": false,
 | 
				
			||||||
 | 
					        "values": false
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "lines": true,
 | 
				
			||||||
 | 
					      "linewidth": 1,
 | 
				
			||||||
 | 
					      "nullPointMode": "null",
 | 
				
			||||||
 | 
					      "options": {
 | 
				
			||||||
 | 
					        "dataLinks": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "targetBlank": false,
 | 
				
			||||||
 | 
					            "title": "Drill it down",
 | 
				
			||||||
 | 
					            "url": "http://localhost:3000/d/wfTJJL5Wz/datalinks-source?var-seriesName=${__series.name}&var-valueTime=${__value.time}&var-valueNumeric=${__value.numeric}&var-valueText=${__value.text}&var-fieldName=${__field.name}"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "percentage": false,
 | 
				
			||||||
 | 
					      "pointradius": 2,
 | 
				
			||||||
 | 
					      "points": false,
 | 
				
			||||||
 | 
					      "renderer": "flot",
 | 
				
			||||||
 | 
					      "seriesOverrides": [],
 | 
				
			||||||
 | 
					      "spaceLength": 10,
 | 
				
			||||||
 | 
					      "stack": false,
 | 
				
			||||||
 | 
					      "steppedLine": false,
 | 
				
			||||||
 | 
					      "targets": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "alias": "Foo datacenter",
 | 
				
			||||||
 | 
					          "labels": "datacenter=foo,datacenter.region=us-east-1",
 | 
				
			||||||
 | 
					          "refId": "A",
 | 
				
			||||||
 | 
					          "scenarioId": "random_walk_table",
 | 
				
			||||||
 | 
					          "stringInput": ""
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "thresholds": [],
 | 
				
			||||||
 | 
					      "timeFrom": null,
 | 
				
			||||||
 | 
					      "timeRegions": [],
 | 
				
			||||||
 | 
					      "timeShift": null,
 | 
				
			||||||
 | 
					      "title": "Multiple fields",
 | 
				
			||||||
 | 
					      "tooltip": {
 | 
				
			||||||
 | 
					        "shared": true,
 | 
				
			||||||
 | 
					        "sort": 0,
 | 
				
			||||||
 | 
					        "value_type": "individual"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "type": "graph",
 | 
				
			||||||
 | 
					      "xaxis": {
 | 
				
			||||||
 | 
					        "buckets": null,
 | 
				
			||||||
 | 
					        "mode": "time",
 | 
				
			||||||
 | 
					        "name": null,
 | 
				
			||||||
 | 
					        "show": true,
 | 
				
			||||||
 | 
					        "values": []
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "yaxes": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "format": "short",
 | 
				
			||||||
 | 
					          "label": null,
 | 
				
			||||||
 | 
					          "logBase": 1,
 | 
				
			||||||
 | 
					          "max": null,
 | 
				
			||||||
 | 
					          "min": null,
 | 
				
			||||||
 | 
					          "show": true
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "format": "short",
 | 
				
			||||||
 | 
					          "label": null,
 | 
				
			||||||
 | 
					          "logBase": 1,
 | 
				
			||||||
 | 
					          "max": null,
 | 
				
			||||||
 | 
					          "min": null,
 | 
				
			||||||
 | 
					          "show": true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "yaxis": {
 | 
				
			||||||
 | 
					        "align": false,
 | 
				
			||||||
 | 
					        "alignLevel": null
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "cacheTimeout": null,
 | 
				
			||||||
 | 
					      "datasource": "-- Dashboard --",
 | 
				
			||||||
 | 
					      "gridPos": {
 | 
				
			||||||
 | 
					        "h": 8,
 | 
				
			||||||
 | 
					        "w": 9,
 | 
				
			||||||
 | 
					        "x": 6,
 | 
				
			||||||
 | 
					        "y": 8
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "id": 6,
 | 
				
			||||||
 | 
					      "links": [],
 | 
				
			||||||
 | 
					      "options": {
 | 
				
			||||||
 | 
					        "displayMode": "lcd",
 | 
				
			||||||
 | 
					        "fieldOptions": {
 | 
				
			||||||
 | 
					          "calcs": ["last"],
 | 
				
			||||||
 | 
					          "defaults": {
 | 
				
			||||||
 | 
					            "links": [
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "targetBlank": true,
 | 
				
			||||||
 | 
					                "title": "Drill it down!",
 | 
				
			||||||
 | 
					                "url": "http://localhost:3000/d/wfTJJL5Wz/datalinks-source\n?var-fieldName=${__field.name}\n&var-labelDatacenter=${__series.labels.datacenter}\n&var-labelDatacenterRegion=${__series.labels[\"datacenter.region\"]}\n&var-valueNumeric=${__value.numeric}\n&var-valueText=${__value.text}\n&var-valueCalc=${__value.calc}"
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "mappings": [
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "id": 0,
 | 
				
			||||||
 | 
					                "op": "=",
 | 
				
			||||||
 | 
					                "text": "N/A",
 | 
				
			||||||
 | 
					                "type": 1,
 | 
				
			||||||
 | 
					                "value": "null"
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "max": 100,
 | 
				
			||||||
 | 
					            "min": 0,
 | 
				
			||||||
 | 
					            "nullValueMode": "connected",
 | 
				
			||||||
 | 
					            "thresholds": [
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "color": "green",
 | 
				
			||||||
 | 
					                "value": null
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "color": "red",
 | 
				
			||||||
 | 
					                "value": 80
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "title": "${__series.name} - $__calc",
 | 
				
			||||||
 | 
					            "unit": "none"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "override": {},
 | 
				
			||||||
 | 
					          "values": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "orientation": "horizontal"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "pluginVersion": "6.4.0-pre",
 | 
				
			||||||
 | 
					      "targets": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "panelId": 2,
 | 
				
			||||||
 | 
					          "refId": "A"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "timeFrom": null,
 | 
				
			||||||
 | 
					      "timeShift": null,
 | 
				
			||||||
 | 
					      "title": "Value reducers 1",
 | 
				
			||||||
 | 
					      "type": "bargauge"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "datasource": "-- Dashboard --",
 | 
				
			||||||
 | 
					      "gridPos": {
 | 
				
			||||||
 | 
					        "h": 8,
 | 
				
			||||||
 | 
					        "w": 9,
 | 
				
			||||||
 | 
					        "x": 15,
 | 
				
			||||||
 | 
					        "y": 8
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "id": 4,
 | 
				
			||||||
 | 
					      "options": {
 | 
				
			||||||
 | 
					        "fieldOptions": {
 | 
				
			||||||
 | 
					          "calcs": ["mean"],
 | 
				
			||||||
 | 
					          "defaults": {
 | 
				
			||||||
 | 
					            "links": [
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "title": "Drill it down",
 | 
				
			||||||
 | 
					                "url": "http://localhost:3000/d/wfTJJL5Wz/datalinks-source?var-fieldName=${__field.name}&var-labelDatacenter=${__series.labels.datacenter}&var-labelDatacenterRegion=${__series.labels[\"datacenter.region\"]}&var-valueNumeric=${__value.numeric}&var-valueText=${__value.text}&var-valueCalc=${__value.calc}"
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "mappings": [],
 | 
				
			||||||
 | 
					            "max": 100,
 | 
				
			||||||
 | 
					            "min": 0,
 | 
				
			||||||
 | 
					            "thresholds": [
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "color": "green",
 | 
				
			||||||
 | 
					                "value": null
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					              {
 | 
				
			||||||
 | 
					                "color": "red",
 | 
				
			||||||
 | 
					                "value": 80
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "title": "${__series.name} - $__calc"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "override": {},
 | 
				
			||||||
 | 
					          "values": false
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "orientation": "auto",
 | 
				
			||||||
 | 
					        "showThresholdLabels": false,
 | 
				
			||||||
 | 
					        "showThresholdMarkers": true
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "pluginVersion": "6.4.0-pre",
 | 
				
			||||||
 | 
					      "targets": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "panelId": 2,
 | 
				
			||||||
 | 
					          "refId": "A"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "timeFrom": null,
 | 
				
			||||||
 | 
					      "timeShift": null,
 | 
				
			||||||
 | 
					      "title": "Value reducers 2",
 | 
				
			||||||
 | 
					      "type": "gauge"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "schemaVersion": 20,
 | 
				
			||||||
 | 
					  "style": "dark",
 | 
				
			||||||
 | 
					  "tags": ["gdev", "templating"],
 | 
				
			||||||
 | 
					  "templating": {
 | 
				
			||||||
 | 
					    "list": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "current": {
 | 
				
			||||||
 | 
					          "text": "",
 | 
				
			||||||
 | 
					          "value": ""
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "hide": 2,
 | 
				
			||||||
 | 
					        "label": "Series name",
 | 
				
			||||||
 | 
					        "name": "seriesName",
 | 
				
			||||||
 | 
					        "options": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "text": "",
 | 
				
			||||||
 | 
					            "value": ""
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "query": "",
 | 
				
			||||||
 | 
					        "skipUrlSync": false,
 | 
				
			||||||
 | 
					        "type": "textbox"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "current": {
 | 
				
			||||||
 | 
					          "text": "",
 | 
				
			||||||
 | 
					          "value": ""
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "hide": 2,
 | 
				
			||||||
 | 
					        "label": null,
 | 
				
			||||||
 | 
					        "name": "labelDatacenter",
 | 
				
			||||||
 | 
					        "options": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "text": "",
 | 
				
			||||||
 | 
					            "value": ""
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "query": "",
 | 
				
			||||||
 | 
					        "skipUrlSync": false,
 | 
				
			||||||
 | 
					        "type": "textbox"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "current": {
 | 
				
			||||||
 | 
					          "text": "",
 | 
				
			||||||
 | 
					          "value": ""
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "hide": 2,
 | 
				
			||||||
 | 
					        "label": null,
 | 
				
			||||||
 | 
					        "name": "labelDatacenterRegion",
 | 
				
			||||||
 | 
					        "options": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "text": "",
 | 
				
			||||||
 | 
					            "value": ""
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "query": "",
 | 
				
			||||||
 | 
					        "skipUrlSync": false,
 | 
				
			||||||
 | 
					        "type": "textbox"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "current": {
 | 
				
			||||||
 | 
					          "text": "",
 | 
				
			||||||
 | 
					          "value": ""
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "hide": 2,
 | 
				
			||||||
 | 
					        "label": null,
 | 
				
			||||||
 | 
					        "name": "valueTime",
 | 
				
			||||||
 | 
					        "options": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "text": "",
 | 
				
			||||||
 | 
					            "value": ""
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "query": "",
 | 
				
			||||||
 | 
					        "skipUrlSync": false,
 | 
				
			||||||
 | 
					        "type": "textbox"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "current": {
 | 
				
			||||||
 | 
					          "text": "",
 | 
				
			||||||
 | 
					          "value": ""
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "hide": 2,
 | 
				
			||||||
 | 
					        "label": null,
 | 
				
			||||||
 | 
					        "name": "valueNumeric",
 | 
				
			||||||
 | 
					        "options": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "text": "",
 | 
				
			||||||
 | 
					            "value": ""
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "query": "",
 | 
				
			||||||
 | 
					        "skipUrlSync": false,
 | 
				
			||||||
 | 
					        "type": "textbox"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "current": {
 | 
				
			||||||
 | 
					          "text": "",
 | 
				
			||||||
 | 
					          "value": ""
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "hide": 2,
 | 
				
			||||||
 | 
					        "label": null,
 | 
				
			||||||
 | 
					        "name": "valueText",
 | 
				
			||||||
 | 
					        "options": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "text": "",
 | 
				
			||||||
 | 
					            "value": ""
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "query": "",
 | 
				
			||||||
 | 
					        "skipUrlSync": false,
 | 
				
			||||||
 | 
					        "type": "textbox"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "current": {
 | 
				
			||||||
 | 
					          "text": "",
 | 
				
			||||||
 | 
					          "value": ""
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "hide": 2,
 | 
				
			||||||
 | 
					        "label": null,
 | 
				
			||||||
 | 
					        "name": "valueCalc",
 | 
				
			||||||
 | 
					        "options": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "text": "",
 | 
				
			||||||
 | 
					            "value": ""
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "query": "",
 | 
				
			||||||
 | 
					        "skipUrlSync": false,
 | 
				
			||||||
 | 
					        "type": "textbox"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "current": {
 | 
				
			||||||
 | 
					          "text": "",
 | 
				
			||||||
 | 
					          "value": ""
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "hide": 2,
 | 
				
			||||||
 | 
					        "label": null,
 | 
				
			||||||
 | 
					        "name": "fieldName",
 | 
				
			||||||
 | 
					        "options": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "text": "",
 | 
				
			||||||
 | 
					            "value": ""
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "query": "",
 | 
				
			||||||
 | 
					        "skipUrlSync": false,
 | 
				
			||||||
 | 
					        "type": "textbox"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "time": {
 | 
				
			||||||
 | 
					    "from": "now-6h",
 | 
				
			||||||
 | 
					    "to": "now"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "timepicker": {
 | 
				
			||||||
 | 
					    "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "timezone": "",
 | 
				
			||||||
 | 
					  "title": "Datalinks - variables",
 | 
				
			||||||
 | 
					  "uid": "wfTJJL5Wz",
 | 
				
			||||||
 | 
					  "version": 1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -192,7 +192,7 @@ Panel time overrides & timeshift are described in more detail [here]({{< relref
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> Only available in Grafana v6.3+.
 | 
					> Only available in Grafana v6.3+.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Data link in graph settings allows adding dynamic links to the visualization. Those links can link to either other dashboard or to an external URL.
 | 
					Data link allows adding dynamic links to the visualization. Those links can link to either other dashboard or to an external URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{{< docs-imagebox img="/img/docs/data_link.png"  max-width= "800px" >}}
 | 
					{{< docs-imagebox img="/img/docs/data_link.png"  max-width= "800px" >}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -208,14 +208,40 @@ available suggestions:
 | 
				
			||||||
{{< docs-imagebox img="/img/docs/data_link_typeahead.png"  max-width= "800px" >}}
 | 
					{{< docs-imagebox img="/img/docs/data_link_typeahead.png"  max-width= "800px" >}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Available built-in variables are:
 | 
					#### Built-in variables
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. ``__all_variables`` - will add all current dashboard's variables to the URL
 | 
					``__url_time_range`` - current dashboard's time range (i.e. ``?from=now-6h&to=now``)
 | 
				
			||||||
2. ``__url_time_range`` - will add current dashboard's time range to the URL (i.e. ``?from=now-6h&to=now``)
 | 
					``__from`` - current dashboard's time range from value
 | 
				
			||||||
3. ``__series_name`` - will add series name as a query param in the URL (i.e. ``?series=B-series``)
 | 
					``__to`` - current dashboard's time range to value
 | 
				
			||||||
4. ``__value_time`` - will add datapoint's timestamp (Unix ms epoch) to the URL (i.e. ``?time=1560268814105``)
 | 
					
 | 
				
			||||||
 | 
					#### Series variables
 | 
				
			||||||
 | 
					Series specific variables are available under ``__series`` namespace:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``__series.name`` - series name to the URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``__series.labels.<LABEL>`` - label's value to the URL. If your label contains dots use ``__series.labels["<LABEL>"]`` syntax
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Field variables
 | 
				
			||||||
 | 
					Field specific variables are available under ``__field`` namespace:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``__field.name`` - field name to the URL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Value variables
 | 
				
			||||||
 | 
					Value specific variables are available under ``__value`` namespace:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``__value.time`` - value's timestamp (Unix ms epoch) to the URL (i.e. ``?time=1560268814105``)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``__value.raw`` - raw value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``__value.numeric`` - numeric representation of a value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``__value.text`` - text representation of a value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``__value.calc`` - calculation name if the value is result of calculation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#### Template variables in data links
 | 
					
 | 
				
			||||||
 | 
					#### Template variables
 | 
				
			||||||
 | 
					
 | 
				
			||||||
When linking to another dashboard that uses template variables, you can use ``var-myvar=${myvar}`` syntax (where ``myvar`` is a name of template variable)
 | 
					When linking to another dashboard that uses template variables, you can use ``var-myvar=${myvar}`` syntax (where ``myvar`` is a name of template variable)
 | 
				
			||||||
to use current dashboard's variable value.
 | 
					to use current dashboard's variable value. If you want to add all of the current dashboard's variables to the URL use  ``__all_variables`` variable.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,8 @@ export class FieldCache {
 | 
				
			||||||
      index: idx,
 | 
					      index: idx,
 | 
				
			||||||
    }));
 | 
					    }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const [index, field] of data.fields.entries()) {
 | 
					    for (let i = 0; i < data.fields.length; i++) {
 | 
				
			||||||
 | 
					      const field = data.fields[i];
 | 
				
			||||||
      // Make sure it has a type
 | 
					      // Make sure it has a type
 | 
				
			||||||
      if (field.type === FieldType.other) {
 | 
					      if (field.type === FieldType.other) {
 | 
				
			||||||
        const t = guessFieldTypeForField(field);
 | 
					        const t = guessFieldTypeForField(field);
 | 
				
			||||||
| 
						 | 
					@ -33,13 +34,13 @@ export class FieldCache {
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      this.fieldByType[field.type].push({
 | 
					      this.fieldByType[field.type].push({
 | 
				
			||||||
        ...field,
 | 
					        ...field,
 | 
				
			||||||
        index,
 | 
					        index: i,
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.fieldByName[field.name]) {
 | 
					      if (this.fieldByName[field.name]) {
 | 
				
			||||||
        console.warn('Duplicate field names in DataFrame: ', field.name);
 | 
					        console.warn('Duplicate field names in DataFrame: ', field.name);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.fieldByName[field.name] = { ...field, index };
 | 
					        this.fieldByName[field.name] = { ...field, index: i };
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,12 +2,13 @@ import React, { useState, ChangeEvent, useContext } from 'react';
 | 
				
			||||||
import { DataLink } from '@grafana/data';
 | 
					import { DataLink } from '@grafana/data';
 | 
				
			||||||
import { FormField, Switch } from '../index';
 | 
					import { FormField, Switch } from '../index';
 | 
				
			||||||
import { VariableSuggestion } from './DataLinkSuggestions';
 | 
					import { VariableSuggestion } from './DataLinkSuggestions';
 | 
				
			||||||
import { css, cx } from 'emotion';
 | 
					import { css } from 'emotion';
 | 
				
			||||||
import { ThemeContext } from '../../themes/index';
 | 
					import { ThemeContext } from '../../themes/index';
 | 
				
			||||||
import { DataLinkInput } from './DataLinkInput';
 | 
					import { DataLinkInput } from './DataLinkInput';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface DataLinkEditorProps {
 | 
					interface DataLinkEditorProps {
 | 
				
			||||||
  index: number;
 | 
					  index: number;
 | 
				
			||||||
 | 
					  isLast: boolean;
 | 
				
			||||||
  value: DataLink;
 | 
					  value: DataLink;
 | 
				
			||||||
  suggestions: VariableSuggestion[];
 | 
					  suggestions: VariableSuggestion[];
 | 
				
			||||||
  onChange: (index: number, link: DataLink) => void;
 | 
					  onChange: (index: number, link: DataLink) => void;
 | 
				
			||||||
| 
						 | 
					@ -15,7 +16,7 @@ interface DataLinkEditorProps {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const DataLinkEditor: React.FC<DataLinkEditorProps> = React.memo(
 | 
					export const DataLinkEditor: React.FC<DataLinkEditorProps> = React.memo(
 | 
				
			||||||
  ({ index, value, onChange, onRemove, suggestions }) => {
 | 
					  ({ index, value, onChange, onRemove, suggestions, isLast }) => {
 | 
				
			||||||
    const theme = useContext(ThemeContext);
 | 
					    const theme = useContext(ThemeContext);
 | 
				
			||||||
    const [title, setTitle] = useState(value.title);
 | 
					    const [title, setTitle] = useState(value.title);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,46 +39,48 @@ export const DataLinkEditor: React.FC<DataLinkEditorProps> = React.memo(
 | 
				
			||||||
      onChange(index, { ...value, targetBlank: !value.targetBlank });
 | 
					      onChange(index, { ...value, targetBlank: !value.targetBlank });
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const listItemStyle = css`
 | 
				
			||||||
 | 
					      margin-bottom: ${theme.spacing.sm};
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const infoTextStyle = css`
 | 
				
			||||||
 | 
					      padding-bottom: ${theme.spacing.md};
 | 
				
			||||||
 | 
					      margin-left: 66px;
 | 
				
			||||||
 | 
					      color: ${theme.colors.textWeak};
 | 
				
			||||||
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div
 | 
					      <div className={listItemStyle}>
 | 
				
			||||||
        className={cx(
 | 
					        <div className="gf-form gf-form--inline">
 | 
				
			||||||
          'gf-form gf-form--inline',
 | 
					 | 
				
			||||||
          css`
 | 
					 | 
				
			||||||
            > * {
 | 
					 | 
				
			||||||
              margin-right: ${theme.spacing.xs};
 | 
					 | 
				
			||||||
              &:last-child {
 | 
					 | 
				
			||||||
                margin-right: 0;
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          `
 | 
					 | 
				
			||||||
        )}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
          <FormField
 | 
					          <FormField
 | 
				
			||||||
 | 
					            className="gf-form--grow"
 | 
				
			||||||
            label="Title"
 | 
					            label="Title"
 | 
				
			||||||
            value={title}
 | 
					            value={title}
 | 
				
			||||||
            onChange={onTitleChange}
 | 
					            onChange={onTitleChange}
 | 
				
			||||||
            onBlur={onTitleBlur}
 | 
					            onBlur={onTitleBlur}
 | 
				
			||||||
          inputWidth={15}
 | 
					            inputWidth={0}
 | 
				
			||||||
            labelWidth={5}
 | 
					            labelWidth={5}
 | 
				
			||||||
            placeholder="Show details"
 | 
					            placeholder="Show details"
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
 | 
					          <Switch label="Open in new tab" checked={value.targetBlank || false} onChange={onOpenInNewTabChanged} />
 | 
				
			||||||
 | 
					          <button className="gf-form-label gf-form-label--btn" onClick={onRemoveClick} title="Remove link">
 | 
				
			||||||
 | 
					            <i className="fa fa-times" />
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
        <FormField
 | 
					        <FormField
 | 
				
			||||||
          label="URL"
 | 
					          label="URL"
 | 
				
			||||||
          labelWidth={4}
 | 
					          labelWidth={5}
 | 
				
			||||||
          inputEl={<DataLinkInput value={value.url} onChange={onUrlChange} suggestions={suggestions} />}
 | 
					          inputEl={<DataLinkInput value={value.url} onChange={onUrlChange} suggestions={suggestions} />}
 | 
				
			||||||
          className={css`
 | 
					          className={css`
 | 
				
			||||||
            width: 100%;
 | 
					            width: 100%;
 | 
				
			||||||
          `}
 | 
					          `}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
 | 
					        {isLast && (
 | 
				
			||||||
        <Switch label="Open in new tab" checked={value.targetBlank || false} onChange={onOpenInNewTabChanged} />
 | 
					          <div className={infoTextStyle}>
 | 
				
			||||||
 | 
					            With data links you can reference data variables like series name, labels and values. Type CMD+Space,
 | 
				
			||||||
        <div className="gf-form">
 | 
					            CTRL+Space, or $ to open variable suggestions.
 | 
				
			||||||
          <button className="gf-form-label gf-form-label--btn" onClick={onRemoveClick}>
 | 
					 | 
				
			||||||
            <i className="fa fa-times" />
 | 
					 | 
				
			||||||
          </button>
 | 
					 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import React, { useState, useMemo, useCallback, useContext } from 'react';
 | 
					import React, { useState, useMemo, useCallback, useContext } from 'react';
 | 
				
			||||||
import { VariableSuggestion, VariableOrigin, DataLinkSuggestions } from './DataLinkSuggestions';
 | 
					import { VariableSuggestion, VariableOrigin, DataLinkSuggestions } from './DataLinkSuggestions';
 | 
				
			||||||
import { makeValue, ThemeContext } from '../../index';
 | 
					import { makeValue, ThemeContext, DataLinkBuiltInVars } from '../../index';
 | 
				
			||||||
import { SelectionReference } from './SelectionReference';
 | 
					import { SelectionReference } from './SelectionReference';
 | 
				
			||||||
import { Portal } from '../index';
 | 
					import { Portal } from '../index';
 | 
				
			||||||
// @ts-ignore
 | 
					// @ts-ignore
 | 
				
			||||||
| 
						 | 
					@ -77,10 +77,10 @@ export const DataLinkInput: React.FC<DataLinkInputProps> = ({ value, onChange, s
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useDebounce(updateUsedSuggestions, 500, [linkUrl]);
 | 
					  useDebounce(updateUsedSuggestions, 250, [linkUrl]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onKeyDown = (event: KeyboardEvent) => {
 | 
					  const onKeyDown = (event: KeyboardEvent) => {
 | 
				
			||||||
    if (event.key === 'Backspace') {
 | 
					    if (event.key === 'Backspace' || event.key === 'Escape') {
 | 
				
			||||||
      setShowingSuggestions(false);
 | 
					      setShowingSuggestions(false);
 | 
				
			||||||
      setSuggestionsIndex(0);
 | 
					      setSuggestionsIndex(0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -111,7 +111,7 @@ export const DataLinkInput: React.FC<DataLinkInputProps> = ({ value, onChange, s
 | 
				
			||||||
      setShowingSuggestions(true);
 | 
					      setShowingSuggestions(true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (event.key === 'Enter') {
 | 
					    if (event.key === 'Enter' && showingSuggestions) {
 | 
				
			||||||
      // Preventing entering a new line
 | 
					      // Preventing entering a new line
 | 
				
			||||||
      // As of https://github.com/ianstormtaylor/slate/issues/1345#issuecomment-340508289
 | 
					      // As of https://github.com/ianstormtaylor/slate/issues/1345#issuecomment-340508289
 | 
				
			||||||
      return false;
 | 
					      return false;
 | 
				
			||||||
| 
						 | 
					@ -134,7 +134,7 @@ export const DataLinkInput: React.FC<DataLinkInputProps> = ({ value, onChange, s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const change = linkUrl.change();
 | 
					    const change = linkUrl.change();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (item.origin === VariableOrigin.BuiltIn) {
 | 
					    if (item.origin !== VariableOrigin.Template || item.value === DataLinkBuiltInVars.includeVars) {
 | 
				
			||||||
      change.insertText(`${includeDollarSign ? '$' : ''}\{${item.value}}`);
 | 
					      change.insertText(`${includeDollarSign ? '$' : ''}\{${item.value}}`);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      change.insertText(`var-${item.value}=$\{${item.value}}`);
 | 
					      change.insertText(`var-${item.value}=$\{${item.value}}`);
 | 
				
			||||||
| 
						 | 
					@ -167,7 +167,7 @@ export const DataLinkInput: React.FC<DataLinkInputProps> = ({ value, onChange, s
 | 
				
			||||||
              modifiers={{
 | 
					              modifiers={{
 | 
				
			||||||
                preventOverflow: { enabled: true, boundariesElement: 'window' },
 | 
					                preventOverflow: { enabled: true, boundariesElement: 'window' },
 | 
				
			||||||
                arrow: { enabled: false },
 | 
					                arrow: { enabled: false },
 | 
				
			||||||
                offset: { offset: 200 }, // width of the suggestions menu
 | 
					                offset: { offset: 250 }, // width of the suggestions menu
 | 
				
			||||||
              }}
 | 
					              }}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              {({ ref, style, placement }) => {
 | 
					              {({ ref, style, placement }) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,22 @@
 | 
				
			||||||
import { GrafanaTheme, selectThemeVariant, ThemeContext } from '../../index';
 | 
					import { GrafanaTheme, selectThemeVariant, ThemeContext } from '../../index';
 | 
				
			||||||
import { css, cx } from 'emotion';
 | 
					import { css, cx } from 'emotion';
 | 
				
			||||||
 | 
					import _ from 'lodash';
 | 
				
			||||||
import React, { useRef, useContext, useMemo } from 'react';
 | 
					import React, { useRef, useContext, useMemo } from 'react';
 | 
				
			||||||
import useClickAway from 'react-use/lib/useClickAway';
 | 
					import useClickAway from 'react-use/lib/useClickAway';
 | 
				
			||||||
import { List } from '../index';
 | 
					import { List } from '../index';
 | 
				
			||||||
 | 
					import tinycolor from 'tinycolor2';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum VariableOrigin {
 | 
					export enum VariableOrigin {
 | 
				
			||||||
  BuiltIn = 'builtin',
 | 
					  Series = 'series',
 | 
				
			||||||
 | 
					  Field = 'field',
 | 
				
			||||||
 | 
					  Value = 'value',
 | 
				
			||||||
 | 
					  BuiltIn = 'built-in',
 | 
				
			||||||
  Template = 'template',
 | 
					  Template = 'template',
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface VariableSuggestion {
 | 
					export interface VariableSuggestion {
 | 
				
			||||||
  value: string;
 | 
					  value: string;
 | 
				
			||||||
 | 
					  label: string;
 | 
				
			||||||
  documentation?: string;
 | 
					  documentation?: string;
 | 
				
			||||||
  origin: VariableOrigin;
 | 
					  origin: VariableOrigin;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -71,16 +77,34 @@ const getStyles = (theme: GrafanaTheme) => {
 | 
				
			||||||
    theme.type
 | 
					    theme.type
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const separatorColor = selectThemeVariant(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      light: tinycolor(wrapperBg.toString())
 | 
				
			||||||
 | 
					        .darken(10)
 | 
				
			||||||
 | 
					        .toString(),
 | 
				
			||||||
 | 
					      dark: tinycolor(wrapperBg.toString())
 | 
				
			||||||
 | 
					        .lighten(10)
 | 
				
			||||||
 | 
					        .toString(),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    theme.type
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
 | 
					    list: css`
 | 
				
			||||||
 | 
					      border-bottom: 1px solid ${separatorColor};
 | 
				
			||||||
 | 
					      &:last-child {
 | 
				
			||||||
 | 
					        border: none;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    `,
 | 
				
			||||||
    wrapper: css`
 | 
					    wrapper: css`
 | 
				
			||||||
      background: ${wrapperBg};
 | 
					      background: ${wrapperBg};
 | 
				
			||||||
      z-index: 1;
 | 
					      z-index: 1;
 | 
				
			||||||
      width: 200px;
 | 
					      width: 250px;
 | 
				
			||||||
      box-shadow: 0 5px 10px 0 ${wrapperShadow};
 | 
					      box-shadow: 0 5px 10px 0 ${wrapperShadow};
 | 
				
			||||||
    `,
 | 
					    `,
 | 
				
			||||||
    item: css`
 | 
					    item: css`
 | 
				
			||||||
      background: none;
 | 
					      background: none;
 | 
				
			||||||
      padding: 4px 8px;
 | 
					      padding: 2px 8px;
 | 
				
			||||||
      color: ${itemColor};
 | 
					      color: ${itemColor};
 | 
				
			||||||
      cursor: pointer;
 | 
					      cursor: pointer;
 | 
				
			||||||
      &:hover {
 | 
					      &:hover {
 | 
				
			||||||
| 
						 | 
					@ -89,9 +113,6 @@ const getStyles = (theme: GrafanaTheme) => {
 | 
				
			||||||
    `,
 | 
					    `,
 | 
				
			||||||
    label: css`
 | 
					    label: css`
 | 
				
			||||||
      color: ${theme.colors.textWeak};
 | 
					      color: ${theme.colors.textWeak};
 | 
				
			||||||
      font-size: ${theme.typography.size.sm};
 | 
					 | 
				
			||||||
      line-height: ${theme.typography.lineHeight.lg};
 | 
					 | 
				
			||||||
      padding: ${theme.spacing.sm};
 | 
					 | 
				
			||||||
    `,
 | 
					    `,
 | 
				
			||||||
    activeItem: css`
 | 
					    activeItem: css`
 | 
				
			||||||
      background: ${itemBgActive};
 | 
					      background: ${itemBgActive};
 | 
				
			||||||
| 
						 | 
					@ -101,11 +122,11 @@ const getStyles = (theme: GrafanaTheme) => {
 | 
				
			||||||
    `,
 | 
					    `,
 | 
				
			||||||
    itemValue: css`
 | 
					    itemValue: css`
 | 
				
			||||||
      font-family: ${theme.typography.fontFamily.monospace};
 | 
					      font-family: ${theme.typography.fontFamily.monospace};
 | 
				
			||||||
 | 
					      font-size: ${theme.typography.size.sm};
 | 
				
			||||||
    `,
 | 
					    `,
 | 
				
			||||||
    itemDocs: css`
 | 
					    itemDocs: css`
 | 
				
			||||||
      margin-top: ${theme.spacing.xs};
 | 
					      margin-top: ${theme.spacing.xs};
 | 
				
			||||||
      color: ${itemDocsColor};
 | 
					      color: ${itemDocsColor};
 | 
				
			||||||
      font-size: ${theme.typography.size.sm};
 | 
					 | 
				
			||||||
    `,
 | 
					    `,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -119,34 +140,35 @@ export const DataLinkSuggestions: React.FC<DataLinkSuggestionsProps> = ({ sugges
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const templateSuggestions = useMemo(() => {
 | 
					  const groupedSuggestions = useMemo(() => {
 | 
				
			||||||
    return suggestions.filter(suggestion => suggestion.origin === VariableOrigin.Template);
 | 
					    return _.groupBy(suggestions, s => s.origin);
 | 
				
			||||||
  }, [suggestions]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const builtInSuggestions = useMemo(() => {
 | 
					 | 
				
			||||||
    return suggestions.filter(suggestion => suggestion.origin === VariableOrigin.BuiltIn);
 | 
					 | 
				
			||||||
  }, [suggestions]);
 | 
					  }, [suggestions]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const styles = getStyles(theme);
 | 
					  const styles = getStyles(theme);
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div ref={ref} className={styles.wrapper}>
 | 
					    <div ref={ref} className={styles.wrapper}>
 | 
				
			||||||
      {templateSuggestions.length > 0 && (
 | 
					      {Object.keys(groupedSuggestions).map((key, i) => {
 | 
				
			||||||
 | 
					        const indexOffset =
 | 
				
			||||||
 | 
					          i === 0
 | 
				
			||||||
 | 
					            ? 0
 | 
				
			||||||
 | 
					            : Object.keys(groupedSuggestions).reduce((acc, current, index) => {
 | 
				
			||||||
 | 
					                if (index >= i) {
 | 
				
			||||||
 | 
					                  return acc;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                return acc + groupedSuggestions[current].length;
 | 
				
			||||||
 | 
					              }, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
          <DataLinkSuggestionsList
 | 
					          <DataLinkSuggestionsList
 | 
				
			||||||
            {...otherProps}
 | 
					            {...otherProps}
 | 
				
			||||||
          suggestions={templateSuggestions}
 | 
					            suggestions={groupedSuggestions[key]}
 | 
				
			||||||
          label="Template variables"
 | 
					            label={`${_.capitalize(key)}`}
 | 
				
			||||||
            activeIndex={otherProps.activeIndex}
 | 
					            activeIndex={otherProps.activeIndex}
 | 
				
			||||||
          activeIndexOffset={0}
 | 
					            activeIndexOffset={indexOffset}
 | 
				
			||||||
 | 
					            key={key}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
      )}
 | 
					        );
 | 
				
			||||||
      {builtInSuggestions.length > 0 && (
 | 
					      })}
 | 
				
			||||||
        <DataLinkSuggestionsList
 | 
					 | 
				
			||||||
          {...otherProps}
 | 
					 | 
				
			||||||
          suggestions={builtInSuggestions}
 | 
					 | 
				
			||||||
          label="Built-in variables"
 | 
					 | 
				
			||||||
          activeIndexOffset={templateSuggestions.length}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -165,8 +187,8 @@ const DataLinkSuggestionsList: React.FC<DataLinkSuggestionsListProps> = React.me
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <>
 | 
					      <>
 | 
				
			||||||
        <div className={styles.label}>{label}</div>
 | 
					 | 
				
			||||||
        <List
 | 
					        <List
 | 
				
			||||||
 | 
					          className={styles.list}
 | 
				
			||||||
          items={suggestions}
 | 
					          items={suggestions}
 | 
				
			||||||
          renderItem={(item, index) => {
 | 
					          renderItem={(item, index) => {
 | 
				
			||||||
            return (
 | 
					            return (
 | 
				
			||||||
| 
						 | 
					@ -175,9 +197,11 @@ const DataLinkSuggestionsList: React.FC<DataLinkSuggestionsListProps> = React.me
 | 
				
			||||||
                onClick={() => {
 | 
					                onClick={() => {
 | 
				
			||||||
                  onSuggestionSelect(item);
 | 
					                  onSuggestionSelect(item);
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
 | 
					                title={item.documentation}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <div className={styles.itemValue}>{item.value}</div>
 | 
					                <span className={styles.itemValue}>
 | 
				
			||||||
                {item.documentation && <div className={styles.itemDocs}>{item.documentation}</div>}
 | 
					                  <span className={styles.label}>{label}</span> {item.label}
 | 
				
			||||||
 | 
					                </span>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ interface DataLinksEditorProps {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Prism.languages['links'] = {
 | 
					Prism.languages['links'] = {
 | 
				
			||||||
  builtInVariable: {
 | 
					  builtInVariable: {
 | 
				
			||||||
    pattern: /(\${\w+})/,
 | 
					    pattern: /(\${\S+?})/,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,6 +57,7 @@ export const DataLinksEditor: FC<DataLinksEditorProps> = React.memo(({ value, on
 | 
				
			||||||
            <DataLinkEditor
 | 
					            <DataLinkEditor
 | 
				
			||||||
              key={index.toString()}
 | 
					              key={index.toString()}
 | 
				
			||||||
              index={index}
 | 
					              index={index}
 | 
				
			||||||
 | 
					              isLast={index === value.length - 1}
 | 
				
			||||||
              value={link}
 | 
					              value={link}
 | 
				
			||||||
              onChange={onLinkChanged}
 | 
					              onChange={onLinkChanged}
 | 
				
			||||||
              onRemove={onRemove}
 | 
					              onRemove={onRemove}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ import React, { InputHTMLAttributes, FunctionComponent } from 'react';
 | 
				
			||||||
import { FormLabel } from '../FormLabel/FormLabel';
 | 
					import { FormLabel } from '../FormLabel/FormLabel';
 | 
				
			||||||
import { PopoverContent } from '../Tooltip/Tooltip';
 | 
					import { PopoverContent } from '../Tooltip/Tooltip';
 | 
				
			||||||
import { cx } from 'emotion';
 | 
					import { cx } from 'emotion';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Props extends InputHTMLAttributes<HTMLInputElement> {
 | 
					export interface Props extends InputHTMLAttributes<HTMLInputElement> {
 | 
				
			||||||
  label: string;
 | 
					  label: string;
 | 
				
			||||||
  tooltip?: PopoverContent;
 | 
					  tooltip?: PopoverContent;
 | 
				
			||||||
| 
						 | 
					@ -33,7 +34,9 @@ export const FormField: FunctionComponent<Props> = ({
 | 
				
			||||||
      <FormLabel width={labelWidth} tooltip={tooltip}>
 | 
					      <FormLabel width={labelWidth} tooltip={tooltip}>
 | 
				
			||||||
        {label}
 | 
					        {label}
 | 
				
			||||||
      </FormLabel>
 | 
					      </FormLabel>
 | 
				
			||||||
      {inputEl || <input type="text" className={`gf-form-input width-${inputWidth}`} {...inputProps} />}
 | 
					      {inputEl || (
 | 
				
			||||||
 | 
					        <input type="text" className={`gf-form-input ${inputWidth ? `width-${inputWidth}` : ''}`} {...inputProps} />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,10 +36,10 @@ export class AbstractList<T> extends React.PureComponent<AbstractListProps<T>> {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render() {
 | 
					  render() {
 | 
				
			||||||
    const { items, renderItem, getItemKey } = this.props;
 | 
					    const { items, renderItem, getItemKey, className } = this.props;
 | 
				
			||||||
    const styles = this.getListStyles();
 | 
					    const styles = this.getListStyles();
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <ul className={styles.list}>
 | 
					      <ul className={cx(styles.list, className)}>
 | 
				
			||||||
        {items.map((item, i) => {
 | 
					        {items.map((item, i) => {
 | 
				
			||||||
          return (
 | 
					          return (
 | 
				
			||||||
            <li className={styles.item} key={getItemKey ? getItemKey(item) : i}>
 | 
					            <li className={styles.item} key={getItemKey ? getItemKey(item) : i}>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,9 +70,9 @@ export const FieldPropertiesEditor: React.FC<Props> = ({ value, onChange, showMi
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
      Template Variables:
 | 
					      Template Variables:
 | 
				
			||||||
      <br />
 | 
					      <br />
 | 
				
			||||||
      {'$' + VAR_SERIES_NAME}
 | 
					      {'${' + VAR_SERIES_NAME + '}'}
 | 
				
			||||||
      <br />
 | 
					      <br />
 | 
				
			||||||
      {'$' + VAR_FIELD_NAME}
 | 
					      {'${' + VAR_FIELD_NAME + '}'}
 | 
				
			||||||
      <br />
 | 
					      <br />
 | 
				
			||||||
      {'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC}
 | 
					      {'$' + VAR_CELL_PREFIX + '{N}'} / {'$' + VAR_CALC}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,7 @@ export interface PanelProps<T = any> {
 | 
				
			||||||
export interface PanelEditorProps<T = any> {
 | 
					export interface PanelEditorProps<T = any> {
 | 
				
			||||||
  options: T;
 | 
					  options: T;
 | 
				
			||||||
  onOptionsChange: (options: T) => void;
 | 
					  onOptionsChange: (options: T) => void;
 | 
				
			||||||
 | 
					  data: PanelData;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface PanelModel<TOptions = any> {
 | 
					export interface PanelModel<TOptions = any> {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,17 @@ import { LinkModelSupplier } from '@grafana/data';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const DataLinkBuiltInVars = {
 | 
					export const DataLinkBuiltInVars = {
 | 
				
			||||||
  keepTime: '__url_time_range',
 | 
					  keepTime: '__url_time_range',
 | 
				
			||||||
 | 
					  timeRangeFrom: '__from',
 | 
				
			||||||
 | 
					  timeRangeTo: '__to',
 | 
				
			||||||
  includeVars: '__all_variables',
 | 
					  includeVars: '__all_variables',
 | 
				
			||||||
  seriesName: '__series_name',
 | 
					  seriesName: '__series.name',
 | 
				
			||||||
  valueTime: '__value_time',
 | 
					  fieldName: '__field.name',
 | 
				
			||||||
 | 
					  valueTime: '__value.time',
 | 
				
			||||||
 | 
					  valueNumeric: '__value.numeric',
 | 
				
			||||||
 | 
					  valueText: '__value.text',
 | 
				
			||||||
 | 
					  valueRaw: '__value.raw',
 | 
				
			||||||
 | 
					  // name of the calculation represented by the value
 | 
				
			||||||
 | 
					  valueCalc: '__value.calc',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,10 +18,10 @@ describe('Process simple display values', () => {
 | 
				
			||||||
    getDisplayProcessor(),
 | 
					    getDisplayProcessor(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Add a simple option that is not used (uses a different base class)
 | 
					    // Add a simple option that is not used (uses a different base class)
 | 
				
			||||||
    getDisplayProcessor({ field: { min: 0, max: 100 } }),
 | 
					    getDisplayProcessor({ config: { min: 0, max: 100 } }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Add a simple option that is not used (uses a different base class)
 | 
					    // Add a simple option that is not used (uses a different base class)
 | 
				
			||||||
    getDisplayProcessor({ field: { unit: 'locale' } }),
 | 
					    getDisplayProcessor({ config: { unit: 'locale' } }),
 | 
				
			||||||
  ];
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('support null', () => {
 | 
					  it('support null', () => {
 | 
				
			||||||
| 
						 | 
					@ -102,7 +102,7 @@ describe('Format value', () => {
 | 
				
			||||||
  it('should return if value isNaN', () => {
 | 
					  it('should return if value isNaN', () => {
 | 
				
			||||||
    const valueMappings: ValueMapping[] = [];
 | 
					    const valueMappings: ValueMapping[] = [];
 | 
				
			||||||
    const value = 'N/A';
 | 
					    const value = 'N/A';
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { mappings: valueMappings } });
 | 
					    const instance = getDisplayProcessor({ config: { mappings: valueMappings } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const result = instance(value);
 | 
					    const result = instance(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -113,7 +113,7 @@ describe('Format value', () => {
 | 
				
			||||||
    const valueMappings: ValueMapping[] = [];
 | 
					    const valueMappings: ValueMapping[] = [];
 | 
				
			||||||
    const value = '6';
 | 
					    const value = '6';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: 1, mappings: valueMappings } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: 1, mappings: valueMappings } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const result = instance(value);
 | 
					    const result = instance(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,7 +126,7 @@ describe('Format value', () => {
 | 
				
			||||||
      { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
 | 
					      { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
    const value = '10';
 | 
					    const value = '10';
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: 1, mappings: valueMappings } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: 1, mappings: valueMappings } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const result = instance(value);
 | 
					    const result = instance(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -135,20 +135,20 @@ describe('Format value', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should set auto decimals, 1 significant', () => {
 | 
					  it('should set auto decimals, 1 significant', () => {
 | 
				
			||||||
    const value = 3.23;
 | 
					    const value = 3.23;
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: null } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: null } });
 | 
				
			||||||
    expect(instance(value).text).toEqual('3.2');
 | 
					    expect(instance(value).text).toEqual('3.2');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should set auto decimals, 2 significant', () => {
 | 
					  it('should set auto decimals, 2 significant', () => {
 | 
				
			||||||
    const value = 0.0245;
 | 
					    const value = 0.0245;
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: null } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: null } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(instance(value).text).toEqual('0.025');
 | 
					    expect(instance(value).text).toEqual('0.025');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('should use override decimals', () => {
 | 
					  it('should use override decimals', () => {
 | 
				
			||||||
    const value = 100030303;
 | 
					    const value = 100030303;
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: 2, unit: 'bytes' } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: 2, unit: 'bytes' } });
 | 
				
			||||||
    expect(instance(value).text).toEqual('95.40 MiB');
 | 
					    expect(instance(value).text).toEqual('95.40 MiB');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -158,7 +158,7 @@ describe('Format value', () => {
 | 
				
			||||||
      { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
 | 
					      { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
    const value = '11';
 | 
					    const value = '11';
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: 1, mappings: valueMappings } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: 1, mappings: valueMappings } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    expect(instance(value).text).toEqual('1-20');
 | 
					    expect(instance(value).text).toEqual('1-20');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					@ -169,25 +169,25 @@ describe('Format value', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('with value 1000 and unit short', () => {
 | 
					  it('with value 1000 and unit short', () => {
 | 
				
			||||||
    const value = 1000;
 | 
					    const value = 1000;
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: null, unit: 'short' } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: null, unit: 'short' } });
 | 
				
			||||||
    expect(instance(value).text).toEqual('1.000 K');
 | 
					    expect(instance(value).text).toEqual('1.000 K');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('with value 1200 and unit short', () => {
 | 
					  it('with value 1200 and unit short', () => {
 | 
				
			||||||
    const value = 1200;
 | 
					    const value = 1200;
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: null, unit: 'short' } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: null, unit: 'short' } });
 | 
				
			||||||
    expect(instance(value).text).toEqual('1.200 K');
 | 
					    expect(instance(value).text).toEqual('1.200 K');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('with value 1250 and unit short', () => {
 | 
					  it('with value 1250 and unit short', () => {
 | 
				
			||||||
    const value = 1250;
 | 
					    const value = 1250;
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: null, unit: 'short' } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: null, unit: 'short' } });
 | 
				
			||||||
    expect(instance(value).text).toEqual('1.250 K');
 | 
					    expect(instance(value).text).toEqual('1.250 K');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('with value 10000000 and unit short', () => {
 | 
					  it('with value 10000000 and unit short', () => {
 | 
				
			||||||
    const value = 1000000;
 | 
					    const value = 1000000;
 | 
				
			||||||
    const instance = getDisplayProcessor({ field: { decimals: null, unit: 'short' } });
 | 
					    const instance = getDisplayProcessor({ config: { decimals: null, unit: 'short' } });
 | 
				
			||||||
    expect(instance(value).text).toEqual('1.000 Mil');
 | 
					    expect(instance(value).text).toEqual('1.000 Mil');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ import { getColorFromHexRgbOrName } from './namedColorsPalette';
 | 
				
			||||||
import { GrafanaTheme, GrafanaThemeType } from '../types/index';
 | 
					import { GrafanaTheme, GrafanaThemeType } from '../types/index';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface DisplayProcessorOptions {
 | 
					interface DisplayProcessorOptions {
 | 
				
			||||||
  field?: FieldConfig;
 | 
					  config?: FieldConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Context
 | 
					  // Context
 | 
				
			||||||
  isUtc?: boolean;
 | 
					  isUtc?: boolean;
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ interface DisplayProcessorOptions {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayProcessor {
 | 
					export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayProcessor {
 | 
				
			||||||
  if (options && !_.isEmpty(options)) {
 | 
					  if (options && !_.isEmpty(options)) {
 | 
				
			||||||
    const field = options.field ? options.field : {};
 | 
					    const field = options.config ? options.config : {};
 | 
				
			||||||
    const formatFunc = getValueFormat(field.unit || 'none');
 | 
					    const formatFunc = getValueFormat(field.unit || 'none');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (value: any) => {
 | 
					    return (value: any) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,6 @@ import toString from 'lodash/toString';
 | 
				
			||||||
import { GrafanaTheme, InterpolateFunction } from '../types/index';
 | 
					import { GrafanaTheme, InterpolateFunction } from '../types/index';
 | 
				
			||||||
import { getDisplayProcessor } from './displayProcessor';
 | 
					import { getDisplayProcessor } from './displayProcessor';
 | 
				
			||||||
import { getFlotPairs } from './flotPairs';
 | 
					import { getFlotPairs } from './flotPairs';
 | 
				
			||||||
import { DataLinkBuiltInVars } from '../utils/dataLinks';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface FieldDisplayOptions {
 | 
					export interface FieldDisplayOptions {
 | 
				
			||||||
  values?: boolean; // If true show each row value
 | 
					  values?: boolean; // If true show each row value
 | 
				
			||||||
| 
						 | 
					@ -28,8 +27,8 @@ export interface FieldDisplayOptions {
 | 
				
			||||||
  override: FieldConfig; // Set these values regardless of the source
 | 
					  override: FieldConfig; // Set these values regardless of the source
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
// TODO: use built in variables, same as for data links?
 | 
					// TODO: use built in variables, same as for data links?
 | 
				
			||||||
export const VAR_SERIES_NAME = '__series_name';
 | 
					export const VAR_SERIES_NAME = '__series.name';
 | 
				
			||||||
export const VAR_FIELD_NAME = '__field_name';
 | 
					export const VAR_FIELD_NAME = '__field.name';
 | 
				
			||||||
export const VAR_CALC = '__calc';
 | 
					export const VAR_CALC = '__calc';
 | 
				
			||||||
export const VAR_CELL_PREFIX = '__cell_'; // consistent with existing table templates
 | 
					export const VAR_CELL_PREFIX = '__cell_'; // consistent with existing table templates
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,7 +53,7 @@ function getTitleTemplate(title: string | undefined, stats: string[], data?: Dat
 | 
				
			||||||
    parts.push('$' + VAR_CALC);
 | 
					    parts.push('$' + VAR_CALC);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (data.length > 1) {
 | 
					  if (data.length > 1) {
 | 
				
			||||||
    parts.push('$' + VAR_SERIES_NAME);
 | 
					    parts.push('${' + VAR_SERIES_NAME + '}');
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (fieldCount > 1 || !parts.length) {
 | 
					  if (fieldCount > 1 || !parts.length) {
 | 
				
			||||||
    parts.push('$' + VAR_FIELD_NAME);
 | 
					    parts.push('$' + VAR_FIELD_NAME);
 | 
				
			||||||
| 
						 | 
					@ -70,8 +69,8 @@ export interface FieldDisplay {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Expose to the original values for delayed inspection (DataLinks etc)
 | 
					  // Expose to the original values for delayed inspection (DataLinks etc)
 | 
				
			||||||
  view?: DataFrameView;
 | 
					  view?: DataFrameView;
 | 
				
			||||||
  column?: number; // The field column index
 | 
					  colIndex?: number; // The field column index
 | 
				
			||||||
  row?: number; // only filled in when the value is from a row (ie, not a reduction)
 | 
					  rowIndex?: number; // only filled in when the value is from a row (ie, not a reduction)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface GetFieldDisplayValuesOptions {
 | 
					export interface GetFieldDisplayValuesOptions {
 | 
				
			||||||
| 
						 | 
					@ -106,7 +105,7 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      scopedVars[DataLinkBuiltInVars.seriesName] = { text: 'Series', value: series.name };
 | 
					      scopedVars['__series'] = { text: 'Series', value: { name: series.name } };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const { timeField } = getTimeField(series);
 | 
					      const { timeField } = getTimeField(series);
 | 
				
			||||||
      const view = new DataFrameView(series);
 | 
					      const view = new DataFrameView(series);
 | 
				
			||||||
| 
						 | 
					@ -125,15 +124,14 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
 | 
				
			||||||
          name = `Field[${s}]`;
 | 
					          name = `Field[${s}]`;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        scopedVars[VAR_FIELD_NAME] = { text: 'Field', value: name };
 | 
					        scopedVars['__field'] = { text: 'Field', value: { name } };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const display = getDisplayProcessor({
 | 
					        const display = getDisplayProcessor({
 | 
				
			||||||
          field: config,
 | 
					          config,
 | 
				
			||||||
          theme: options.theme,
 | 
					          theme: options.theme,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const title = config.title ? config.title : defaultTitle;
 | 
					        const title = config.title ? config.title : defaultTitle;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Show all rows
 | 
					        // Show all rows
 | 
				
			||||||
        if (fieldOptions.values) {
 | 
					        if (fieldOptions.values) {
 | 
				
			||||||
          const usesCellValues = title.indexOf(VAR_CELL_PREFIX) >= 0;
 | 
					          const usesCellValues = title.indexOf(VAR_CELL_PREFIX) >= 0;
 | 
				
			||||||
| 
						 | 
					@ -158,8 +156,8 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
 | 
				
			||||||
              field: config,
 | 
					              field: config,
 | 
				
			||||||
              display: displayValue,
 | 
					              display: displayValue,
 | 
				
			||||||
              view,
 | 
					              view,
 | 
				
			||||||
              column: i,
 | 
					              colIndex: i,
 | 
				
			||||||
              row: j,
 | 
					              rowIndex: j,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (values.length >= limit) {
 | 
					            if (values.length >= limit) {
 | 
				
			||||||
| 
						 | 
					@ -187,12 +185,12 @@ export const getFieldDisplayValues = (options: GetFieldDisplayValuesOptions): Fi
 | 
				
			||||||
            const displayValue = display(results[calc]);
 | 
					            const displayValue = display(results[calc]);
 | 
				
			||||||
            displayValue.title = replaceVariables(title, scopedVars);
 | 
					            displayValue.title = replaceVariables(title, scopedVars);
 | 
				
			||||||
            values.push({
 | 
					            values.push({
 | 
				
			||||||
              name,
 | 
					              name: calc,
 | 
				
			||||||
              field: config,
 | 
					              field: config,
 | 
				
			||||||
              display: displayValue,
 | 
					              display: displayValue,
 | 
				
			||||||
              sparkline,
 | 
					              sparkline,
 | 
				
			||||||
              view,
 | 
					              view,
 | 
				
			||||||
              column: i,
 | 
					              colIndex: i,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -246,7 +246,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel {
 | 
				
			||||||
      hasUniqueLabels = true;
 | 
					      hasUniqueLabels = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const timeFieldIndex = fieldCache.getFirstFieldOfType(FieldType.time);
 | 
					    const timeField = fieldCache.getFirstFieldOfType(FieldType.time);
 | 
				
			||||||
    const stringField = fieldCache.getFirstFieldOfType(FieldType.string);
 | 
					    const stringField = fieldCache.getFirstFieldOfType(FieldType.string);
 | 
				
			||||||
    const logLevelField = fieldCache.getFieldByName('level');
 | 
					    const logLevelField = fieldCache.getFieldByName('level');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -256,7 +256,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (let j = 0; j < series.length; j++) {
 | 
					    for (let j = 0; j < series.length; j++) {
 | 
				
			||||||
      const ts = timeFieldIndex.values.get(j);
 | 
					      const ts = timeField.values.get(j);
 | 
				
			||||||
      const time = dateTime(ts);
 | 
					      const time = dateTime(ts);
 | 
				
			||||||
      const timeEpochMs = time.valueOf();
 | 
					      const timeEpochMs = time.valueOf();
 | 
				
			||||||
      const timeFromNow = time.fromNow();
 | 
					      const timeFromNow = time.fromNow();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ export class KeybindingSrv {
 | 
				
			||||||
      this.bind('g p', this.goToProfile);
 | 
					      this.bind('g p', this.goToProfile);
 | 
				
			||||||
      this.bind('s o', this.openSearch);
 | 
					      this.bind('s o', this.openSearch);
 | 
				
			||||||
      this.bind('f', this.openSearch);
 | 
					      this.bind('f', this.openSearch);
 | 
				
			||||||
      this.bindGlobal('esc', this.exit);
 | 
					      this.bind('esc', this.exit);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,7 +78,7 @@ exports[`DashboardPage Dashboard init completed  Should render dashboard grid 1`
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "refresh": undefined,
 | 
					        "refresh": undefined,
 | 
				
			||||||
        "revision": undefined,
 | 
					        "revision": undefined,
 | 
				
			||||||
        "schemaVersion": 19,
 | 
					        "schemaVersion": 20,
 | 
				
			||||||
        "snapshot": undefined,
 | 
					        "snapshot": undefined,
 | 
				
			||||||
        "style": "dark",
 | 
					        "style": "dark",
 | 
				
			||||||
        "tags": Array [],
 | 
					        "tags": Array [],
 | 
				
			||||||
| 
						 | 
					@ -191,7 +191,7 @@ exports[`DashboardPage Dashboard init completed  Should render dashboard grid 1`
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
              "refresh": undefined,
 | 
					              "refresh": undefined,
 | 
				
			||||||
              "revision": undefined,
 | 
					              "revision": undefined,
 | 
				
			||||||
              "schemaVersion": 19,
 | 
					              "schemaVersion": 20,
 | 
				
			||||||
              "snapshot": undefined,
 | 
					              "snapshot": undefined,
 | 
				
			||||||
              "style": "dark",
 | 
					              "style": "dark",
 | 
				
			||||||
              "tags": Array [],
 | 
					              "tags": Array [],
 | 
				
			||||||
| 
						 | 
					@ -315,7 +315,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "refresh": undefined,
 | 
					        "refresh": undefined,
 | 
				
			||||||
        "revision": undefined,
 | 
					        "revision": undefined,
 | 
				
			||||||
        "schemaVersion": 19,
 | 
					        "schemaVersion": 20,
 | 
				
			||||||
        "snapshot": undefined,
 | 
					        "snapshot": undefined,
 | 
				
			||||||
        "style": "dark",
 | 
					        "style": "dark",
 | 
				
			||||||
        "tags": Array [],
 | 
					        "tags": Array [],
 | 
				
			||||||
| 
						 | 
					@ -426,7 +426,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "refresh": undefined,
 | 
					            "refresh": undefined,
 | 
				
			||||||
            "revision": undefined,
 | 
					            "revision": undefined,
 | 
				
			||||||
            "schemaVersion": 19,
 | 
					            "schemaVersion": 20,
 | 
				
			||||||
            "snapshot": undefined,
 | 
					            "snapshot": undefined,
 | 
				
			||||||
            "style": "dark",
 | 
					            "style": "dark",
 | 
				
			||||||
            "tags": Array [],
 | 
					            "tags": Array [],
 | 
				
			||||||
| 
						 | 
					@ -521,7 +521,7 @@ exports[`DashboardPage When dashboard has editview url state should render setti
 | 
				
			||||||
              ],
 | 
					              ],
 | 
				
			||||||
              "refresh": undefined,
 | 
					              "refresh": undefined,
 | 
				
			||||||
              "revision": undefined,
 | 
					              "revision": undefined,
 | 
				
			||||||
              "schemaVersion": 19,
 | 
					              "schemaVersion": 20,
 | 
				
			||||||
              "snapshot": undefined,
 | 
					              "snapshot": undefined,
 | 
				
			||||||
              "style": "dark",
 | 
					              "style": "dark",
 | 
				
			||||||
              "tags": Array [],
 | 
					              "tags": Array [],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -232,7 +232,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
          "refresh": undefined,
 | 
					          "refresh": undefined,
 | 
				
			||||||
          "revision": undefined,
 | 
					          "revision": undefined,
 | 
				
			||||||
          "schemaVersion": 19,
 | 
					          "schemaVersion": 20,
 | 
				
			||||||
          "snapshot": undefined,
 | 
					          "snapshot": undefined,
 | 
				
			||||||
          "style": "dark",
 | 
					          "style": "dark",
 | 
				
			||||||
          "tags": Array [],
 | 
					          "tags": Array [],
 | 
				
			||||||
| 
						 | 
					@ -469,7 +469,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
          "refresh": undefined,
 | 
					          "refresh": undefined,
 | 
				
			||||||
          "revision": undefined,
 | 
					          "revision": undefined,
 | 
				
			||||||
          "schemaVersion": 19,
 | 
					          "schemaVersion": 20,
 | 
				
			||||||
          "snapshot": undefined,
 | 
					          "snapshot": undefined,
 | 
				
			||||||
          "style": "dark",
 | 
					          "style": "dark",
 | 
				
			||||||
          "tags": Array [],
 | 
					          "tags": Array [],
 | 
				
			||||||
| 
						 | 
					@ -706,7 +706,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
          "refresh": undefined,
 | 
					          "refresh": undefined,
 | 
				
			||||||
          "revision": undefined,
 | 
					          "revision": undefined,
 | 
				
			||||||
          "schemaVersion": 19,
 | 
					          "schemaVersion": 20,
 | 
				
			||||||
          "snapshot": undefined,
 | 
					          "snapshot": undefined,
 | 
				
			||||||
          "style": "dark",
 | 
					          "style": "dark",
 | 
				
			||||||
          "tags": Array [],
 | 
					          "tags": Array [],
 | 
				
			||||||
| 
						 | 
					@ -943,7 +943,7 @@ exports[`DashboardGrid Can render dashboard grid Should render 1`] = `
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
          "refresh": undefined,
 | 
					          "refresh": undefined,
 | 
				
			||||||
          "revision": undefined,
 | 
					          "revision": undefined,
 | 
				
			||||||
          "schemaVersion": 19,
 | 
					          "schemaVersion": 20,
 | 
				
			||||||
          "snapshot": undefined,
 | 
					          "snapshot": undefined,
 | 
				
			||||||
          "style": "dark",
 | 
					          "style": "dark",
 | 
				
			||||||
          "tags": Array [],
 | 
					          "tags": Array [],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,8 +18,10 @@ import { PanelModel } from '../state';
 | 
				
			||||||
import { DashboardModel } from '../state';
 | 
					import { DashboardModel } from '../state';
 | 
				
			||||||
import { VizPickerSearch } from './VizPickerSearch';
 | 
					import { VizPickerSearch } from './VizPickerSearch';
 | 
				
			||||||
import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
 | 
					import PluginStateinfo from 'app/features/plugins/PluginStateInfo';
 | 
				
			||||||
import { PanelPlugin, PanelPluginMeta } from '@grafana/ui';
 | 
					import { PanelPlugin, PanelPluginMeta, PanelData } from '@grafana/ui';
 | 
				
			||||||
import { PanelCtrl } from 'app/plugins/sdk';
 | 
					import { PanelCtrl } from 'app/plugins/sdk';
 | 
				
			||||||
 | 
					import { Unsubscribable } from 'rxjs';
 | 
				
			||||||
 | 
					import { LoadingState } from '@grafana/data';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Props {
 | 
					interface Props {
 | 
				
			||||||
  panel: PanelModel;
 | 
					  panel: PanelModel;
 | 
				
			||||||
| 
						 | 
					@ -36,11 +38,13 @@ interface State {
 | 
				
			||||||
  searchQuery: string;
 | 
					  searchQuery: string;
 | 
				
			||||||
  scrollTop: number;
 | 
					  scrollTop: number;
 | 
				
			||||||
  hasBeenFocused: boolean;
 | 
					  hasBeenFocused: boolean;
 | 
				
			||||||
 | 
					  data: PanelData;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class VisualizationTab extends PureComponent<Props, State> {
 | 
					export class VisualizationTab extends PureComponent<Props, State> {
 | 
				
			||||||
  element: HTMLElement;
 | 
					  element: HTMLElement;
 | 
				
			||||||
  angularOptions: AngularComponent;
 | 
					  angularOptions: AngularComponent;
 | 
				
			||||||
 | 
					  querySubscription: Unsubscribable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(props: Props) {
 | 
					  constructor(props: Props) {
 | 
				
			||||||
    super(props);
 | 
					    super(props);
 | 
				
			||||||
| 
						 | 
					@ -50,6 +54,10 @@ export class VisualizationTab extends PureComponent<Props, State> {
 | 
				
			||||||
      hasBeenFocused: false,
 | 
					      hasBeenFocused: false,
 | 
				
			||||||
      searchQuery: '',
 | 
					      searchQuery: '',
 | 
				
			||||||
      scrollTop: 0,
 | 
					      scrollTop: 0,
 | 
				
			||||||
 | 
					      data: {
 | 
				
			||||||
 | 
					        state: LoadingState.NotStarted,
 | 
				
			||||||
 | 
					        series: [],
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,16 +74,28 @@ export class VisualizationTab extends PureComponent<Props, State> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (plugin.editor) {
 | 
					    if (plugin.editor) {
 | 
				
			||||||
      return <plugin.editor options={this.getReactPanelOptions()} onOptionsChange={this.onPanelOptionsChanged} />;
 | 
					      return (
 | 
				
			||||||
 | 
					        <plugin.editor
 | 
				
			||||||
 | 
					          data={this.state.data}
 | 
				
			||||||
 | 
					          options={this.getReactPanelOptions()}
 | 
				
			||||||
 | 
					          onOptionsChange={this.onPanelOptionsChanged}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return <p>Visualization has no options</p>;
 | 
					    return <p>Visualization has no options</p>;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentDidMount() {
 | 
					  componentDidMount() {
 | 
				
			||||||
 | 
					    const { panel } = this.props;
 | 
				
			||||||
 | 
					    const queryRunner = panel.getQueryRunner();
 | 
				
			||||||
    if (this.shouldLoadAngularOptions()) {
 | 
					    if (this.shouldLoadAngularOptions()) {
 | 
				
			||||||
      this.loadAngularOptions();
 | 
					      this.loadAngularOptions();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.querySubscription = queryRunner.getData().subscribe({
 | 
				
			||||||
 | 
					      next: (data: PanelData) => this.setState({ data }),
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentDidUpdate(prevProps: Props) {
 | 
					  componentDidUpdate(prevProps: Props) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -128,7 +128,7 @@ describe('DashboardModel', () => {
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('dashboard schema version should be set to latest', () => {
 | 
					    it('dashboard schema version should be set to latest', () => {
 | 
				
			||||||
      expect(model.schemaVersion).toBe(19);
 | 
					      expect(model.schemaVersion).toBe(20);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('graph thresholds should be migrated', () => {
 | 
					    it('graph thresholds should be migrated', () => {
 | 
				
			||||||
| 
						 | 
					@ -441,6 +441,71 @@ describe('DashboardModel', () => {
 | 
				
			||||||
      expect(model.panels[0].links[3].url).toBe(`/dashboard/db/my-other-dashboard`);
 | 
					      expect(model.panels[0].links[3].url).toBe(`/dashboard/db/my-other-dashboard`);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('when migrating variables', () => {
 | 
				
			||||||
 | 
					    let model: any;
 | 
				
			||||||
 | 
					    beforeEach(() => {
 | 
				
			||||||
 | 
					      model = new DashboardModel({
 | 
				
			||||||
 | 
					        panels: [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            //graph panel
 | 
				
			||||||
 | 
					            options: {
 | 
				
			||||||
 | 
					              dataLinks: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  url: 'http://mylink.com?series=${__series_name}',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                  url: 'http://mylink.com?series=${__value_time}',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            //  panel with field options
 | 
				
			||||||
 | 
					            options: {
 | 
				
			||||||
 | 
					              fieldOptions: {
 | 
				
			||||||
 | 
					                defaults: {
 | 
				
			||||||
 | 
					                  links: [
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                      url: 'http://mylink.com?series=${__series_name}',
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                      url: 'http://mylink.com?series=${__value_time}',
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                  title: '$__cell_0 * $__field_name * $__series_name',
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe('data links', () => {
 | 
				
			||||||
 | 
					      it('should replace __series_name variable with __series.name', () => {
 | 
				
			||||||
 | 
					        expect(model.panels[0].options.dataLinks[0].url).toBe('http://mylink.com?series=${__series.name}');
 | 
				
			||||||
 | 
					        expect(model.panels[1].options.fieldOptions.defaults.links[0].url).toBe(
 | 
				
			||||||
 | 
					          'http://mylink.com?series=${__series.name}'
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it('should replace __value_time variable with __value.time', () => {
 | 
				
			||||||
 | 
					        expect(model.panels[0].options.dataLinks[1].url).toBe('http://mylink.com?series=${__value.time}');
 | 
				
			||||||
 | 
					        expect(model.panels[1].options.fieldOptions.defaults.links[1].url).toBe(
 | 
				
			||||||
 | 
					          'http://mylink.com?series=${__value.time}'
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    describe('field display', () => {
 | 
				
			||||||
 | 
					      it('should replace __series_name and __field_name variables with new syntax', () => {
 | 
				
			||||||
 | 
					        expect(model.panels[1].options.fieldOptions.defaults.title).toBe(
 | 
				
			||||||
 | 
					          '$__cell_0 * ${__field.name} * ${__series.name}'
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createRow(options: any, panelDescriptions: any[]) {
 | 
					function createRow(options: any, panelDescriptions: any[]) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,7 @@ export class DashboardMigrator {
 | 
				
			||||||
    let i, j, k, n;
 | 
					    let i, j, k, n;
 | 
				
			||||||
    const oldVersion = this.dashboard.schemaVersion;
 | 
					    const oldVersion = this.dashboard.schemaVersion;
 | 
				
			||||||
    const panelUpgrades = [];
 | 
					    const panelUpgrades = [];
 | 
				
			||||||
    this.dashboard.schemaVersion = 19;
 | 
					    this.dashboard.schemaVersion = 20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (oldVersion === this.dashboard.schemaVersion) {
 | 
					    if (oldVersion === this.dashboard.schemaVersion) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
| 
						 | 
					@ -436,6 +436,33 @@ export class DashboardMigrator {
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (oldVersion < 20) {
 | 
				
			||||||
 | 
					      const updateLinks = (link: DataLink) => {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          ...link,
 | 
				
			||||||
 | 
					          url: updateVariablesSyntax(link.url),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      panelUpgrades.push((panel: any) => {
 | 
				
			||||||
 | 
					        // For graph panel
 | 
				
			||||||
 | 
					        if (panel.options && panel.options.dataLinks && _.isArray(panel.options.dataLinks)) {
 | 
				
			||||||
 | 
					          panel.options.dataLinks = panel.options.dataLinks.map(updateLinks);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // For panel with fieldOptions
 | 
				
			||||||
 | 
					        if (panel.options && panel.options.fieldOptions && panel.options.fieldOptions.defaults) {
 | 
				
			||||||
 | 
					          if (panel.options.fieldOptions.defaults.links && _.isArray(panel.options.fieldOptions.defaults.links)) {
 | 
				
			||||||
 | 
					            panel.options.fieldOptions.defaults.links = panel.options.fieldOptions.defaults.links.map(updateLinks);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (panel.options.fieldOptions.defaults.title) {
 | 
				
			||||||
 | 
					            panel.options.fieldOptions.defaults.title = updateVariablesSyntax(
 | 
				
			||||||
 | 
					              panel.options.fieldOptions.defaults.title
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (panelUpgrades.length === 0) {
 | 
					    if (panelUpgrades.length === 0) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -666,3 +693,26 @@ function upgradePanelLink(link: any): DataLink {
 | 
				
			||||||
    targetBlank: link.targetBlank,
 | 
					    targetBlank: link.targetBlank,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updateVariablesSyntax(text: string) {
 | 
				
			||||||
 | 
					  const legacyVariableNamesRegex = /(__series_name)|(\$__series_name)|(__value_time)|(__field_name)|(\$__field_name)/g;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return text.replace(legacyVariableNamesRegex, (match, seriesName, seriesName1, valueTime, fieldName, fieldName1) => {
 | 
				
			||||||
 | 
					    if (seriesName) {
 | 
				
			||||||
 | 
					      return '__series.name';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (seriesName1) {
 | 
				
			||||||
 | 
					      return '${__series.name}';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (valueTime) {
 | 
				
			||||||
 | 
					      return '__value.time';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (fieldName) {
 | 
				
			||||||
 | 
					      return '__field.name';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (fieldName1) {
 | 
				
			||||||
 | 
					      return '${__field.name}';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return match;
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,32 @@
 | 
				
			||||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
 | 
					import { PanelModel } from 'app/features/dashboard/state/PanelModel';
 | 
				
			||||||
import { FieldDisplay, DataLinkBuiltInVars } from '@grafana/ui';
 | 
					import { FieldDisplay } from '@grafana/ui';
 | 
				
			||||||
import { LinkModelSupplier, getTimeField, ScopedVars } from '@grafana/data';
 | 
					import { LinkModelSupplier, getTimeField, Labels, ScopedVars, ScopedVar } from '@grafana/data';
 | 
				
			||||||
import { getLinkSrv } from './link_srv';
 | 
					import { getLinkSrv } from './link_srv';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface SeriesVars {
 | 
				
			||||||
 | 
					  name?: string;
 | 
				
			||||||
 | 
					  labels?: Labels;
 | 
				
			||||||
 | 
					  refId?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface FieldVars {
 | 
				
			||||||
 | 
					  name: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ValueVars {
 | 
				
			||||||
 | 
					  raw: any;
 | 
				
			||||||
 | 
					  numeric: number;
 | 
				
			||||||
 | 
					  text: string;
 | 
				
			||||||
 | 
					  time?: number;
 | 
				
			||||||
 | 
					  calc?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface DataLinkScopedVars extends ScopedVars {
 | 
				
			||||||
 | 
					  __series?: ScopedVar<SeriesVars>;
 | 
				
			||||||
 | 
					  __field?: ScopedVar<FieldVars>;
 | 
				
			||||||
 | 
					  __value?: ScopedVar<ValueVars>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Link suppliers creates link models based on a link origin
 | 
					 * Link suppliers creates link models based on a link origin
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -14,29 +38,53 @@ export const getFieldLinksSupplier = (value: FieldDisplay): LinkModelSupplier<Fi
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    getLinks: (_scopedVars?: any) => {
 | 
					    getLinks: (_scopedVars?: any) => {
 | 
				
			||||||
      const scopedVars: ScopedVars = {};
 | 
					      const scopedVars: DataLinkScopedVars = {};
 | 
				
			||||||
      // TODO, add values to scopedVars and/or pass objects to event listeners
 | 
					
 | 
				
			||||||
      if (value.view) {
 | 
					      if (value.view) {
 | 
				
			||||||
        scopedVars[DataLinkBuiltInVars.seriesName] = {
 | 
					        const { dataFrame } = value.view;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        scopedVars['__series'] = {
 | 
				
			||||||
 | 
					          value: {
 | 
				
			||||||
 | 
					            name: dataFrame.name,
 | 
				
			||||||
 | 
					            labels: dataFrame.labels,
 | 
				
			||||||
 | 
					            refId: dataFrame.refId,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
          text: 'Series',
 | 
					          text: 'Series',
 | 
				
			||||||
          value: value.view.dataFrame.name,
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        const field = value.column ? value.view.dataFrame.fields[value.column] : undefined;
 | 
					
 | 
				
			||||||
 | 
					        const field = value.colIndex !== undefined ? dataFrame.fields[value.colIndex] : undefined;
 | 
				
			||||||
        if (field) {
 | 
					        if (field) {
 | 
				
			||||||
          console.log('Full Field Info:', field);
 | 
					          console.log('Full Field Info:', field);
 | 
				
			||||||
        }
 | 
					          scopedVars['__field'] = {
 | 
				
			||||||
        if (value.row) {
 | 
					            value: {
 | 
				
			||||||
          const row = value.view.get(value.row);
 | 
					              name: field.name,
 | 
				
			||||||
          console.log('ROW:', row);
 | 
					            },
 | 
				
			||||||
          const dataFrame = value.view.dataFrame;
 | 
					            text: 'Field',
 | 
				
			||||||
 | 
					 | 
				
			||||||
          const { timeField } = getTimeField(dataFrame);
 | 
					 | 
				
			||||||
          if (timeField) {
 | 
					 | 
				
			||||||
            scopedVars[DataLinkBuiltInVars.valueTime] = {
 | 
					 | 
				
			||||||
              text: 'Value time',
 | 
					 | 
				
			||||||
              value: timeField.values.get(value.row),
 | 
					 | 
				
			||||||
          };
 | 
					          };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (value.rowIndex) {
 | 
				
			||||||
 | 
					          const { timeField } = getTimeField(dataFrame);
 | 
				
			||||||
 | 
					          scopedVars['__value'] = {
 | 
				
			||||||
 | 
					            value: {
 | 
				
			||||||
 | 
					              raw: field.values.get(value.rowIndex),
 | 
				
			||||||
 | 
					              numeric: value.display.numeric,
 | 
				
			||||||
 | 
					              text: value.display.text,
 | 
				
			||||||
 | 
					              time: timeField ? timeField.values.get(value.rowIndex) : undefined,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            text: 'Value',
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          // calculation
 | 
				
			||||||
 | 
					          scopedVars['__value'] = {
 | 
				
			||||||
 | 
					            value: {
 | 
				
			||||||
 | 
					              raw: value.display.numeric,
 | 
				
			||||||
 | 
					              numeric: value.display.numeric,
 | 
				
			||||||
 | 
					              text: value.display.text,
 | 
				
			||||||
 | 
					              calc: value.name,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            text: 'Value',
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        console.log('VALUE', value);
 | 
					        console.log('VALUE', value);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,47 +4,118 @@ import templateSrv, { TemplateSrv } from 'app/features/templating/template_srv';
 | 
				
			||||||
import coreModule from 'app/core/core_module';
 | 
					import coreModule from 'app/core/core_module';
 | 
				
			||||||
import { appendQueryToUrl, toUrlParams } from 'app/core/utils/url';
 | 
					import { appendQueryToUrl, toUrlParams } from 'app/core/utils/url';
 | 
				
			||||||
import { VariableSuggestion, VariableOrigin, DataLinkBuiltInVars } from '@grafana/ui';
 | 
					import { VariableSuggestion, VariableOrigin, DataLinkBuiltInVars } from '@grafana/ui';
 | 
				
			||||||
import { DataLink, KeyValue, deprecationWarning, LinkModel, ScopedVars } from '@grafana/data';
 | 
					import { DataLink, KeyValue, deprecationWarning, LinkModel, DataFrame, ScopedVars } from '@grafana/data';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const timeRangeVars = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    value: `${DataLinkBuiltInVars.keepTime}`,
 | 
				
			||||||
 | 
					    label: 'Time range',
 | 
				
			||||||
 | 
					    documentation: 'Adds current time range',
 | 
				
			||||||
 | 
					    origin: VariableOrigin.BuiltIn,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    value: `${DataLinkBuiltInVars.timeRangeFrom}`,
 | 
				
			||||||
 | 
					    label: 'Time range: from',
 | 
				
			||||||
 | 
					    documentation: "Adds current time range's from value",
 | 
				
			||||||
 | 
					    origin: VariableOrigin.BuiltIn,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    value: `${DataLinkBuiltInVars.timeRangeTo}`,
 | 
				
			||||||
 | 
					    label: 'Time range: to',
 | 
				
			||||||
 | 
					    documentation: "Adds current time range's to value",
 | 
				
			||||||
 | 
					    origin: VariableOrigin.BuiltIn,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fieldVars = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    value: `${DataLinkBuiltInVars.fieldName}`,
 | 
				
			||||||
 | 
					    label: 'Name',
 | 
				
			||||||
 | 
					    documentation: 'Field name of the clicked datapoint (in ms epoch)',
 | 
				
			||||||
 | 
					    origin: VariableOrigin.Field,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const valueVars = [
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    value: `${DataLinkBuiltInVars.valueNumeric}`,
 | 
				
			||||||
 | 
					    label: 'Numeric',
 | 
				
			||||||
 | 
					    documentation: 'Numeric representation of selected value',
 | 
				
			||||||
 | 
					    origin: VariableOrigin.Value,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    value: `${DataLinkBuiltInVars.valueText}`,
 | 
				
			||||||
 | 
					    label: 'Text',
 | 
				
			||||||
 | 
					    documentation: 'Text representation of selected value',
 | 
				
			||||||
 | 
					    origin: VariableOrigin.Value,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    value: `${DataLinkBuiltInVars.valueRaw}`,
 | 
				
			||||||
 | 
					    label: 'Raw',
 | 
				
			||||||
 | 
					    documentation: 'Raw value',
 | 
				
			||||||
 | 
					    origin: VariableOrigin.Value,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const buildLabelPath = (label: string) => {
 | 
				
			||||||
 | 
					  return label.indexOf('.') > -1 ? `["${label}"]` : `.${label}`;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getPanelLinksVariableSuggestions = (): VariableSuggestion[] => [
 | 
					export const getPanelLinksVariableSuggestions = (): VariableSuggestion[] => [
 | 
				
			||||||
  ...templateSrv.variables.map(variable => ({
 | 
					  ...templateSrv.variables.map(variable => ({
 | 
				
			||||||
    value: variable.name as string,
 | 
					    value: variable.name as string,
 | 
				
			||||||
 | 
					    label: variable.name,
 | 
				
			||||||
    origin: VariableOrigin.Template,
 | 
					    origin: VariableOrigin.Template,
 | 
				
			||||||
  })),
 | 
					  })),
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    value: `${DataLinkBuiltInVars.includeVars}`,
 | 
					    value: `${DataLinkBuiltInVars.includeVars}`,
 | 
				
			||||||
 | 
					    label: 'All variables',
 | 
				
			||||||
    documentation: 'Adds current variables',
 | 
					    documentation: 'Adds current variables',
 | 
				
			||||||
    origin: VariableOrigin.BuiltIn,
 | 
					    origin: VariableOrigin.Template,
 | 
				
			||||||
  },
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    value: `${DataLinkBuiltInVars.keepTime}`,
 | 
					 | 
				
			||||||
    documentation: 'Adds current time range',
 | 
					 | 
				
			||||||
    origin: VariableOrigin.BuiltIn,
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  ...timeRangeVars,
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getDataLinksVariableSuggestions = (): VariableSuggestion[] => [
 | 
					const getSeriesVars = (dataFrames: DataFrame[]) => {
 | 
				
			||||||
  ...getPanelLinksVariableSuggestions(),
 | 
					  const labels = _.flatten(dataFrames.map(df => Object.keys(df.labels || {})));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      value: `${DataLinkBuiltInVars.seriesName}`,
 | 
					      value: `${DataLinkBuiltInVars.seriesName}`,
 | 
				
			||||||
    documentation: 'Adds series name',
 | 
					      label: 'Name',
 | 
				
			||||||
    origin: VariableOrigin.BuiltIn,
 | 
					      documentation: 'Name of the series',
 | 
				
			||||||
 | 
					      origin: VariableOrigin.Series,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  {
 | 
					    ...labels.map(label => ({
 | 
				
			||||||
 | 
					      value: `__series.labels${buildLabelPath(label)}`,
 | 
				
			||||||
 | 
					      label: `labels.${label}`,
 | 
				
			||||||
 | 
					      documentation: `${label} label value`,
 | 
				
			||||||
 | 
					      origin: VariableOrigin.Series,
 | 
				
			||||||
 | 
					    })),
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export const getDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => {
 | 
				
			||||||
 | 
					  const seriesVars = getSeriesVars(dataFrames);
 | 
				
			||||||
 | 
					  const valueTimeVar = {
 | 
				
			||||||
    value: `${DataLinkBuiltInVars.valueTime}`,
 | 
					    value: `${DataLinkBuiltInVars.valueTime}`,
 | 
				
			||||||
 | 
					    label: 'Time',
 | 
				
			||||||
    documentation: 'Time value of the clicked datapoint (in ms epoch)',
 | 
					    documentation: 'Time value of the clicked datapoint (in ms epoch)',
 | 
				
			||||||
    origin: VariableOrigin.BuiltIn,
 | 
					    origin: VariableOrigin.Value,
 | 
				
			||||||
  },
 | 
					  };
 | 
				
			||||||
];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getCalculationValueDataLinksVariableSuggestions = (): VariableSuggestion[] => [
 | 
					  return [...seriesVars, ...fieldVars, ...valueVars, valueTimeVar, ...getPanelLinksVariableSuggestions()];
 | 
				
			||||||
  ...getPanelLinksVariableSuggestions(),
 | 
					};
 | 
				
			||||||
  {
 | 
					
 | 
				
			||||||
    value: `${DataLinkBuiltInVars.seriesName}`,
 | 
					export const getCalculationValueDataLinksVariableSuggestions = (dataFrames: DataFrame[]): VariableSuggestion[] => {
 | 
				
			||||||
    documentation: 'Adds series name',
 | 
					  const seriesVars = getSeriesVars(dataFrames);
 | 
				
			||||||
    origin: VariableOrigin.BuiltIn,
 | 
					  const valueCalcVar = {
 | 
				
			||||||
  },
 | 
					    value: `${DataLinkBuiltInVars.valueCalc}`,
 | 
				
			||||||
];
 | 
					    label: 'Calculation name',
 | 
				
			||||||
 | 
					    documentation: 'Name of the calculation the value is a result of',
 | 
				
			||||||
 | 
					    origin: VariableOrigin.Value,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  return [...seriesVars, ...fieldVars, ...valueVars, valueCalcVar, ...getPanelLinksVariableSuggestions()];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface LinkService {
 | 
					export interface LinkService {
 | 
				
			||||||
  getDataLinkUIModel: <T>(link: DataLink, scopedVars: ScopedVars, origin: T) => LinkModel<T>;
 | 
					  getDataLinkUIModel: <T>(link: DataLink, scopedVars: ScopedVars, origin: T) => LinkModel<T>;
 | 
				
			||||||
| 
						 | 
					@ -83,16 +154,15 @@ export class LinkSrv implements LinkService {
 | 
				
			||||||
    const timeRangeUrl = toUrlParams(this.timeSrv.timeRangeForUrl());
 | 
					    const timeRangeUrl = toUrlParams(this.timeSrv.timeRangeForUrl());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const info: LinkModel<T> = {
 | 
					    const info: LinkModel<T> = {
 | 
				
			||||||
      href: link.url,
 | 
					      href: link.url.replace(/\s|\n/g, ''),
 | 
				
			||||||
      title: this.templateSrv.replace(link.title || '', scopedVars),
 | 
					      title: this.templateSrv.replace(link.title || '', scopedVars),
 | 
				
			||||||
      target: link.targetBlank ? '_blank' : '_self',
 | 
					      target: link.targetBlank ? '_blank' : '_self',
 | 
				
			||||||
      origin,
 | 
					      origin,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.templateSrv.fillVariableValuesForUrl(params, scopedVars);
 | 
					    this.templateSrv.fillVariableValuesForUrl(params, scopedVars);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const variablesQuery = toUrlParams(params);
 | 
					    const variablesQuery = toUrlParams(params);
 | 
				
			||||||
    info.href = this.templateSrv.replace(link.url, {
 | 
					    info.href = this.templateSrv.replace(info.href, {
 | 
				
			||||||
      ...scopedVars,
 | 
					      ...scopedVars,
 | 
				
			||||||
      [DataLinkBuiltInVars.keepTime]: {
 | 
					      [DataLinkBuiltInVars.keepTime]: {
 | 
				
			||||||
        text: timeRangeUrl,
 | 
					        text: timeRangeUrl,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -105,11 +105,13 @@ describe('linkSrv', () => {
 | 
				
			||||||
        linkSrv.getDataLinkUIModel(
 | 
					        linkSrv.getDataLinkUIModel(
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            title: 'Any title',
 | 
					            title: 'Any title',
 | 
				
			||||||
            url: `/d/1?var-test=$${DataLinkBuiltInVars.seriesName}`,
 | 
					            url: `/d/1?var-test=$\{${DataLinkBuiltInVars.seriesName}}`,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            [DataLinkBuiltInVars.seriesName]: {
 | 
					            __series: {
 | 
				
			||||||
              value: 'A-series',
 | 
					              value: {
 | 
				
			||||||
 | 
					                name: 'A-series',
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
              text: 'A-series',
 | 
					              text: 'A-series',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
| 
						 | 
					@ -122,12 +124,12 @@ describe('linkSrv', () => {
 | 
				
			||||||
        linkSrv.getDataLinkUIModel(
 | 
					        linkSrv.getDataLinkUIModel(
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            title: 'Any title',
 | 
					            title: 'Any title',
 | 
				
			||||||
            url: `/d/1?time=$${DataLinkBuiltInVars.valueTime}`,
 | 
					            url: `/d/1?time=$\{${DataLinkBuiltInVars.valueTime}}`,
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            [DataLinkBuiltInVars.valueTime]: {
 | 
					            __value: {
 | 
				
			||||||
              value: dataPointMock.datapoint[0],
 | 
					              value: { time: dataPointMock.datapoint[0] },
 | 
				
			||||||
              text: dataPointMock.datapoint[0],
 | 
					              text: 'Value',
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          {}
 | 
					          {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,27 @@ describe('templateSrv', () => {
 | 
				
			||||||
      initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
 | 
					      initTemplateSrv([{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('scoped vars should support objects', () => {
 | 
				
			||||||
 | 
					      const target = _templateSrv.replace('${series.name} ${series.nested.field}', {
 | 
				
			||||||
 | 
					        series: { value: { name: 'Server1', nested: { field: 'nested' } } },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(target).toBe('Server1 nested');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('scoped vars should support objects with propert names with dot', () => {
 | 
				
			||||||
 | 
					      const target = _templateSrv.replace('${series.name} ${series.nested["field.with.dot"]}', {
 | 
				
			||||||
 | 
					        series: { value: { name: 'Server1', nested: { 'field.with.dot': 'nested' } } },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(target).toBe('Server1 nested');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('scoped vars should support arrays of objects', () => {
 | 
				
			||||||
 | 
					      const target = _templateSrv.replace('${series.rows[0].name} ${series.rows[1].name}', {
 | 
				
			||||||
 | 
					        series: { value: { rows: [{ name: 'first' }, { name: 'second' }] } },
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      expect(target).toBe('first second');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it('should replace $test with scoped value', () => {
 | 
					    it('should replace $test with scoped value', () => {
 | 
				
			||||||
      const target = _templateSrv.replace('this.$test.filters', {
 | 
					      const target = _templateSrv.replace('this.$test.filters', {
 | 
				
			||||||
        test: { value: 'mupp', text: 'asd' },
 | 
					        test: { value: 'mupp', text: 'asd' },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,10 @@ function luceneEscape(value: string) {
 | 
				
			||||||
  return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
 | 
					  return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface FieldAccessorCache {
 | 
				
			||||||
 | 
					  [key: string]: (obj: any) => any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class TemplateSrv {
 | 
					export class TemplateSrv {
 | 
				
			||||||
  variables: any[];
 | 
					  variables: any[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +19,7 @@ export class TemplateSrv {
 | 
				
			||||||
  private grafanaVariables: any = {};
 | 
					  private grafanaVariables: any = {};
 | 
				
			||||||
  private builtIns: any = {};
 | 
					  private builtIns: any = {};
 | 
				
			||||||
  private timeRange: TimeRange = null;
 | 
					  private timeRange: TimeRange = null;
 | 
				
			||||||
 | 
					  private fieldAccessorCache: FieldAccessorCache = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    this.builtIns['__interval'] = { text: '1s', value: '1s' };
 | 
					    this.builtIns['__interval'] = { text: '1s', value: '1s' };
 | 
				
			||||||
| 
						 | 
					@ -224,21 +229,44 @@ export class TemplateSrv {
 | 
				
			||||||
    return values;
 | 
					    return values;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getFieldAccessor(fieldPath: string) {
 | 
				
			||||||
 | 
					    const accessor = this.fieldAccessorCache[fieldPath];
 | 
				
			||||||
 | 
					    if (accessor) {
 | 
				
			||||||
 | 
					      return accessor;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (this.fieldAccessorCache[fieldPath] = _.property(fieldPath));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getVariableValue(variableName: string, fieldPath: string | undefined, scopedVars: ScopedVars) {
 | 
				
			||||||
 | 
					    const scopedVar = scopedVars[variableName];
 | 
				
			||||||
 | 
					    if (!scopedVar) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (fieldPath) {
 | 
				
			||||||
 | 
					      return this.getFieldAccessor(fieldPath)(scopedVar.value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return scopedVar.value;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  replace(target: string, scopedVars?: ScopedVars, format?: string | Function): any {
 | 
					  replace(target: string, scopedVars?: ScopedVars, format?: string | Function): any {
 | 
				
			||||||
    if (!target) {
 | 
					    if (!target) {
 | 
				
			||||||
      return target;
 | 
					      return target;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let variable, systemValue, value, fmt;
 | 
					 | 
				
			||||||
    this.regex.lastIndex = 0;
 | 
					    this.regex.lastIndex = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return target.replace(this.regex, (match, var1, var2, fmt2, var3, fmt3) => {
 | 
					    return target.replace(this.regex, (match, var1, var2, fmt2, var3, fieldPath, fmt3) => {
 | 
				
			||||||
      variable = this.index[var1 || var2 || var3];
 | 
					      const variableName = var1 || var2 || var3;
 | 
				
			||||||
      fmt = fmt2 || fmt3 || format;
 | 
					      const variable = this.index[variableName];
 | 
				
			||||||
 | 
					      const fmt = fmt2 || fmt3 || format;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (scopedVars) {
 | 
					      if (scopedVars) {
 | 
				
			||||||
        value = scopedVars[var1 || var2 || var3];
 | 
					        const value = this.getVariableValue(variableName, fieldPath, scopedVars);
 | 
				
			||||||
        if (value) {
 | 
					        if (value !== null && value !== undefined) {
 | 
				
			||||||
          return this.formatValue(value.value, fmt, variable);
 | 
					          return this.formatValue(value, fmt, variable);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -246,12 +274,12 @@ export class TemplateSrv {
 | 
				
			||||||
        return match;
 | 
					        return match;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      systemValue = this.grafanaVariables[variable.current.value];
 | 
					      const systemValue = this.grafanaVariables[variable.current.value];
 | 
				
			||||||
      if (systemValue) {
 | 
					      if (systemValue) {
 | 
				
			||||||
        return this.formatValue(systemValue, fmt, variable);
 | 
					        return this.formatValue(systemValue, fmt, variable);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      value = variable.current.value;
 | 
					      let value = variable.current.value;
 | 
				
			||||||
      if (this.isAllValue(value)) {
 | 
					      if (this.isAllValue(value)) {
 | 
				
			||||||
        value = this.getAllValue(variable);
 | 
					        value = this.getAllValue(variable);
 | 
				
			||||||
        // skip formatting of custom all values
 | 
					        // skip formatting of custom all values
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@ import { assignModelProperties } from 'app/core/utils/model_utils';
 | 
				
			||||||
 * \[\[([\s\S]+?)(?::(\w+))?\]\]    [[var2]] or [[var2:fmt2]]
 | 
					 * \[\[([\s\S]+?)(?::(\w+))?\]\]    [[var2]] or [[var2:fmt2]]
 | 
				
			||||||
 * \${(\w+)(?::(\w+))?}             ${var3} or ${var3:fmt3}
 | 
					 * \${(\w+)(?::(\w+))?}             ${var3} or ${var3:fmt3}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export const variableRegex = /\$(\w+)|\[\[([\s\S]+?)(?::(\w+))?\]\]|\${(\w+)(?::(\w+))?}/g;
 | 
					export const variableRegex = /\$(\w+)|\[\[([\s\S]+?)(?::(\w+))?\]\]|\${(\w+)(?:\.([^:^\}]+))?(?::(\w+))?}/g;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Helper function since lastIndex is not reset
 | 
					// Helper function since lastIndex is not reset
 | 
				
			||||||
export const variableRegexExec = (variableString: string) => {
 | 
					export const variableRegexExec = (variableString: string) => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,8 +68,8 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
 | 
				
			||||||
    const { defaults } = fieldOptions;
 | 
					    const { defaults } = fieldOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const suggestions = fieldOptions.values
 | 
					    const suggestions = fieldOptions.values
 | 
				
			||||||
      ? getDataLinksVariableSuggestions()
 | 
					      ? getDataLinksVariableSuggestions(this.props.data.series)
 | 
				
			||||||
      : getCalculationValueDataLinksVariableSuggestions();
 | 
					      : getCalculationValueDataLinksVariableSuggestions(this.props.data.series);
 | 
				
			||||||
    const labelWidth = 6;
 | 
					    const labelWidth = 6;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,9 +72,10 @@ export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOption
 | 
				
			||||||
    const { options } = this.props;
 | 
					    const { options } = this.props;
 | 
				
			||||||
    const { fieldOptions, showThresholdLabels, showThresholdMarkers } = options;
 | 
					    const { fieldOptions, showThresholdLabels, showThresholdMarkers } = options;
 | 
				
			||||||
    const { defaults } = fieldOptions;
 | 
					    const { defaults } = fieldOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const suggestions = fieldOptions.values
 | 
					    const suggestions = fieldOptions.values
 | 
				
			||||||
      ? getDataLinksVariableSuggestions()
 | 
					      ? getDataLinksVariableSuggestions(this.props.data.series)
 | 
				
			||||||
      : getCalculationValueDataLinksVariableSuggestions();
 | 
					      : getCalculationValueDataLinksVariableSuggestions(this.props.data.series);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <>
 | 
					      <>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ export interface FlotDataPoint {
 | 
				
			||||||
export class GraphContextMenuCtrl {
 | 
					export class GraphContextMenuCtrl {
 | 
				
			||||||
  private source?: FlotDataPoint | null;
 | 
					  private source?: FlotDataPoint | null;
 | 
				
			||||||
  private scope?: any;
 | 
					  private scope?: any;
 | 
				
			||||||
  menuItems: ContextMenuItem[];
 | 
					  menuItemsSupplier?: () => ContextMenuItem[];
 | 
				
			||||||
  scrollContextElement: HTMLElement | null;
 | 
					  scrollContextElement: HTMLElement | null;
 | 
				
			||||||
  position: {
 | 
					  position: {
 | 
				
			||||||
    x: number;
 | 
					    x: number;
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,6 @@ export class GraphContextMenuCtrl {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor($scope: any) {
 | 
					  constructor($scope: any) {
 | 
				
			||||||
    this.isVisible = false;
 | 
					    this.isVisible = false;
 | 
				
			||||||
    this.menuItems = [];
 | 
					 | 
				
			||||||
    this.scope = $scope;
 | 
					    this.scope = $scope;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,11 +69,7 @@ export class GraphContextMenuCtrl {
 | 
				
			||||||
    return this.source;
 | 
					    return this.source;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setMenuItems = (items: ContextMenuItem[]) => {
 | 
					  setMenuItemsSupplier = (menuItemsSupplier: () => ContextMenuItem[]) => {
 | 
				
			||||||
    this.menuItems = items;
 | 
					    this.menuItemsSupplier = menuItemsSupplier;
 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getMenuItems = () => {
 | 
					 | 
				
			||||||
    return this.menuItems;
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,7 +16,6 @@ import GraphTooltip from './graph_tooltip';
 | 
				
			||||||
import { ThresholdManager } from './threshold_manager';
 | 
					import { ThresholdManager } from './threshold_manager';
 | 
				
			||||||
import { TimeRegionManager } from './time_region_manager';
 | 
					import { TimeRegionManager } from './time_region_manager';
 | 
				
			||||||
import { EventManager } from 'app/features/annotations/all';
 | 
					import { EventManager } from 'app/features/annotations/all';
 | 
				
			||||||
import { LinkService, LinkSrv } from 'app/features/panel/panellinks/link_srv';
 | 
					 | 
				
			||||||
import { convertToHistogramData } from './histogram';
 | 
					import { convertToHistogramData } from './histogram';
 | 
				
			||||||
import { alignYLevel } from './align_yaxes';
 | 
					import { alignYLevel } from './align_yaxes';
 | 
				
			||||||
import config from 'app/core/config';
 | 
					import config from 'app/core/config';
 | 
				
			||||||
| 
						 | 
					@ -25,12 +24,13 @@ import ReactDOM from 'react-dom';
 | 
				
			||||||
import { GraphLegendProps, Legend } from './Legend/Legend';
 | 
					import { GraphLegendProps, Legend } from './Legend/Legend';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { GraphCtrl } from './module';
 | 
					import { GraphCtrl } from './module';
 | 
				
			||||||
import { getValueFormat, ContextMenuItem, ContextMenuGroup, DataLinkBuiltInVars } from '@grafana/ui';
 | 
					import { getValueFormat, ContextMenuGroup, FieldDisplay, ContextMenuItem, getDisplayProcessor } from '@grafana/ui';
 | 
				
			||||||
import { provideTheme } from 'app/core/utils/ConfigProvider';
 | 
					import { provideTheme, getCurrentTheme } from 'app/core/utils/ConfigProvider';
 | 
				
			||||||
import { DataLink, toUtc } from '@grafana/data';
 | 
					import { toUtc, LinkModelSupplier, DataFrameView } from '@grafana/data';
 | 
				
			||||||
import { GraphContextMenuCtrl, FlotDataPoint } from './GraphContextMenuCtrl';
 | 
					import { GraphContextMenuCtrl } from './GraphContextMenuCtrl';
 | 
				
			||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 | 
					import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
 | 
				
			||||||
import { ContextSrv } from 'app/core/services/context_srv';
 | 
					import { ContextSrv } from 'app/core/services/context_srv';
 | 
				
			||||||
 | 
					import { getFieldLinksSupplier } from 'app/features/panel/panellinks/linkSuppliers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const LegendWithThemeProvider = provideTheme(Legend);
 | 
					const LegendWithThemeProvider = provideTheme(Legend);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,7 +50,7 @@ class GraphElement {
 | 
				
			||||||
  timeRegionManager: TimeRegionManager;
 | 
					  timeRegionManager: TimeRegionManager;
 | 
				
			||||||
  legendElem: HTMLElement;
 | 
					  legendElem: HTMLElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(private scope: any, private elem: JQuery, private timeSrv: TimeSrv, private linkSrv: LinkService) {
 | 
					  constructor(private scope: any, private elem: JQuery, private timeSrv: TimeSrv) {
 | 
				
			||||||
    this.ctrl = scope.ctrl;
 | 
					    this.ctrl = scope.ctrl;
 | 
				
			||||||
    this.contextMenu = scope.ctrl.contextMenuCtrl;
 | 
					    this.contextMenu = scope.ctrl.contextMenuCtrl;
 | 
				
			||||||
    this.dashboard = this.ctrl.dashboard;
 | 
					    this.dashboard = this.ctrl.dashboard;
 | 
				
			||||||
| 
						 | 
					@ -175,9 +175,12 @@ class GraphElement {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getContextMenuItems = (flotPosition: { x: number; y: number }, item?: FlotDataPoint): ContextMenuGroup[] => {
 | 
					  getContextMenuItemsSupplier = (
 | 
				
			||||||
    const dataLinks: DataLink[] = this.panel.options.dataLinks || [];
 | 
					    flotPosition: { x: number; y: number },
 | 
				
			||||||
 | 
					    linksSupplier?: LinkModelSupplier<FieldDisplay>
 | 
				
			||||||
 | 
					  ): (() => ContextMenuGroup[]) => {
 | 
				
			||||||
 | 
					    return () => {
 | 
				
			||||||
 | 
					      // Fixed context menu items
 | 
				
			||||||
      const items: ContextMenuGroup[] = [
 | 
					      const items: ContextMenuGroup[] = [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          items: [
 | 
					          items: [
 | 
				
			||||||
| 
						 | 
					@ -190,38 +193,30 @@ class GraphElement {
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      ];
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return item
 | 
					      if (!linksSupplier) {
 | 
				
			||||||
      ? [
 | 
					        return items;
 | 
				
			||||||
          ...items,
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const dataLinks = [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            items: [
 | 
					          items: linksSupplier.getLinks(this.panel.scopedVars).map<ContextMenuItem>(link => {
 | 
				
			||||||
              ...dataLinks.map<ContextMenuItem>(link => {
 | 
					 | 
				
			||||||
                const linkUiModel = this.linkSrv.getDataLinkUIModel(
 | 
					 | 
				
			||||||
                  link,
 | 
					 | 
				
			||||||
                  {
 | 
					 | 
				
			||||||
                    ...this.panel.scopedVars,
 | 
					 | 
				
			||||||
                    [DataLinkBuiltInVars.seriesName]: { value: item.series.alias, text: item.series.alias },
 | 
					 | 
				
			||||||
                    [DataLinkBuiltInVars.valueTime]: { value: item.datapoint[0], text: item.datapoint[0] },
 | 
					 | 
				
			||||||
                  },
 | 
					 | 
				
			||||||
                  item
 | 
					 | 
				
			||||||
                );
 | 
					 | 
				
			||||||
            return {
 | 
					            return {
 | 
				
			||||||
                  label: linkUiModel.title,
 | 
					              label: link.title,
 | 
				
			||||||
                  url: linkUiModel.href,
 | 
					              url: link.href,
 | 
				
			||||||
                  target: linkUiModel.target,
 | 
					              target: link.target,
 | 
				
			||||||
                  icon: `fa ${linkUiModel.target === '_self' ? 'fa-link' : 'fa-external-link'}`,
 | 
					              icon: `fa ${link.target === '_self' ? 'fa-link' : 'fa-external-link'}`,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
          }),
 | 
					          }),
 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        ]
 | 
					      ];
 | 
				
			||||||
      : items;
 | 
					
 | 
				
			||||||
 | 
					      return [...items, ...dataLinks];
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onPlotClick(event: JQueryEventObject, pos: any, item: any) {
 | 
					  onPlotClick(event: JQueryEventObject, pos: any, item: any) {
 | 
				
			||||||
    const scrollContextElement = this.elem.closest('.view') ? this.elem.closest('.view').get()[0] : null;
 | 
					    const scrollContextElement = this.elem.closest('.view') ? this.elem.closest('.view').get()[0] : null;
 | 
				
			||||||
    const contextMenuSourceItem = item;
 | 
					    const contextMenuSourceItem = item;
 | 
				
			||||||
    let contextMenuItems: ContextMenuItem[];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.panel.xaxis.mode !== 'time') {
 | 
					    if (this.panel.xaxis.mode !== 'time') {
 | 
				
			||||||
      // Skip if panel in histogram or series mode
 | 
					      // Skip if panel in histogram or series mode
 | 
				
			||||||
| 
						 | 
					@ -239,12 +234,40 @@ class GraphElement {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      this.tooltip.clear(this.plot);
 | 
					      this.tooltip.clear(this.plot);
 | 
				
			||||||
      contextMenuItems = this.getContextMenuItems(pos, item) as ContextMenuItem[];
 | 
					      let linksSupplier: LinkModelSupplier<FieldDisplay>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (item) {
 | 
				
			||||||
 | 
					        // pickup y-axis index to know which field's config to apply
 | 
				
			||||||
 | 
					        const yAxisConfig = this.panel.yaxes[item.series.yaxis.n === 2 ? 1 : 0];
 | 
				
			||||||
 | 
					        const fieldConfig = {
 | 
				
			||||||
 | 
					          decimals: yAxisConfig.decimals,
 | 
				
			||||||
 | 
					          links: this.panel.options.dataLinks || [],
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        const dataFrame = this.ctrl.dataList[item.series.dataFrameIndex];
 | 
				
			||||||
 | 
					        const field = dataFrame.fields[item.series.fieldIndex];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const fieldDisplay = getDisplayProcessor({
 | 
				
			||||||
 | 
					          config: fieldConfig,
 | 
				
			||||||
 | 
					          theme: getCurrentTheme(),
 | 
				
			||||||
 | 
					        })(field.values.get(item.dataIndex));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        linksSupplier = this.panel.options.dataLinks
 | 
				
			||||||
 | 
					          ? getFieldLinksSupplier({
 | 
				
			||||||
 | 
					              display: fieldDisplay,
 | 
				
			||||||
 | 
					              name: field.name,
 | 
				
			||||||
 | 
					              view: new DataFrameView(dataFrame),
 | 
				
			||||||
 | 
					              rowIndex: item.dataIndex,
 | 
				
			||||||
 | 
					              colIndex: item.series.fieldIndex,
 | 
				
			||||||
 | 
					              field: fieldConfig,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          : undefined;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.scope.$apply(() => {
 | 
					      this.scope.$apply(() => {
 | 
				
			||||||
        // Setting nearest CustomScrollbar element as a scroll context for graph context menu
 | 
					        // Setting nearest CustomScrollbar element as a scroll context for graph context menu
 | 
				
			||||||
        this.contextMenu.setScrollContextElement(scrollContextElement);
 | 
					        this.contextMenu.setScrollContextElement(scrollContextElement);
 | 
				
			||||||
        this.contextMenu.setSource(contextMenuSourceItem);
 | 
					        this.contextMenu.setSource(contextMenuSourceItem);
 | 
				
			||||||
        this.contextMenu.setMenuItems(contextMenuItems);
 | 
					        this.contextMenu.setMenuItemsSupplier(this.getContextMenuItemsSupplier(pos, linksSupplier) as any);
 | 
				
			||||||
        this.contextMenu.toggleMenu(pos);
 | 
					        this.contextMenu.toggleMenu(pos);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -363,7 +386,6 @@ class GraphElement {
 | 
				
			||||||
    this.thresholdManager.addFlotOptions(options, this.panel);
 | 
					    this.thresholdManager.addFlotOptions(options, this.panel);
 | 
				
			||||||
    this.timeRegionManager.addFlotOptions(options, this.panel);
 | 
					    this.timeRegionManager.addFlotOptions(options, this.panel);
 | 
				
			||||||
    this.eventManager.addFlotEvents(this.annotations, options);
 | 
					    this.eventManager.addFlotEvents(this.annotations, options);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.sortedSeries = this.sortSeries(this.data, this.panel);
 | 
					    this.sortedSeries = this.sortSeries(this.data, this.panel);
 | 
				
			||||||
    this.callPlot(options, true);
 | 
					    this.callPlot(options, true);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -855,12 +877,12 @@ class GraphElement {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** @ngInject */
 | 
					/** @ngInject */
 | 
				
			||||||
function graphDirective(timeSrv: TimeSrv, popoverSrv: any, contextSrv: ContextSrv, linkSrv: LinkSrv) {
 | 
					function graphDirective(timeSrv: TimeSrv, popoverSrv: any, contextSrv: ContextSrv) {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    restrict: 'A',
 | 
					    restrict: 'A',
 | 
				
			||||||
    template: '',
 | 
					    template: '',
 | 
				
			||||||
    link: (scope: any, elem: JQuery) => {
 | 
					    link: (scope: any, elem: JQuery) => {
 | 
				
			||||||
      return new GraphElement(scope, elem, timeSrv, linkSrv);
 | 
					      return new GraphElement(scope, elem, timeSrv);
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@ class GraphCtrl extends MetricsPanelCtrl {
 | 
				
			||||||
  subTabIndex: number;
 | 
					  subTabIndex: number;
 | 
				
			||||||
  processor: DataProcessor;
 | 
					  processor: DataProcessor;
 | 
				
			||||||
  contextMenuCtrl: GraphContextMenuCtrl;
 | 
					  contextMenuCtrl: GraphContextMenuCtrl;
 | 
				
			||||||
  linkVariableSuggestions: VariableSuggestion[] = getDataLinksVariableSuggestions();
 | 
					  linkVariableSuggestions: VariableSuggestion[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  panelDefaults: any = {
 | 
					  panelDefaults: any = {
 | 
				
			||||||
    // datasource name, null = default datasource
 | 
					    // datasource name, null = default datasource
 | 
				
			||||||
| 
						 | 
					@ -216,6 +216,8 @@ class GraphCtrl extends MetricsPanelCtrl {
 | 
				
			||||||
      range: this.range,
 | 
					      range: this.range,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.linkVariableSuggestions = getDataLinksVariableSuggestions(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.dataWarning = null;
 | 
					    this.dataWarning = null;
 | 
				
			||||||
    const datapointsCount = this.seriesList.reduce((prev, series) => {
 | 
					    const datapointsCount = this.seriesList.reduce((prev, series) => {
 | 
				
			||||||
      return prev + series.datapoints.length;
 | 
					      return prev + series.datapoints.length;
 | 
				
			||||||
| 
						 | 
					@ -337,6 +339,10 @@ class GraphCtrl extends MetricsPanelCtrl {
 | 
				
			||||||
  formatDate = (date: DateTimeInput, format?: string) => {
 | 
					  formatDate = (date: DateTimeInput, format?: string) => {
 | 
				
			||||||
    return this.dashboard.formatDate.apply(this.dashboard, [date, format]);
 | 
					    return this.dashboard.formatDate.apply(this.dashboard, [date, format]);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getDataFrameByRefId = (refId: string) => {
 | 
				
			||||||
 | 
					    return this.dataList.filter(dataFrame => dataFrame.refId === refId)[0];
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export { GraphCtrl, GraphCtrl as PanelCtrl };
 | 
					export { GraphCtrl, GraphCtrl as PanelCtrl };
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -121,7 +121,7 @@ describe('grafanaGraph', () => {
 | 
				
			||||||
    $.plot = ctrl.plot = jest.fn();
 | 
					    $.plot = ctrl.plot = jest.fn();
 | 
				
			||||||
    scope.ctrl = ctrl;
 | 
					    scope.ctrl = ctrl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    link = graphDirective({} as any, {}, {} as any, {} as any).link(scope, {
 | 
					    link = graphDirective({} as any, {}, {} as any).link(scope, {
 | 
				
			||||||
      width: () => 500,
 | 
					      width: () => 500,
 | 
				
			||||||
      mouseleave: () => {},
 | 
					      mouseleave: () => {},
 | 
				
			||||||
      bind: () => {},
 | 
					      bind: () => {},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ const template = `
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div ng-if="ctrl.contextMenuCtrl.isVisible">
 | 
					  <div ng-if="ctrl.contextMenuCtrl.isVisible">
 | 
				
			||||||
    <graph-context-menu
 | 
					    <graph-context-menu
 | 
				
			||||||
      items="ctrl.contextMenuCtrl.menuItems"
 | 
					      items="ctrl.contextMenuCtrl.menuItemsSupplier()"
 | 
				
			||||||
      onClose="ctrl.onContextMenuClose"
 | 
					      onClose="ctrl.onContextMenuClose"
 | 
				
			||||||
      getContextMenuSource="ctrl.contextMenuCtrl.getSource"
 | 
					      getContextMenuSource="ctrl.contextMenuCtrl.getSource"
 | 
				
			||||||
      formatSourceDate="ctrl.formatDate"
 | 
					      formatSourceDate="ctrl.formatDate"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,7 @@ export const getGraphSeriesModel = (
 | 
				
			||||||
  const graphs: GraphSeriesXY[] = [];
 | 
					  const graphs: GraphSeriesXY[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const displayProcessor = getDisplayProcessor({
 | 
					  const displayProcessor = getDisplayProcessor({
 | 
				
			||||||
    field: {
 | 
					    config: {
 | 
				
			||||||
      decimals: legendOptions.decimals,
 | 
					      decimals: legendOptions.decimals,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render() {
 | 
					  render() {
 | 
				
			||||||
    const { onOptionsChange, options } = this.props;
 | 
					    const { onOptionsChange, options, data } = this.props;
 | 
				
			||||||
    const { fieldOptions } = options;
 | 
					    const { fieldOptions } = options;
 | 
				
			||||||
    const { defaults } = fieldOptions;
 | 
					    const { defaults } = fieldOptions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,7 +51,7 @@ export class PieChartPanelEditor extends PureComponent<PanelEditorProps<PieChart
 | 
				
			||||||
            <FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} />
 | 
					            <FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} />
 | 
				
			||||||
          </PanelOptionsGroup>
 | 
					          </PanelOptionsGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <PieChartOptionsBox onOptionsChange={onOptionsChange} options={options} />
 | 
					          <PieChartOptionsBox data={data} onOptionsChange={onOptionsChange} options={options} />
 | 
				
			||||||
        </PanelOptionsGrid>
 | 
					        </PanelOptionsGrid>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
 | 
					        <ValueMappingsEditor onChange={this.onValueMappingsChanged} valueMappings={defaults.mappings} />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -238,7 +238,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const processor = getDisplayProcessor({
 | 
					    const processor = getDisplayProcessor({
 | 
				
			||||||
      field: {
 | 
					      config: {
 | 
				
			||||||
        ...fieldInfo.field.config,
 | 
					        ...fieldInfo.field.config,
 | 
				
			||||||
        unit: panel.format,
 | 
					        unit: panel.format,
 | 
				
			||||||
        decimals: panel.decimals,
 | 
					        decimals: panel.decimals,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,8 +70,8 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
 | 
				
			||||||
    const { fieldOptions } = options;
 | 
					    const { fieldOptions } = options;
 | 
				
			||||||
    const { defaults } = fieldOptions;
 | 
					    const { defaults } = fieldOptions;
 | 
				
			||||||
    const suggestions = fieldOptions.values
 | 
					    const suggestions = fieldOptions.values
 | 
				
			||||||
      ? getDataLinksVariableSuggestions()
 | 
					      ? getDataLinksVariableSuggestions(this.props.data.series)
 | 
				
			||||||
      : getCalculationValueDataLinksVariableSuggestions();
 | 
					      : getCalculationValueDataLinksVariableSuggestions(this.props.data.series);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <>
 | 
					      <>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue