Merge remote-tracking branch 'origin/main' into 34485
This commit is contained in:
+2
-1
@@ -2,9 +2,10 @@ load('scripts/pr.star', 'pr_pipelines')
|
||||
load('scripts/main.star', 'main_pipelines')
|
||||
load('scripts/release.star', 'release_pipelines', 'test_release_pipelines')
|
||||
load('scripts/version.star', 'version_branch_pipelines')
|
||||
load('scripts/job.star', 'cronjobs')
|
||||
load('scripts/vault.star', 'secrets')
|
||||
|
||||
def main(ctx):
|
||||
edition = 'oss'
|
||||
return pr_pipelines(edition=edition) + main_pipelines(edition=edition) + release_pipelines() + \
|
||||
test_release_pipelines() + version_branch_pipelines() + secrets()
|
||||
test_release_pipelines() + version_branch_pipelines() + cronjobs(edition=edition) + secrets()
|
||||
|
||||
+32
@@ -3432,6 +3432,38 @@ depends_on:
|
||||
- enterprise-build-release-branch
|
||||
- enterprise-windows-release-branch
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: scan-docker-images
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: scan-docker-images-unkown-low-medium-vulnerabilities
|
||||
image: aquasec/trivy:0.18.3
|
||||
commands:
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana:latest
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana:main
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana:latest-ubuntu
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana:main-ubuntu
|
||||
|
||||
- name: scan-docker-images-high-critical-vulnerabilities
|
||||
image: aquasec/trivy:0.18.3
|
||||
commands:
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL grafana/grafana:latest
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL grafana/grafana:main
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL grafana/grafana:latest-ubuntu
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL grafana/grafana:main-ubuntu
|
||||
|
||||
trigger:
|
||||
cron:
|
||||
- nightly
|
||||
event:
|
||||
- cron
|
||||
|
||||
---
|
||||
kind: secret
|
||||
name: dockerconfigjson
|
||||
|
||||
@@ -4,14 +4,14 @@ Upgrading Go or Node.js requires making changes in many different files. See bel
|
||||
|
||||
## Go
|
||||
|
||||
- CircleCi
|
||||
- Drone
|
||||
- `grafana/build-container`
|
||||
- Appveyor
|
||||
- Dockerfile
|
||||
|
||||
## Node.js
|
||||
|
||||
- CircleCI
|
||||
- Drone
|
||||
- `grafana/build-container`
|
||||
- Appveyor
|
||||
- Dockerfile
|
||||
@@ -42,6 +42,10 @@ GO111MODULE=on go mod tidy
|
||||
|
||||
You have to commit the changes to `go.mod` and `go.sum` before submitting the pull request.
|
||||
|
||||
### Upgrading dependencies
|
||||
|
||||
If you need to upgrade a direct or indirect dependency, you can do it like so, $MODULE being the dependency in question: `go get -u $MODULE`. The corresponding entry in go.mod should then have the version you specified; if it's an indirect dependency, the entry should have the `// indirect` comment. Follow this by executing `go mod tidy`, to ensure that go.mod and go.sum are up to date. If the indirect dependency turns out to not be used (transitively) by any of our packages, `go mod tidy` will actually strip it from go.mod. In that case, you can just ignore it since it isn't used in the end.
|
||||
|
||||
## Node.js dependencies
|
||||
|
||||
Updated using `yarn`.
|
||||
@@ -50,9 +54,9 @@ Updated using `yarn`.
|
||||
|
||||
## Where to make changes
|
||||
|
||||
### CircleCI
|
||||
### Drone
|
||||
|
||||
Our builds run on CircleCI through our build script.
|
||||
Our CI builds run on Drone.
|
||||
|
||||
#### Files
|
||||
|
||||
@@ -66,7 +70,7 @@ Our builds run on CircleCI through our build script.
|
||||
|
||||
### grafana/build-container
|
||||
|
||||
The main build step (in CircleCI) is built using a custom build container that comes pre-baked with some of the necessary dependencies.
|
||||
The main build steps (in Drone) happen using a custom Docker image that comes pre-baked with some of the necessary dependencies.
|
||||
|
||||
Link: [grafana/build-container](https://github.com/grafana/grafana/tree/main/scripts/build/ci-build)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,6 @@
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": 441,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
@@ -56,19 +55,21 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 24,
|
||||
"h": 10,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 9,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 1,
|
||||
"groupWidth": 0.82,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"calcs": [
|
||||
"max"
|
||||
],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
"placement": "right"
|
||||
},
|
||||
"orientation": "auto",
|
||||
"showValue": "auto",
|
||||
@@ -79,11 +80,85 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nNegative, 15, -5\nLong value, 15,10",
|
||||
"refId": "A",
|
||||
"scenarioId": "categorical_data"
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "Plenty od data, automatic value sizing",
|
||||
"title": "Auto sizing & auto show values",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"description": "Should be smaller given the longer value",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"fillOpacity": 80,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineWidth": 0
|
||||
},
|
||||
"decimals": 2,
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 15,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 0.82,
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"max"
|
||||
],
|
||||
"displayMode": "list",
|
||||
"placement": "right"
|
||||
},
|
||||
"orientation": "auto",
|
||||
"showValue": "auto",
|
||||
"text": {},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nNegative, 15, -5\nLong value, 15,10",
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "Auto sizing & auto show values",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
@@ -124,10 +199,152 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"w": 24,
|
||||
"h": 11,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 13
|
||||
"y": 10
|
||||
},
|
||||
"id": 16,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 0.89,
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"max"
|
||||
],
|
||||
"displayMode": "list",
|
||||
"placement": "right"
|
||||
},
|
||||
"orientation": "auto",
|
||||
"showValue": "auto",
|
||||
"text": {},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"csvContent": "Name,Stat1,Stat2,Stat3,Stat4,Stat5,Stat6,Stat7,Stat8,Stat9,Stat10\nA, 10, 15,8,3,4,12,14,1,5,10\nB, 19, 5,8,3,4,12,14,6,7,2\nC, 15, 5,8,3,4,10,4,6,7,2\n",
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "auto show values & No room for value",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"fillOpacity": 80,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineWidth": 0
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 10
|
||||
},
|
||||
"id": 17,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 0.89,
|
||||
"legend": {
|
||||
"calcs": [
|
||||
"max"
|
||||
],
|
||||
"displayMode": "list",
|
||||
"placement": "right"
|
||||
},
|
||||
"orientation": "auto",
|
||||
"showValue": "always",
|
||||
"text": {},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"csvContent": "Name,Stat1,Stat2,Stat3,Stat4,Stat5,Stat6,Stat7,Stat8,Stat9,Stat10\nA, 10, 15,8,3,4,12,14,1,5,10\nB, 19, 5,8,3,4,12,14,6,7,2\nC, 15, 5,8,3,4,10,4,6,7,2\n",
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "auto show values & Always show value",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": "-- Dashboard --",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"fillOpacity": 80,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineWidth": 0
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 10
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
@@ -142,7 +359,7 @@
|
||||
"showValue": "auto",
|
||||
"text": {
|
||||
"size": 10,
|
||||
"valueSize": 10
|
||||
"valueSize": 25
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
@@ -150,11 +367,11 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "categorical_data"
|
||||
"panelId": 9,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Plenty od data, fixed value sizing",
|
||||
"title": "Fixed value sizing",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
@@ -195,267 +412,42 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"h": 11,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 25
|
||||
"y": 21
|
||||
},
|
||||
"id": 11,
|
||||
"id": 18,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 1,
|
||||
"groupWidth": 0.82,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"calcs": [
|
||||
"max"
|
||||
],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"orientation": "auto",
|
||||
"showValue": "auto",
|
||||
"text": {
|
||||
"size": 10
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "categorical_data"
|
||||
}
|
||||
],
|
||||
"title": "Auto font size, value auto visible",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "filterByValue",
|
||||
"options": {
|
||||
"filters": [
|
||||
{
|
||||
"config": {
|
||||
"id": "equal",
|
||||
"options": {
|
||||
"value": "Bedroom"
|
||||
}
|
||||
},
|
||||
"fieldName": "location"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "equal",
|
||||
"options": {
|
||||
"value": "Cellar"
|
||||
}
|
||||
},
|
||||
"fieldName": "location"
|
||||
}
|
||||
],
|
||||
"match": "any",
|
||||
"type": "include"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"fillOpacity": 80,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineWidth": 0
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 25
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 1,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"orientation": "auto",
|
||||
"showValue": "always",
|
||||
"text": {
|
||||
"size": 10
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "categorical_data"
|
||||
}
|
||||
],
|
||||
"title": "Auto font size, value always visible",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "filterByValue",
|
||||
"options": {
|
||||
"filters": [
|
||||
{
|
||||
"config": {
|
||||
"id": "equal",
|
||||
"options": {
|
||||
"value": "Bedroom"
|
||||
}
|
||||
},
|
||||
"fieldName": "location"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "equal",
|
||||
"options": {
|
||||
"value": "Cellar"
|
||||
}
|
||||
},
|
||||
"fieldName": "location"
|
||||
}
|
||||
],
|
||||
"match": "any",
|
||||
"type": "include"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"axisSoftMin": 0,
|
||||
"fillOpacity": 80,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"graph": false,
|
||||
"legend": false,
|
||||
"tooltip": false
|
||||
},
|
||||
"lineWidth": 0
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 37
|
||||
},
|
||||
"id": 13,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 1,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
"placement": "right"
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"showValue": "auto",
|
||||
"text": {
|
||||
"size": 10
|
||||
},
|
||||
"text": {},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nLong value, 15,10",
|
||||
"refId": "A",
|
||||
"scenarioId": "categorical_data"
|
||||
}
|
||||
],
|
||||
"title": "Auto font size, value auto visible",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "filterByValue",
|
||||
"options": {
|
||||
"filters": [
|
||||
{
|
||||
"config": {
|
||||
"id": "equal",
|
||||
"options": {
|
||||
"value": "Bedroom"
|
||||
}
|
||||
},
|
||||
"fieldName": "location"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "equal",
|
||||
"options": {
|
||||
"value": "Cellar"
|
||||
}
|
||||
},
|
||||
"fieldName": "location"
|
||||
}
|
||||
],
|
||||
"match": "any",
|
||||
"type": "include"
|
||||
}
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "Auto sizing & auto show values",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@@ -492,65 +484,37 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"h": 11,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 37
|
||||
"y": 21
|
||||
},
|
||||
"id": 14,
|
||||
"id": 19,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 1,
|
||||
"groupWidth": 0.89,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"calcs": [
|
||||
"max"
|
||||
],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
"placement": "right"
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"showValue": "always",
|
||||
"text": {
|
||||
"size": 10
|
||||
},
|
||||
"showValue": "auto",
|
||||
"text": {},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"csvContent": "Name,Stat1,Stat2,Stat3,Stat4,Stat5,Stat6,Stat7,Stat8,Stat9,Stat10\nA, 10, 15,8,3,4,12,14,1,5,10\nB, 19, 5,8,3,4,12,14,6,7,2\nC, 15, 5,8,3,4,10,4,6,7,2\n",
|
||||
"refId": "A",
|
||||
"scenarioId": "categorical_data"
|
||||
}
|
||||
],
|
||||
"title": "Auto font size, value always visible",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "filterByValue",
|
||||
"options": {
|
||||
"filters": [
|
||||
{
|
||||
"config": {
|
||||
"id": "equal",
|
||||
"options": {
|
||||
"value": "Bedroom"
|
||||
}
|
||||
},
|
||||
"fieldName": "location"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"id": "equal",
|
||||
"options": {
|
||||
"value": "Cellar"
|
||||
}
|
||||
},
|
||||
"fieldName": "location"
|
||||
}
|
||||
],
|
||||
"match": "any",
|
||||
"type": "include"
|
||||
}
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "auto show values & little room",
|
||||
"type": "barchart"
|
||||
}
|
||||
],
|
||||
@@ -571,7 +535,7 @@
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "BarChart - value text sizing",
|
||||
"title": "BarChart - Panel Tests - Value sizing",
|
||||
"uid": "WFlOM-jM1",
|
||||
"version": 9
|
||||
}
|
||||
"version": 3
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
ORIGIN_SERVER=http://localhost:9090/
|
||||
ORIGIN_SERVER=http://host.docker.internal:9090/
|
||||
SLEEP_DURATION=60s
|
||||
@@ -1,7 +1,10 @@
|
||||
|
||||
FROM golang:latest
|
||||
FROM golang:latest as builder
|
||||
ADD main.go /
|
||||
WORKDIR /
|
||||
RUN GO111MODULE=off go build -o main .
|
||||
RUN GO111MODULE=off CGO_ENABLED=0 go build -o main .
|
||||
|
||||
FROM scratch
|
||||
WORKDIR /
|
||||
EXPOSE 3011
|
||||
COPY --from=builder /main /main
|
||||
ENTRYPOINT ["/main"]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
slow_proxy:
|
||||
build: docker/blocks/slow_proxy
|
||||
network_mode: host
|
||||
ports:
|
||||
- "3011:3011"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
ORIGIN_SERVER: ${ORIGIN_SERVER}
|
||||
SLEEP_DURATION: ${SLEEP_DURATION}
|
||||
@@ -12,12 +12,14 @@ import (
|
||||
func main() {
|
||||
origin := os.Getenv("ORIGIN_SERVER")
|
||||
if origin == "" {
|
||||
origin = "http://localhost:9090/"
|
||||
// it is never not-set, the default is in the `.env` file
|
||||
log.Fatalf("missing env-variable ORIGIN_SERVER")
|
||||
}
|
||||
|
||||
sleepDurationStr := os.Getenv("SLEEP_DURATION")
|
||||
if sleepDurationStr == "" {
|
||||
sleepDurationStr = "60s"
|
||||
// it is never not-set, the default is in the `.env` file
|
||||
log.Fatalf("missing env-variable SLEEP_DURATION")
|
||||
}
|
||||
|
||||
sleep, err := time.ParseDuration(sleepDurationStr)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
ORIGIN_SERVER=http://host.docker.internal:9090/
|
||||
SLEEP_DURATION=60s
|
||||
@@ -1,10 +0,0 @@
|
||||
FROM golang:latest as builder
|
||||
ADD main.go /
|
||||
WORKDIR /
|
||||
RUN GO111MODULE=off CGO_ENABLED=0 go build -o main .
|
||||
|
||||
FROM scratch
|
||||
WORKDIR /
|
||||
EXPOSE 3011
|
||||
COPY --from=builder /main /main
|
||||
ENTRYPOINT ["/main"]
|
||||
@@ -1,7 +0,0 @@
|
||||
slow_proxy_mac:
|
||||
build: docker/blocks/slow_proxy_mac
|
||||
ports:
|
||||
- '3011:3011'
|
||||
environment:
|
||||
ORIGIN_SERVER: ${ORIGIN_SERVER}
|
||||
SLEEP_DURATION: ${SLEEP_DURATION}
|
||||
@@ -1,40 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
origin := os.Getenv("ORIGIN_SERVER")
|
||||
if origin == "" {
|
||||
// it is never not-set, the default is in the `.env` file
|
||||
log.Fatalf("missing env-variable ORIGIN_SERVER")
|
||||
}
|
||||
|
||||
sleepDurationStr := os.Getenv("SLEEP_DURATION")
|
||||
if sleepDurationStr == "" {
|
||||
// it is never not-set, the default is in the `.env` file
|
||||
log.Fatalf("missing env-variable SLEEP_DURATION")
|
||||
}
|
||||
|
||||
sleep, err := time.ParseDuration(sleepDurationStr)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse SLEEP_DURATION: %v", err)
|
||||
}
|
||||
|
||||
originURL, _ := url.Parse(origin)
|
||||
proxy := httputil.NewSingleHostReverseProxy(originURL)
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("sleeping for %s then proxying request: url '%s', headers: '%v'", sleep.String(), r.RequestURI, r.Header)
|
||||
<-time.After(sleep)
|
||||
proxy.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
log.Fatal(http.ListenAndServe(":3011", nil))
|
||||
}
|
||||
@@ -7,7 +7,7 @@ weight = 300
|
||||
|
||||
# Image rendering
|
||||
|
||||
Grafana supports automatic rendering of panels as PNG images. This allows Grafana to automatically generate images of your panels to include in [alert notifications]({{< relref "../alerting/notifications.md" >}}).
|
||||
Grafana supports automatic rendering of panels as PNG images. This allows Grafana to automatically generate images of your panels to include in [alert notifications]({{< relref "../alerting/old-alerting/notifications.md" >}}).
|
||||
|
||||
>**Note:** Image rendering of dashboards is not supported at this time.
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ Since not all datasources have the same configuration settings we only have the
|
||||
| timeInterval | string | Prometheus, Elasticsearch, InfluxDB, MySQL, PostgreSQL and MSSQL | Lowest interval/step value that should be used for this data source. |
|
||||
| httpMode | string | Influxdb | HTTP Method. 'GET', 'POST', defaults to GET |
|
||||
| maxSeries | number | Influxdb | Max number of series/tables that Grafana processes |
|
||||
| httpMethod | string | Prometheus | HTTP Method. 'GET', 'POST', defaults to GET |
|
||||
| httpMethod | string | Prometheus | HTTP Method. 'GET', 'POST', defaults to POST |
|
||||
| customQueryParameters | string | Prometheus | Query parameters to add, as a URL-encoded string. |
|
||||
| esVersion | string | Elasticsearch | Elasticsearch version (E.g. `7.0.0`, `7.6.1`) |
|
||||
| timeField | string | Elasticsearch | Which field that should be used as timestamp |
|
||||
|
||||
@@ -22,7 +22,7 @@ Grafana will now persist all long term data in the database. How to configure th
|
||||
|
||||
## Alerting
|
||||
|
||||
Currently alerting supports a limited form of high availability. [Alert notifications]({{< relref "../alerting/notifications.md" >}}) are deduplicated when running multiple servers. This means all alerts are executed on every server but alert notifications are only sent once per alert. Grafana does not support load distribution between servers.
|
||||
Currently alerting supports a limited form of high availability. [Alert notifications]({{< relref "../alerting/old-alerting/notifications.md" >}}) are deduplicated when running multiple servers. This means all alerts are executed on every server but alert notifications are only sent once per alert. Grafana does not support load distribution between servers.
|
||||
|
||||
## User sessions
|
||||
|
||||
|
||||
@@ -8,21 +8,24 @@ weight = 110
|
||||
|
||||
Alerts allow you to know about problems in your systems moments after they occur. Robust and actionable alerts help you identify and resolve issues quickly, minimizing disruption to your services.
|
||||
|
||||
> **Note:** In Grafana 8.0, you can optionally choose the beta version of our new [unified altering]({{< relref "./unified-alerting/_index.md" >}}) system. Out of the box, Grafana still supports the [old alters]({{< relref "./old-alerting/_index.md" >}}) .
|
||||
|
||||
|
||||
Alerts have four main components:
|
||||
|
||||
- Alert rule - One or more conditions, the frequency of evaluation, and the (optional) duration that a condition must be met before notifying.
|
||||
- Contact point - A channel for sending notifications when the conditions of an alert rule are met.
|
||||
- Contact point - A channel for sending notifications when the conditions of an alert rule are met. (Available in unified alerting only.)
|
||||
- Notification policy - A set of matching and grouping criteria used to determine where, and how frequently, to send notifications.
|
||||
- Silences - Date and matching criteria used to silence notifications.
|
||||
- Silences - Date and matching criteria used to silence notifications. (Available in unified alerting only.)
|
||||
|
||||
## Alert tasks
|
||||
|
||||
You can perform the following tasks for alerts:
|
||||
|
||||
- [Create an alert rule]({{< relref "create-alerts.md" >}})
|
||||
- [View existing alert rules and their current state]({{< relref "view-alerts.md" >}})
|
||||
- [Test alert rules and troubleshoot]({{< relref "troubleshoot-alerts.md" >}})
|
||||
- [Add or edit an alert contact point]({{< relref "notifications.md" >}})
|
||||
- [Create an alert rule]({{< relref "./old-alerting/create-alerts.md" >}})
|
||||
- [View existing alert rules and their current state]({{< relref "./old-alerting/view-alerts.md" >}})
|
||||
- [Test alert rules and troubleshoot]({{< relref "./old-alerting/troubleshoot-alerts.md" >}})
|
||||
- [Add or edit an alert contact point]({{< relref "./old-alerting/notifications.md" >}})
|
||||
|
||||
## Clustering
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
+++
|
||||
title = "Difference between new and old alerts"
|
||||
title = " Difference between unified and old Grafana alerts"
|
||||
description = "Pause an existing alert rule"
|
||||
keywords = ["grafana", "alerting", "guide", "rules", "view"]
|
||||
weight = 400
|
||||
weight = 112
|
||||
+++
|
||||
|
||||
# Difference between new and old alerts
|
||||
# Difference between unified and old Grafana alerts
|
||||
|
||||
The `ngalert` feature toggle enables the beta version of our new alerting system.
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
+++
|
||||
title = "Old Grafana Alerts"
|
||||
aliases = ["/docs/grafana/latest/alerting/rules/", "/docs/grafana/latest/alerting/metrics/"]
|
||||
weight = 114
|
||||
+++
|
||||
|
||||
# Old Grafana alerts
|
||||
|
||||
Alerts allow you to know about problems in your systems moments after they occur. Robust and actionable alerts help you identify and resolve issues quickly, minimizing disruption to your services.
|
||||
|
||||
Alerts have four main components:
|
||||
|
||||
- Alert rule - One or more conditions, the frequency of evaluation, and the (optional) duration that a condition must be met before notifying.
|
||||
- Contact point - A channel for sending notifications when the conditions of an alert rule are met.
|
||||
- Notification policy - A set of matching and grouping criteria used to determine where, and how frequently, to send notifications.
|
||||
- Silences - Date and matching criteria used to silence notifications.
|
||||
|
||||
## Alert tasks
|
||||
|
||||
You can perform the following tasks for alerts:
|
||||
|
||||
- [Create an alert rule]({{< relref "create-alerts.md" >}})
|
||||
- [View existing alert rules and their current state]({{< relref "view-alerts.md" >}})
|
||||
- [Test alert rules and troubleshoot]({{< relref "troubleshoot-alerts.md" >}})
|
||||
- [Add or edit an alert contact point]({{< relref "notifications.md" >}})
|
||||
|
||||
## Clustering
|
||||
|
||||
Currently alerting supports a limited form of high availability. Since v4.2.0 of Grafana, alert notifications are deduped when running multiple servers. This means all alerts are executed on every server but no duplicate alert notifications are sent due to the deduping logic. Proper load balancing of alerts will be introduced in the future.
|
||||
|
||||
## Alert evaluation
|
||||
|
||||
Grafana managed alerts are evaluated by the Grafana backend. Rule evaluations are scheduled, according to the alert rule configuration, and queries are evaluated by an engine that is part of core Grafana.
|
||||
|
||||
Alert rules can only query backend data sources with alerting enabled:
|
||||
- builtin or developed and maintained by grafana: `Graphite`, `Prometheus`, `Loki`, `InfluxDB`, `Elasticsearch`,
|
||||
`Google Cloud Monitoring`, `Cloudwatch`, `Azure Monitor`, `MySQL`, `PostgreSQL`, `MSSQL`, `OpenTSDB`, `Oracle`, and `Azure Data Explorer`
|
||||
- any community backend data sources with alerting enabled (`backend` and `alerting` properties are set in the [plugin.json]({{< relref "../../developers/plugins/metadata.md" >}}))
|
||||
|
||||
## Metrics from the alert engine
|
||||
|
||||
The alert engine publishes some internal metrics about itself. You can read more about how Grafana publishes [internal metrics]({{< relref "../../administration/view-server/internal-metrics.md" >}}).
|
||||
|
||||
Metric Name | Type | Description
|
||||
---------- | ----------- | ----------
|
||||
`alerting.alerts` | gauge | How many alerts by state
|
||||
`alerting.request_duration_seconds` | histogram | Histogram of requests to the Alerting API
|
||||
`alerting.active_configurations` | gauge | The number of active, non default alertmanager configurations for grafana managed alerts
|
||||
`alerting.rule_evaluations_total` | counter | The total number of rule evaluations
|
||||
`alerting.rule_evaluation_failures_total` | counter | The total number of rule evaluation failures
|
||||
`alerting.rule_evaluation_duration_seconds` | summary | The duration for a rule to execute
|
||||
`alerting.rule_group_rules` | gauge | The number of rules
|
||||
+1
-1
@@ -37,7 +37,7 @@ This section describes the fields you fill out to create an alert.
|
||||
- **Evaluate every -** Specify how often the scheduler should evaluate the alert rule. This is referred to as the _evaluation interval_.
|
||||
- **For -** Specify how long the query needs to violate the configured thresholds before the alert notification triggers.
|
||||
|
||||
You can set a minimum evaluation interval in the `alerting.min_interval_seconds` configuration field, to set a minimum time between evaluations. Refer to [Configuration]({{< relref "../administration/configuration.md" >}}#min-interval-seconds) for more information.
|
||||
You can set a minimum evaluation interval in the `alerting.min_interval_seconds` configuration field, to set a minimum time between evaluations. Refer to [Configuration]({{< relref "../../administration/configuration.md" >}}#min-interval-seconds) for more information.
|
||||
|
||||
> **Caution:** Do not use `For` with the `If no data or all values are null` setting set to `No Data`. The triggering of `No Data` will trigger instantly and not take `For` into consideration. This may also result in that an OK notification not being sent if alert transitions from `No Data -> Pending -> OK`.
|
||||
|
||||
+5
-5
@@ -74,7 +74,7 @@ VictorOps | `victorops` | yes, external only | yes
|
||||
|
||||
### Email
|
||||
|
||||
To enable email notifications you have to set up [SMTP settings]({{< relref "../administration/configuration/#smtp" >}})
|
||||
To enable email notifications you have to set up [SMTP settings]({{< relref "../../administration/configuration/#smtp" >}})
|
||||
in the Grafana config. Email notifications will upload an image of the alert graph to an
|
||||
external image destination if available or fallback to attaching the image to the email.
|
||||
Be aware that if you use the `local` image storage email servers and clients might not be
|
||||
@@ -252,19 +252,19 @@ Alertmanager handles alerts sent by client applications such as Prometheus serve
|
||||
## Enable images in notifications {#external-image-store}
|
||||
|
||||
Grafana can render the panel associated with the alert rule as a PNG image and include that in the notification. Read more about the requirements and how to configure
|
||||
[image rendering]({{< relref "../administration/image_rendering/" >}}).
|
||||
[image rendering]({{< relref "../../administration/image_rendering/" >}}).
|
||||
|
||||
You must configure an [external image storage provider]({{< relref "../administration/configuration/#external-image-storage" >}}) in order to receive images in alert notifications. If your notification channel requires that the image be publicly accessible (e.g. Slack, PagerDuty), configure a provider which uploads the image to a remote image store like Amazon S3, Webdav, Google Cloud Storage, or Azure Blob Storage. Otherwise, the local provider can be used to serve the image directly from Grafana.
|
||||
You must configure an [external image storage provider]({{< relref "../../administration/configuration/#external-image-storage" >}}) in order to receive images in alert notifications. If your notification channel requires that the image be publicly accessible (e.g. Slack, PagerDuty), configure a provider which uploads the image to a remote image store like Amazon S3, Webdav, Google Cloud Storage, or Azure Blob Storage. Otherwise, the local provider can be used to serve the image directly from Grafana.
|
||||
|
||||
Notification services which need public image access are marked as 'external only'.
|
||||
|
||||
## Configure the link back to Grafana from alert notifications
|
||||
|
||||
All alert notifications contain a link back to the triggered alert in the Grafana instance.
|
||||
This URL is based on the [domain]({{< relref "../administration/configuration/#domain" >}}) setting in Grafana.
|
||||
This URL is based on the [domain]({{< relref "../../administration/configuration/#domain" >}}) setting in Grafana.
|
||||
|
||||
## Notification templating
|
||||
|
||||
> **Note:** Alert notification templating is only available in Grafana v7.4 and above.
|
||||
|
||||
The alert notification template feature allows you to take the [label]({{< relref "../basics/timeseries-dimensions.md#labels" >}}) value from an alert query and [inject that into alert notifications]({{< relref "./add-notification-template.md" >}}).
|
||||
The alert notification template feature allows you to take the [label]({{< relref "../../basics/timeseries-dimensions.md#labels" >}}) value from an alert query and [inject that into alert notifications]({{< relref "./add-notification-template.md" >}}).
|
||||
+6
-3
@@ -1,8 +1,11 @@
|
||||
+++
|
||||
title = "Alerts"
|
||||
title = "Unified Alerts"
|
||||
aliases = ["/docs/grafana/latest/alerting/rules/", "/docs/grafana/latest/alerting/metrics/"]
|
||||
weight = 110
|
||||
weight = 113
|
||||
+++
|
||||
|
||||
# Alerts v2.0 overview
|
||||
# Unified alerts
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ In this case the labels that represent the dimensions will have two keys based o
|
||||
|
||||
> **Note:** More than one dimension is currently only supported in the Logs queries within the Azure Monitor service as of version 7.1.
|
||||
|
||||
> **Note:** Multiple dimensions are not supported in a way that maps to multiple alerts in Grafana, but rather they are treated as multiple conditions to a single alert. See the documentation on [creating alerts with multiple series]({{< relref "../alerting/create-alerts.md#multiple-series" >}}).
|
||||
> **Note:** Multiple dimensions are not supported in a way that maps to multiple alerts in Grafana, but rather they are treated as multiple conditions to a single alert. See the documentation on [creating alerts with multiple series]({{< relref "../alerting/old-alerting/create-alerts.md#multiple-series" >}}).
|
||||
|
||||
### Multiple values
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ However, one limitation with these plugins are that they execute on the client-s
|
||||
|
||||
We use the term _backend plugin_ to denote that a plugin has a backend component. Still, normally a backend plugin requires frontend components as well. This is for example true for backend data source plugins which normally need configuration and query editor components implemented for the frontend.
|
||||
|
||||
Data source plugins can be extended with a backend component. In the future we plan to support additional types and possibly new kinds of plugins, such as [notifiers for Grafana Alerting]({{< relref "../../../alerting/notifications.md" >}}) and custom authentication to name a few.
|
||||
Data source plugins can be extended with a backend component. In the future we plan to support additional types and possibly new kinds of plugins, such as [notifiers for Grafana Alerting]({{< relref "../../../alerting/old-alerting/notifications.md" >}}) and custom authentication to name a few.
|
||||
|
||||
## Use cases for implementing a backend plugin
|
||||
|
||||
|
||||
@@ -41,15 +41,15 @@ Note that it is only possibly to provision [organization local]({{< relref "./ro
|
||||
|
||||
## Manage default built-in role assignments
|
||||
|
||||
During the startup, Grafana creates [default built-in role assignments]({{< relref "./roles#default-built-in-role-assignments" >}}) with [predefined roles]({{< relref "./roles#predefined-roles" >}}). You can remove and add back later those assignments by using provisioning.
|
||||
During the startup, Grafana creates [default built-in role assignments]({{< relref "./roles#default-built-in-role-assignments" >}}) with [fixed roles]({{< relref "./roles#fixed-roles" >}}). You can remove and add back later those assignments by using provisioning.
|
||||
|
||||
### Remove default assignment
|
||||
|
||||
To remove default built-in role assignment, you can use `removeDefaultAssignments` element in the configuration file. You would need to provide built-in role name and predefined role name.
|
||||
To remove default built-in role assignment, you can use `removeDefaultAssignments` element in the configuration file. You would need to provide built-in role name and fixed role name.
|
||||
|
||||
### Add back default assignment
|
||||
|
||||
To add back default built-in role assignment, you can use `addDefaultAssignments` element in the configuration file. You would need to provide built-in role name and predefined role name.
|
||||
To add back default built-in role assignment, you can use `addDefaultAssignments` element in the configuration file. You would need to provide built-in role name and fixed role name.
|
||||
|
||||
## Example of a role configuration file
|
||||
|
||||
@@ -61,15 +61,15 @@ apiVersion: 1
|
||||
removeDefaultAssignments:
|
||||
# <string>, must be one of the Organization roles (`Viewer`, `Editor`, `Admin`) or `Grafana Admin`
|
||||
- builtInRole: "Grafana Admin"
|
||||
# <string>, must be one of the existing predefined roles
|
||||
predefinedRole: "grafana:roles:permissions:admin"
|
||||
# <string>, must be one of the existing fixed roles
|
||||
fixedRole: "fixed:permissions:admin"
|
||||
|
||||
# list of default built-in role assignments that should be added back
|
||||
addDefaultAssignments:
|
||||
# <string>, must be one of the Organization roles (`Viewer`, `Editor`, `Admin`) or `Grafana Admin`
|
||||
- builtInRole: "Admin"
|
||||
# <string>, must be one of the existing predefined roles
|
||||
predefinedRole: "grafana:roles:reporting:admin:read"
|
||||
# <string>, must be one of the existing fixed roles
|
||||
fixedRole: "fixed:reporting:admin:read"
|
||||
|
||||
# list of roles that should be deleted
|
||||
deleteRoles:
|
||||
@@ -127,7 +127,7 @@ A basic set of validation rules are applied to the input `yaml` files.
|
||||
### Roles
|
||||
|
||||
- `name` must not be empty
|
||||
- `name` must not have `grafana:roles:` prefix.
|
||||
- `name` must not have `fixed:` prefix.
|
||||
|
||||
### Built-in role assignments
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
+++
|
||||
title = "Roles"
|
||||
description = "Understand roles in fine-grained access control"
|
||||
keywords = ["grafana", "fine-grained-access-control", "roles", "predefined-roles", "built-in-role-assignments", "permissions", "enterprise"]
|
||||
keywords = ["grafana", "fine-grained-access-control", "roles", "fixed-roles", "built-in-role-assignments", "permissions", "enterprise"]
|
||||
weight = 105
|
||||
+++
|
||||
|
||||
@@ -10,7 +10,7 @@ weight = 105
|
||||
A role represents set of permissions that allow you to perform specific actions on Grafana resources. Refer to [Permissions]({{< relref "./permissions.md" >}}) to understand how permissions work.
|
||||
|
||||
There are two types of roles:
|
||||
- [Predefined roles]({{< relref "./roles.md#predefined-roles" >}}), which provide granular access for specific resources within Grafana and are managed by the Grafana itself.
|
||||
- [Fixed roles]({{< relref "./roles.md#fixed-roles" >}}), which provide granular access for specific resources within Grafana and are managed by the Grafana itself.
|
||||
- [Custom roles]({{< relref "./roles.md#custom-roles.md" >}}), which provide granular access based on the user specified set of permissions.
|
||||
|
||||
You can use [Fine-grained access control API]({{< relref "../../http_api/access_control.md" >}}) to list available roles and permissions.
|
||||
@@ -19,27 +19,27 @@ You can use [Fine-grained access control API]({{< relref "../../http_api/access_
|
||||
|
||||
A role can be either _global_ or _organization local_. _Global_ roles are not mapped to any specific organization and can be reused across multiple organizations, whereas _organization local_ roles are only available for that specific organization.
|
||||
|
||||
## Predefined roles
|
||||
## Fixed roles
|
||||
|
||||
Predefined roles provide convenience and guarantee of consistent behaviour by combining relevant [permissions]({{< relref "./permissions.md" >}}) together. Predefined roles are created and updated by the Grafana, during the startup.
|
||||
There are few basic rules for predefined roles:
|
||||
Fixed roles provide convenience and guarantee of consistent behaviour by combining relevant [permissions]({{< relref "./permissions.md" >}}) together. Fixed roles are created and updated by Grafana during startup.
|
||||
There are few basic rules for fixed roles:
|
||||
|
||||
- All predefined roles are _global_ by default
|
||||
- All predefined roles have a `grafana:roles:` prefix.
|
||||
- You can’t change or delete a predefined role.
|
||||
- All fixed roles are _global_.
|
||||
- All fixed roles have a `fixed:` prefix.
|
||||
- You can’t change or delete a fixed role.
|
||||
|
||||
Role name | Permissions | Description
|
||||
--- | --- | ---
|
||||
grafana:roles:permissions:admin:read | roles:read<br>roles:list<br>roles.builtin:list | Allows to list and get available roles and built-in role assignments.
|
||||
grafana:roles:permissions:admin:edit | All permissions from `grafana:roles:permissions:admin:read` and <br>roles:write<br>roles:delete<br>roles.builtin:add<br>roles.builtin:remove | Allows every read action and in addition allows to create, change and delete custom roles and create or remove built-in role assignments.
|
||||
grafana:roles:reporting:admin:read | reports:read<br>reports:send<br>reports.settings:read | Allows to read reports and report settings.
|
||||
grafana:roles:reporting:admin:edit | All permissions from `grafana:roles:reporting:admin:read` and <br>reports.admin:write<br>reports:delete<br>reports.settings:write | Allows every read action for reports and in addition allows to administer reports.
|
||||
grafana:roles:users:admin:read | users.authtoken:list<br>users.quotas:list<br>users:read<br>users.teams:read | Allows to list and get users and related information.
|
||||
grafana:roles:users:admin:edit | All permissions from `grafana:roles:users:admin:read` and <br>users.password:update<br>users:write<br>users:create<br>users:delete<br>users:enable<br>users:disable<br>users.permissions:update<br>users:logout<br>users.authtoken:update<br>users.quotas:update | Allows every read action for users and in addition allows to administer users.
|
||||
grafana:roles:users:org:read | org.users:read | Allows to get user organizations.
|
||||
grafana:roles:users:org:edit | All permissions from `grafana:roles:users:org:read` and <br>org.users:add<br>org.users:remove<br>org.users.role:update | Allows every read action for user organizations and in addition allows to administer user organizations.
|
||||
grafana:roles:ldap:admin:read | ldap.user:read<br>ldap.status:read | Allows to read LDAP information and status.
|
||||
grafana:roles:ldap:admin:edit | All permissions from `grafana:roles:ldap:admin:read` and <br>ldap.user:sync | Allows every read action for LDAP and in addition allows to administer LDAP.
|
||||
fixed:permissions:admin:read | roles:read<br>roles:list<br>roles.builtin:list | Allows to list and get available roles and built-in role assignments.
|
||||
fixed:permissions:admin:edit | All permissions from `fixed:permissions:admin:read` and <br>roles:write<br>roles:delete<br>roles.builtin:add<br>roles.builtin:remove | Allows every read action and in addition allows to create, change and delete custom roles and create or remove built-in role assignments.
|
||||
fixed:reporting:admin:read | reports:read<br>reports:send<br>reports.settings:read | Allows to read reports and report settings.
|
||||
fixed:reporting:admin:edit | All permissions from `fixed:reporting:admin:read` and <br>reports.admin:write<br>reports:delete<br>reports.settings:write | Allows every read action for reports and in addition allows to administer reports.
|
||||
fixed:users:admin:read | users.authtoken:list<br>users.quotas:list<br>users:read<br>users.teams:read | Allows to list and get users and related information.
|
||||
fixed:users:admin:edit | All permissions from `fixed:users:admin:read` and <br>users.password:update<br>users:write<br>users:create<br>users:delete<br>users:enable<br>users:disable<br>users.permissions:update<br>users:logout<br>users.authtoken:update<br>users.quotas:update | Allows every read action for users and in addition allows to administer users.
|
||||
fixed:users:org:read | org.users:read | Allows to get user organizations.
|
||||
fixed:users:org:edit | All permissions from `fixed:users:org:read` and <br>org.users:add<br>org.users:remove<br>org.users.role:update | Allows every read action for user organizations and in addition allows to administer user organizations.
|
||||
fixed:ldap:admin:read | ldap.user:read<br>ldap.status:read | Allows to read LDAP information and status.
|
||||
fixed:ldap:admin:edit | All permissions from `fixed:ldap:admin:read` and <br>ldap.user:sync | Allows every read action for LDAP and in addition allows to administer LDAP.
|
||||
|
||||
## Custom roles
|
||||
|
||||
@@ -52,7 +52,7 @@ To create, update or delete a custom role, you can use the [Fine-grained access
|
||||
A role's name is intended as a human friendly identifier for the role, helping administrators understand the purpose of a role. The name cannot be longer than 190 characters, and we recommend using ASCII characters.
|
||||
Role names must be unique within an organization.
|
||||
|
||||
Roles with names prefixed by `grafana:roles:` are predefined roles created by Grafana and cannot be created or modified by users.
|
||||
Roles with names prefixed by `fixed:` are fixed roles created by Grafana and cannot be created or modified by users.
|
||||
|
||||
##### Role version
|
||||
|
||||
@@ -79,7 +79,7 @@ Note that you won't be able to create, update or delete a custom role with permi
|
||||
|
||||
## Built-in role assignments
|
||||
|
||||
To control what your users can access or not, you can assign or unassign [Custom roles]({{< ref "#custom-roles" >}}) or [Predefined roles]({{< ref "#predefined-roles" >}}) to the existing [Organization roles]({{< relref "../../permissions/organization_roles.md" >}}) or to [Grafana Server Admin]({{< relref "../../permissions/_index.md#grafana-server-admin-role" >}}) role.
|
||||
To control what your users can access or not, you can assign or unassign [Custom roles]({{< ref "#custom-roles" >}}) or [Fixed roles]({{< ref "#fixed-roles" >}}) to the existing [Organization roles]({{< relref "../../permissions/organization_roles.md" >}}) or to [Grafana Server Admin]({{< relref "../../permissions/_index.md#grafana-server-admin-role" >}}) role.
|
||||
These assignments are called built-in role assignments.
|
||||
|
||||
During startup, Grafana will create default assignments for you. When you make any changes to the built-on role assignments, Grafana will take them into account and won’t overwrite during next start.
|
||||
@@ -97,5 +97,5 @@ You can only create _organization local_ assignments for _organization local_ ro
|
||||
|
||||
Built-in role | Associated role | Description
|
||||
--- | --- | ---
|
||||
Grafana Admin | grafana:roles:permissions:admin:edit<br>grafana:roles:permissions:admin:read<br>grafana:roles:reporting:admin:edit<br>grafana:roles:reporting:admin:read<br>grafana:roles:users:admin:edit<br>grafana:roles:users:admin:read<br>grafana:roles:users:org:edit<br>grafana:roles:users:org:read<br>grafana:roles:ldap:admin:edit<br>grafana:roles:ldap:admin:read | Allows access to resources which [Grafana Server Admin]({{< relref "../../permissions/_index.md#grafana-server-admin-role" >}}) has permissions by default.
|
||||
Admin | grafana:roles:users:org:edit<br>grafana:roles:users:org:read<br>grafana:roles:reporting:admin:edit<br>grafana:roles:reporting:admin:read | Allows access to resource which [Admin]({{< relref "../../permissions/organization_roles.md" >}}) has permissions by default.
|
||||
Grafana Admin | fixed:permissions:admin:edit<br>fixed:permissions:admin:read<br>fixed:reporting:admin:edit<br>fixed:reporting:admin:read<br>fixed:users:admin:edit<br>fixed:users:admin:read<br>fixed:users:org:edit<br>fixed:users:org:read<br>fixed:ldap:admin:edit<br>fixed:ldap:admin:read | Allows access to resources which [Grafana Server Admin]({{< relref "../../permissions/_index.md#grafana-server-admin-role" >}}) has permissions by default.
|
||||
Admin | fixed:users:org:edit<br>fixed:users:org:read<br>fixed:reporting:admin:edit<br>fixed:reporting:admin:read | Allows access to resource which [Admin]({{< relref "../../permissions/organization_roles.md" >}}) has permissions by default.
|
||||
|
||||
@@ -14,7 +14,7 @@ Before you get started, make sure to [enable fine-grained access control]({{< re
|
||||
## Check all built-in role assignments
|
||||
|
||||
You can use the [Fine-grained access control HTTP API]({{< relref "../../http_api/access_control.md#get-all-built-in-role-assignments" >}}) to see all available built-in role assignments.
|
||||
The response contains a mapping between one of the organization roles (`Viewer`, `Editor`, `Admin`) or `Grafana Admin` to the custom or predefined roles.
|
||||
The response contains a mapping between one of the organization roles (`Viewer`, `Editor`, `Admin`) or `Grafana Admin` to the custom or fixed roles.
|
||||
|
||||
Example request:
|
||||
```
|
||||
@@ -29,7 +29,7 @@ Example response:
|
||||
{
|
||||
"version": 2,
|
||||
"uid": "qQui_LCMk",
|
||||
"name": "grafana:roles:users:org:edit",
|
||||
"name": "fixed:users:org:edit",
|
||||
"description": "Allows every read action for user organizations and in addition allows to administer user organizations.",
|
||||
"global": true,
|
||||
"updated": "2021-05-17T20:49:18+02:00",
|
||||
@@ -38,7 +38,7 @@ Example response:
|
||||
{
|
||||
"version": 1,
|
||||
"uid": "Kz9m_YjGz",
|
||||
"name": "grafana:roles:reporting:admin:edit",
|
||||
"name": "fixed:reporting:admin:edit",
|
||||
"description": "Gives access to edit any report or the organization's general reporting settings.",
|
||||
"global": true,
|
||||
"updated": "2021-05-13T16:24:26+02:00",
|
||||
@@ -51,7 +51,7 @@ Example response:
|
||||
{
|
||||
"version": 2,
|
||||
"uid": "qQui_LCMk",
|
||||
"name": "grafana:roles:users:org:edit",
|
||||
"name": "fixed:users:org:edit",
|
||||
"description": "Allows every read action for user organizations and in addition allows to administer user organizations.",
|
||||
"global": true,
|
||||
"updated": "2021-05-17T20:49:18+02:00",
|
||||
@@ -60,7 +60,7 @@ Example response:
|
||||
{
|
||||
"version": 2,
|
||||
"uid": "ajum_YjGk",
|
||||
"name": "grafana:roles:users:admin:read",
|
||||
"name": "fixed:users:admin:read",
|
||||
"description": "Allows to list and get users and related information.",
|
||||
"global": true,
|
||||
"updated": "2021-05-17T20:49:17+02:00",
|
||||
@@ -69,7 +69,7 @@ Example response:
|
||||
{
|
||||
"version": 2,
|
||||
"uid": "K3um_LCMk",
|
||||
"name": "grafana:roles:users:admin:edit",
|
||||
"name": "fixed:users:admin:edit",
|
||||
"description": "Allows every read action for users and in addition allows to administer users.",
|
||||
"global": true,
|
||||
"updated": "2021-05-17T20:49:17+02:00",
|
||||
@@ -94,7 +94,7 @@ Example response:
|
||||
{
|
||||
"version": 2,
|
||||
"uid": "qQui_LCMk",
|
||||
"name": "grafana:roles:users:org:edit",
|
||||
"name": "fixed:users:org:edit",
|
||||
"description": "Allows every read action for user organizations and in addition allows to administer user organizations.",
|
||||
"global": true,
|
||||
"permissions": [
|
||||
@@ -203,7 +203,7 @@ In order to create reports, you would need to have `reports.admin:write` permiss
|
||||
|
||||
If you want your users who have `Viewer` organization role to create reports, you have two options:
|
||||
|
||||
1. First option is to create a built-in role assignment and map `grafana:roles:reporting:admin:edit` predefined role to the `Viewer` built-in role. Note that `grafana:roles:reporting:admin:edit` predefined role allows doing more than creating reports. Refer to [predefined roles]({{< relref "./roles.md#predefined-roles" >}}) for full list of permission assignments.
|
||||
1. First option is to create a built-in role assignment and map `fixed:reporting:admin:edit` fixed role to the `Viewer` built-in role. Note that `fixed:reporting:admin:edit` fixed role allows doing more than creating reports. Refer to [fixed roles]({{< relref "./roles.md#fixed-roles" >}}) for full list of permission assignments.
|
||||
1. Second option is to [create a custom role]({{< ref "#create-your-custom-role" >}}) with `reports.admin:write` permission, and create a built-in role assignment for `Viewer` organization role.
|
||||
|
||||
## Prevent Grafana Admin from creating and inviting users
|
||||
@@ -213,14 +213,14 @@ In order to create users, you would need to have `users:create` permission. By d
|
||||
If you want to prevent Grafana Admin from creating users, you can do the following:
|
||||
|
||||
1. [Check all built-in role assignments]({{< ref "#check-all-built-in-role-assignments" >}}) to see what built-in role assignments are available.
|
||||
1. From built-in role assignments, find the role which gives `users:create` permission. Refer to [predefined roles]({{< relref "./roles.md#predefined-roles" >}}) for full list of permission assignments.
|
||||
1. From built-in role assignments, find the role which gives `users:create` permission. Refer to [fixed roles]({{< relref "./roles.md#fixed-roles" >}}) for full list of permission assignments.
|
||||
1. Remove the built-in role assignment by using an [Fine-grained access control HTTP API]({{< relref "../../http_api/access_control.md" >}}) or by using [Grafana provisioning]({{< relref "./provisioning" >}}).
|
||||
|
||||
## Allow Editors to create new custom roles
|
||||
|
||||
By default, Grafana Server Admin is the only user who can create and manage custom roles. If you want your users to do the same, you have two options:
|
||||
|
||||
1. First option is to create a built-in role assignment and map `grafana:roles:permissions:admin:edit` and `grafana:roles:permissions:admin:read` predefined roles to the `Editor` built-in role.
|
||||
1. First option is to create a built-in role assignment and map `fixed:permissions:admin:edit` and `fixed:permissions:admin:read` fixed roles to the `Editor` built-in role.
|
||||
1. Second option is to [create a custom role]({{< ref "#create-your-custom-role" >}}) with `roles.builtin:add` and `roles:write` permissions, and create a built-in role assignment for `Editor` organization role.
|
||||
|
||||
Note that in any scenario, your `Editor` would be able to create and manage roles only with the permissions they have, or with a subset of them.
|
||||
|
||||
@@ -24,7 +24,7 @@ Refer to [Explore]({{< relref "../explore/_index.md" >}}) for more information.
|
||||
|
||||
## Alerts
|
||||
|
||||
If you're using Grafana alerting, then you can have alerts sent through a number of different [alert notifiers]({{< relref "../alerting/notifications.md" >}}), including PagerDuty, SMS, email, VictorOps, OpsGenie, or Slack.
|
||||
If you're using Grafana alerting, then you can have alerts sent through a number of different [alert notifiers]({{< relref "../alerting/old-alerting/notifications.md" >}}), including PagerDuty, SMS, email, VictorOps, OpsGenie, or Slack.
|
||||
|
||||
Alert hooks allow you to create different notifiers with a bit of code if you prefer some other channels of communication. Visually define [alert rules]({{< relref "../alerting/_index.md" >}}) for your most important metrics.
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ Content-Type: application/json; charset=UTF-8
|
||||
{
|
||||
"version": 1,
|
||||
"uid": "Kz9m_YjGz",
|
||||
"name": "grafana:roles:reporting:admin:edit",
|
||||
"name": "fixed:reporting:admin:edit",
|
||||
"description": "Gives access to edit any report or the organization's general reporting settings.",
|
||||
"global": true,
|
||||
"updated": "2021-05-13T16:24:26+02:00",
|
||||
@@ -96,7 +96,7 @@ Content-Type: application/json; charset=UTF-8
|
||||
{
|
||||
"version": 5,
|
||||
"uid": "vi9mlLjGz",
|
||||
"name": "grafana:roles:permissions:admin:read",
|
||||
"name": "fixed:permissions:admin:read",
|
||||
"description": "Gives access to read and list roles and permissions, as well as built-in role assignments.",
|
||||
"global": true,
|
||||
"updated": "2021-05-13T22:41:49+02:00",
|
||||
@@ -142,7 +142,7 @@ Content-Type: application/json; charset=UTF-8
|
||||
{
|
||||
"version": 2,
|
||||
"uid": "jZrmlLCGk",
|
||||
"name": "grafana:roles:permissions:admin:edit",
|
||||
"name": "fixed:permissions:admin:edit",
|
||||
"description": "Gives access to create, update and delete roles, as well as manage built-in role assignments.",
|
||||
"global": true,
|
||||
"permissions": [
|
||||
@@ -176,7 +176,7 @@ Code | Description
|
||||
|
||||
`POST /api/access-control/roles`
|
||||
|
||||
Creates a new custom role and maps given permissions to that role. Note that roles with the same prefix as [Predefined Roles]({{< relref "../enterprise/access-control/roles.md" >}}) can't be created.
|
||||
Creates a new custom role and maps given permissions to that role. Note that roles with the same prefix as [Fixed Roles]({{< relref "../enterprise/access-control/roles.md" >}}) can't be created.
|
||||
|
||||
#### Required permissions
|
||||
|
||||
@@ -438,7 +438,7 @@ Content-Type: application/json; charset=UTF-8
|
||||
{
|
||||
"version": 1,
|
||||
"uid": "qQui_LCMk",
|
||||
"name": "grafana:roles:users:org:edit",
|
||||
"name": "fixed:users:org:edit",
|
||||
"description": "",
|
||||
"global": true,
|
||||
"updated": "2021-05-13T16:24:26+02:00",
|
||||
@@ -447,7 +447,7 @@ Content-Type: application/json; charset=UTF-8
|
||||
{
|
||||
"version": 1,
|
||||
"uid": "PeXmlYjMk",
|
||||
"name": "grafana:roles:users:org:read",
|
||||
"name": "fixed:users:org:read",
|
||||
"description": "",
|
||||
"global": true,
|
||||
"updated": "2021-05-13T16:24:26+02:00",
|
||||
@@ -458,7 +458,7 @@ Content-Type: application/json; charset=UTF-8
|
||||
{
|
||||
"version": 1,
|
||||
"uid": "qQui_LCMk",
|
||||
"name": "grafana:roles:users:org:edit",
|
||||
"name": "fixed:users:org:edit",
|
||||
"description": "",
|
||||
"global": true,
|
||||
"updated": "2021-05-13T16:24:26+02:00",
|
||||
|
||||
@@ -176,7 +176,7 @@ Content-Type: application/json
|
||||
|
||||
## Create notification channel
|
||||
|
||||
You can find the full list of [supported notifiers]({{< relref "../alerting/notifications/#list-of-supported-notifiers" >}}) on the alert notifiers page.
|
||||
You can find the full list of [supported notifiers]({{< relref "../alerting/old-alerting/notifications/#list-of-supported-notifiers" >}}) on the alert notifiers page.
|
||||
|
||||
`POST /api/alert-notifications`
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
"refresh": "25s"
|
||||
},
|
||||
"folderId": 0,
|
||||
"folderUid": "l3KqBxCMz",
|
||||
"message": "Made changes to xyz",
|
||||
"overwrite": false
|
||||
}
|
||||
@@ -55,6 +56,7 @@ JSON Body schema:
|
||||
- **dashboard.id** – id = null to create a new dashboard.
|
||||
- **dashboard.uid** – Optional unique identifier when creating a dashboard. uid = null will generate a new uid.
|
||||
- **folderId** – The id of the folder to save the dashboard in.
|
||||
- **folderUid** – The UID of the folder to save the dashboard in. Overrides the `folderId`.
|
||||
- **overwrite** – Set to true if you want to overwrite existing dashboard with newer version, same dashboard title in folder or same dashboard uid.
|
||||
- **message** - Set a commit message for the version history.
|
||||
- **refresh** - Set the dashboard refresh interval. If this is lower than [the minimum refresh interval]({{< relref "../administration/configuration.md#min_refresh_interval">}}), then Grafana will ignore it and will enforce the minimum refresh interval.
|
||||
@@ -268,7 +270,7 @@ In case of title already exists the `status` property will be `name-exists`.
|
||||
|
||||
`GET /api/dashboards/uid/:uid`
|
||||
|
||||
Will return the dashboard given the dashboard unique identifier (uid).
|
||||
Will return the dashboard given the dashboard unique identifier (uid). Information about the unique identifier of a folder containing the requested dashboard might be found in the metadata.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
@@ -298,6 +300,8 @@ Content-Type: application/json
|
||||
"meta": {
|
||||
"isStarred": false,
|
||||
"url": "/d/cIBgcSjkk/production-overview",
|
||||
"folderId": 2,
|
||||
"folderUid": "l3KqBxCMz",
|
||||
"slug": "production-overview" //deprecated in Grafana v5.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ The section contains tabs where you enter queries, transform your data, and crea
|
||||
|
||||
- **Query tab -** Select your data source and enter queries here. For more information, refer to [Queries]({{< relref "queries.md" >}}).
|
||||
- **Transform tab -** Apply data transformations. For more information, refer to [Transformations]({{< relref "transformations/_index.md" >}}).
|
||||
- **Alert tab -** Write alert rules. For more information, refer to [Create alerts]({{< relref "../alerting/create-alerts.md" >}}).
|
||||
- **Alert tab -** Write alert rules. For more information, refer to [Create alerts]({{< relref "../alerting/old-alerting/create-alerts.md" >}}).
|
||||
|
||||
### Panel and field options (side pane)
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
title: TBD...
|
||||
---
|
||||
|
||||
## TBD
|
||||
|
||||
Use the instructions in [Getting started with Grafana]({{< relref "../../getting-started/getting-started.md" >}}) to:
|
||||
|
||||
- Install Grafana.
|
||||
- Log in to Grafana.
|
||||
- Create your first dashboard.
|
||||
@@ -107,7 +107,7 @@ You can now provide detailed information to alert notification recipients by inj
|
||||
|
||||
{{< figure src="/img/docs/alerting/alert-notification-template-7-4.png" max-width="700px" caption="Variable support in alert notifications" >}}
|
||||
|
||||
For more information, refer to the [alert notification docs]({{< relref "../alerting/notifications.md#notification-templating" >}}).
|
||||
For more information, refer to the [alert notification docs]({{< relref "../alerting/old-alerting/notifications.md#notification-templating" >}}).
|
||||
|
||||
### Content security policy support
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Library panels allow users to build panels that can be used in multiple dashboar
|
||||
|
||||
### Real-time streaming
|
||||
|
||||
Data sources can now send real-time updates to dashboards over a websocket connection. This can be used with the [MQTT data source](https://github.com/grafana/mqtt-datasource).
|
||||
Data sources can now send real-time updates to dashboards over a websocket connection. This can be used with the [MQTT data source](https://github.com/grafana/mqtt-datasource).
|
||||
|
||||
In addition to data source integration, events can be sent to dashboards by posting metrics to the new live endpoint: `/api/live/push endpoint`.
|
||||
|
||||
@@ -48,7 +48,7 @@ This release introduces a new histogram panel visualization.
|
||||
|
||||
### Time series visualization updates
|
||||
|
||||
The Time series is out of beta! We are removing the `Beta` tag and graduating the Time series visualization to a stable state.
|
||||
The Time series is out of beta! We are removing the `Beta` tag and graduating the Time series visualization to a stable state.
|
||||
|
||||
- **Time series** is now the default visualization option, replacing the **Graph (old)**.
|
||||
- The Time series panel now supports stacking. For more information, refer to [Graph stacked time series]({{< relref "../panels/visualizations/time-series/graph-time-series-stacking.md" >}}).
|
||||
@@ -60,7 +60,7 @@ The Time series is out of beta! We are removing the `Beta` tag and graduating t
|
||||
|
||||
### Pie chart visualization updates
|
||||
|
||||
The Pie chart is out of beta! We are removing the `Beta` tag and graduating the Pie chart visualization to a stable state.
|
||||
The Pie chart is out of beta! We are removing the `Beta` tag and graduating the Pie chart visualization to a stable state.
|
||||
|
||||
### Panel editor updates
|
||||
|
||||
@@ -115,7 +115,7 @@ Grafana 8.0 includes many performance enhancements.
|
||||
|
||||
#### Initial startup and load performance
|
||||
|
||||
We reduced the Grafana initial download size massively, approximately 40%. This means that on slower or mobile connections, the initial login page or home dashboard will load much faster.
|
||||
We reduced the Grafana initial download size massively, approximately 40%. This means that on slower or mobile connections, the initial login page or home dashboard will load much faster.
|
||||
|
||||
All panels that have migrated from Flot to uPlot will also render two to three times faster because the library is much more efficient. Right now, this includes the Time series, Stat, Timeline, Histogram, and Barchart panel visualizations.
|
||||
|
||||
@@ -206,9 +206,9 @@ You can now configure generic OAuth with empty scopes. This allows OAuth Identit
|
||||
|
||||
##### Added OAuth support for strict parsing of role_attribute_path
|
||||
|
||||
You can now configure generic OAuth with strict parsing of the `role_attribute_path`. By default, if th `role_attribute_path` property does not return a role, then the user is assigned the `Viewer` role. You can disable the role assignment by setting `role_attribute_strict = true`. It denies user access if no role or an invalid role is returned.
|
||||
You can now configure generic OAuth with strict parsing of the `role_attribute_path`. By default, if the `role_attribute_path` property does not return a role, then the user is assigned the `Viewer` role. You can disable the role assignment by setting `role_attribute_strict = true`. It denies user access if no role or an invalid role is returned.
|
||||
|
||||
#### Singlestat panel deprecated
|
||||
#### Singlestat panel deprecated
|
||||
|
||||
Support for Singlestat panel has been discontinued. When you upgrade to version 8.0, all existing Singlestat panels automatically becomes Stat panels.
|
||||
Stat panel is available as plugin.
|
||||
@@ -243,3 +243,7 @@ Documentation was updated to reflect these changes.
|
||||
### Elasticsearch: Use application/x-ndjson content type for multi-search requests
|
||||
|
||||
For multi-search requests, we now use the correct application/x-ndjson content type instead of the incorrect application/json. Although this should be transparent to most of the users, if you are running Elasticsearch behind a proxy, then be sure that your proxy correctly handles requests with this content type.
|
||||
|
||||
### Prometheus: Update default HTTP method to POST for existing data sources
|
||||
|
||||
The default HTTP method for Prometheus data source is now POST, previously it was GET. The POST APIs are there since January 2018 (Prometheus 2.1.0) and they have fewer limitations than the GET APIs. Users with Prometheus instance with version < 2.1.0 that use the default HTTP method should update their HTTP method to GET.
|
||||
|
||||
@@ -65,8 +65,6 @@ describe('Exemplars', () => {
|
||||
|
||||
e2e.components.DataSource.Prometheus.exemplarMarker().first().trigger('mouseover');
|
||||
e2e().contains('Query with gdev-tempo').click();
|
||||
|
||||
e2e().get('[aria-label="Node: app"]').should('be.visible');
|
||||
e2e.components.TraceViewer.spanBar().should('have.length', 11);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ describe('Trace view', () => {
|
||||
|
||||
e2e.pages.Explore.General.scrollBar().scrollTo('center');
|
||||
|
||||
// After scrolling we should have 139 spans instead of the first 100
|
||||
e2e.components.TraceViewer.spanBar().should('have.length', 139);
|
||||
// After scrolling we should load more spans
|
||||
e2e.components.TraceViewer.spanBar().should('have.length', 140);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,7 +51,7 @@ require (
|
||||
github.com/gosimple/slug v1.9.0
|
||||
github.com/grafana/grafana-aws-sdk v0.4.0
|
||||
github.com/grafana/grafana-live-sdk v0.0.6
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.100.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.101.0
|
||||
github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/hashicorp/go-hclog v0.16.0
|
||||
@@ -76,7 +76,7 @@ require (
|
||||
github.com/prometheus/client_golang v1.10.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common v0.24.0
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210421143221-52df5ef7a3be
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210430082741-2a4b8e12bbf2
|
||||
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/goxmldsig v1.1.0
|
||||
@@ -91,7 +91,7 @@ require (
|
||||
github.com/weaveworks/common v0.0.0-20210419092856-009d1eebd624
|
||||
github.com/xorcare/pointer v1.1.0
|
||||
github.com/yudai/gojsondiff v1.0.0
|
||||
go.opentelemetry.io/collector v0.25.0
|
||||
go.opentelemetry.io/collector v0.27.0
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f // indirect
|
||||
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f
|
||||
@@ -116,3 +116,5 @@ require (
|
||||
xorm.io/core v0.7.3
|
||||
xorm.io/xorm v0.8.2
|
||||
)
|
||||
|
||||
replace github.com/apache/thrift => github.com/apache/thrift v0.14.1
|
||||
|
||||
@@ -73,7 +73,6 @@ github.com/Azure/azure-sdk-for-go v44.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
|
||||
github.com/Azure/azure-sdk-for-go v45.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v48.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v51.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v52.5.0+incompatible h1:/NLBWHCnIHtZyLPc1P7WIqi4Te4CC23kIQyK3Ep/7lA=
|
||||
github.com/Azure/azure-sdk-for-go v52.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
@@ -181,7 +180,7 @@ github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqR
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/sarama v1.22.2-0.20190604114437-cd910a683f9f/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs=
|
||||
github.com/Shopify/sarama v1.27.1/go.mod h1:g5s5osgELxgM+Md9Qni9rzo7Rbt+vvFQI4bt/Mc93II=
|
||||
github.com/Shopify/sarama v1.28.0/go.mod h1:j/2xTrU39dlzBmsxF1eQ2/DdWrxyBCl6pzz7a81o/ZY=
|
||||
github.com/Shopify/sarama v1.29.0/go.mod h1:2QpgD79wpdAESqNQMxNc0KYMkycd4slxGdV3TWSVqrU=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
@@ -211,8 +210,7 @@ github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VT
|
||||
github.com/apache/arrow/go/arrow v0.0.0-20200629181129-68b1273cbbf7/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0=
|
||||
github.com/apache/arrow/go/arrow v0.0.0-20210223225224-5bea62493d91 h1:rbe942bXzd2vnds4y9fYQL8X4yFltXoZsKW7KtG+TFM=
|
||||
github.com/apache/arrow/go/arrow v0.0.0-20210223225224-5bea62493d91/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.14.1/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA=
|
||||
github.com/aristanetworks/goarista v0.0.0-20190325233358-a123909ec740/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
@@ -922,8 +920,8 @@ github.com/grafana/grafana-live-sdk v0.0.6 h1:P1QFn0ZradOJp3zVpfG0STZMP+pgZrW0e0
|
||||
github.com/grafana/grafana-live-sdk v0.0.6/go.mod h1:f15hHmWyLdFjmuWLsjeKeZnq/HnNQ3QkoPcaEww45AY=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.91.0/go.mod h1:Ot3k7nY7P6DXmUsDgKvNB7oG1v7PRyTdmnYVoS554bU=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.100.0 h1:BryvIFdx/HrsKMt2hkxN7cJ0WrCgKpgjdJW8y8TSol0=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.100.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.101.0 h1:QyXMkgwZXUX9EQjLv5S5uDcvYjwsntqFV/dCC49Fn+w=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.101.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
|
||||
github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103 h1:qCmofFVwQR9QnsinstVqI1NPLMVl33jNCnOCXEAVn6E=
|
||||
github.com/grafana/loki v1.6.2-0.20210520072447-15d417efe103/go.mod h1:GHIsn+EohCChsdu5YouNZewqLeV9L2FNw4DEJU3P9qE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
@@ -1170,8 +1168,8 @@ github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
|
||||
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.7 h1:0hzRabrMN4tSTvMfnL3SCv1ZGeAP23ynzodBgaHeMeg=
|
||||
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
|
||||
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
@@ -1491,7 +1489,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
|
||||
github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
|
||||
github.com/prometheus/alertmanager v0.18.0/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE=
|
||||
github.com/prometheus/alertmanager v0.19.0/go.mod h1:Eyp94Yi/T+kdeb2qvq66E3RGuph5T/jm/RBVh4yz1xo=
|
||||
github.com/prometheus/alertmanager v0.20.0/go.mod h1:9g2i48FAyZW6BtbsnvHtMHQXl2aVtrORKwKVCQ+nbrg=
|
||||
@@ -1584,11 +1582,11 @@ github.com/prometheus/prometheus v1.8.2-0.20201029103703-63be30dceed9/go.mod h1:
|
||||
github.com/prometheus/prometheus v1.8.2-0.20201119142752-3ad25a6dc3d9/go.mod h1:1MDE/bXgu4gqd5w/otko6WQpXZX9vu8QX4KbitCmaPg=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20201119181812-c8f810083d3f/go.mod h1:1MDE/bXgu4gqd5w/otko6WQpXZX9vu8QX4KbitCmaPg=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210215121130-6f488061dfb4/go.mod h1:NAYujktP0dmSSpeV155mtnwX2pndLpVVK/Ps68R01TA=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210217141258-a6be548dbc17/go.mod h1:dv3B1syqmkrkmo665MPCU6L8PbTXIiUeg/OEQULLNxA=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210315220929-1cba1741828b/go.mod h1:MS/bpdil77lPbfQeKk6OqVQ9OLnpN3Rszd0hka0EOWE=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210324152458-c7a62b95cea0/go.mod h1:sf7j/iAbhZahjeC0s3wwMmp5dksrJ/Za1UKdR+j6Hmw=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210421143221-52df5ef7a3be h1:Kt84gUEhCC04CEqQxML1W+wzpydx1t3rmaEq1H971ZM=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210421143221-52df5ef7a3be/go.mod h1:WbIKsp4vWCoPHis5qQfd0QimLOR7qe79roXN5O8U8bs=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210430082741-2a4b8e12bbf2 h1:AHi2TGs09Mv4v688/bjcY2PfAcu9+p4aPvsgVQ4nYDk=
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210430082741-2a4b8e12bbf2/go.mod h1:5aBj+GpLB+V5MCnrKm5+JAqEJwzDiLugOmDhgt7sDec=
|
||||
github.com/prometheus/statsd_exporter v0.20.0/go.mod h1:YL3FWCG8JBBtaUSxAg4Gz2ZYu22bS84XM89ZQXXTWmQ=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY=
|
||||
@@ -1652,7 +1650,7 @@ github.com/sercand/kuberesolver v2.4.0+incompatible/go.mod h1:lWF3GL0xptCB/vCiJP
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil v2.20.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v3.21.4+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20200105231215-408a2507e114/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
@@ -1757,8 +1755,8 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV
|
||||
github.com/timberio/go-datemath v0.1.1-0.20200323150745-74ddef604fff h1:QCdUBuN+iKWAB9HqPTkBwyKPPUHDobJ2AuELSNZwd4o=
|
||||
github.com/timberio/go-datemath v0.1.1-0.20200323150745-74ddef604fff/go.mod h1:m7kjsbCuO4QKP3KLfnxiUZWiOiFXmxj30HeexjL3lc0=
|
||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
|
||||
github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
|
||||
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
|
||||
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
@@ -1824,8 +1822,10 @@ github.com/xdg-go/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:FV1RpvYFmF8
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
github.com/xdg/scram v1.0.3/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
@@ -1899,8 +1899,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/collector v0.25.0 h1:CVrqPgr0Kr/Se1ihS6jam1/n4hndXk3GHHAOsruSDzw=
|
||||
go.opentelemetry.io/collector v0.25.0/go.mod h1:hXpdip0pVo+lISHAzPtu13QIRHgqC1zZ/4EdgoEC0fc=
|
||||
go.opentelemetry.io/collector v0.27.0 h1:/lklt/NGVx+1EAWjjsk2DwAt9A9rGhIKOzo6Ob9kZqA=
|
||||
go.opentelemetry.io/collector v0.27.0/go.mod h1:J2oCzkvFAkgmgrvIdQNg5Dt3QAZ+ep7HNtHPay/7nvo=
|
||||
go.opentelemetry.io/otel v0.11.0/go.mod h1:G8UCk+KooF2HLkgo8RHX9epABH/aRGYET7gQOqBVdB0=
|
||||
go.starlark.net v0.0.0-20200901195727-6e684ef5eeee/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
@@ -1965,6 +1965,7 @@ golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -2092,6 +2093,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210324051636-2c4c8ecb7826/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210427231257-85d9c07bbe3a/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f h1:Si4U+UcgJzya9kpiEUJKQvjr512OLli+gL4poHrz93U=
|
||||
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -2226,7 +2228,6 @@ golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -2235,6 +2236,7 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210314195730-07df6a141424/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -2476,7 +2478,6 @@ google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210302174412-5ede27ff9881/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210312152112-fc591d9ea70f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
|
||||
@@ -217,7 +217,7 @@ export function outerJoinDataFrames(options: JoinOptions): DataFrame | undefined
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
// Copied from uplot
|
||||
export type AlignedData = [number[], ...Array<Array<number | null>>];
|
||||
export type AlignedData = [number[], ...Array<Array<number | null | undefined>>];
|
||||
|
||||
// nullModes
|
||||
const NULL_REMOVE = 0; // nulls are converted to undefined (e.g. for spanGaps: true)
|
||||
|
||||
@@ -20,3 +20,4 @@ export { DataLinkBuiltInVars, mapInternalLinkToExplore } from './dataLinks';
|
||||
export { DocsId } from './docs';
|
||||
export { makeClassES5Compatible } from './makeClassES5Compatible';
|
||||
export { anyToNumber } from './anyToNumber';
|
||||
export { withLoadingIndicator, WithLoadingIndicatorOptions } from './withLoadingIndicator';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { merge, Observable, timer } from 'rxjs';
|
||||
import { mapTo, takeUntil } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type WithLoadingIndicatorOptions<T> = {
|
||||
whileLoading: T;
|
||||
source: Observable<T>;
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function withLoadingIndicator<T>({ whileLoading, source }: WithLoadingIndicatorOptions<T>): Observable<T> {
|
||||
return merge(timer(200).pipe(mapTo(whileLoading), takeUntil(source)), source);
|
||||
}
|
||||
@@ -2,10 +2,11 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
// Components
|
||||
import { HorizontalGroup, PluginSignatureBadge, Select } from '@grafana/ui';
|
||||
import { HorizontalGroup, PluginSignatureBadge, Select, stylesFactory } from '@grafana/ui';
|
||||
import { DataSourceInstanceSettings, isUnsignedPluginSignature, SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { getDataSourceSrv } from '../services/dataSourceSrv';
|
||||
import { css, cx } from '@emotion/css';
|
||||
|
||||
/**
|
||||
* Component props description for the {@link DataSourcePicker}
|
||||
@@ -138,11 +139,12 @@ export class DataSourcePicker extends PureComponent<DataSourcePickerProps, DataS
|
||||
const { error } = this.state;
|
||||
const options = this.getDataSourceOptions();
|
||||
const value = this.getCurrentValue();
|
||||
const styles = getStyles();
|
||||
|
||||
return (
|
||||
<div aria-label={selectors.components.DataSourcePicker.container}>
|
||||
<Select
|
||||
className="ds-picker select-container"
|
||||
className={styles.select}
|
||||
isMulti={false}
|
||||
isClearable={false}
|
||||
backspaceRemovesValue={false}
|
||||
@@ -172,3 +174,13 @@ export class DataSourcePicker extends PureComponent<DataSourcePickerProps, DataS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyles = stylesFactory(() => ({
|
||||
select: cx(
|
||||
css({
|
||||
minWidth: 200,
|
||||
}),
|
||||
'ds-picker',
|
||||
'select-container'
|
||||
),
|
||||
}));
|
||||
|
||||
@@ -83,6 +83,9 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
azure: AzureSettings = {
|
||||
managedIdentityEnabled: false,
|
||||
};
|
||||
caching = {
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
constructor(options: GrafanaBootConfig) {
|
||||
const mode = options.bootData.user.lightTheme ? 'light' : 'dark';
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"react-transition-group": "4.4.1",
|
||||
"slate": "0.47.8",
|
||||
"tinycolor2": "1.4.1",
|
||||
"uplot": "1.6.9"
|
||||
"uplot": "1.6.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "16.0.0",
|
||||
|
||||
@@ -145,8 +145,6 @@ export const getCardStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||
media: css`
|
||||
margin-right: ${theme.spacing(2)};
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > * {
|
||||
width: 100%;
|
||||
|
||||
@@ -76,6 +76,10 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
margin-right: ${theme.spacing(1)};
|
||||
font-size: ${theme.typography.size.md};
|
||||
`,
|
||||
icon: css`
|
||||
label: collapse__icon;
|
||||
margin-left: -5px;
|
||||
`,
|
||||
});
|
||||
|
||||
export interface Props {
|
||||
@@ -133,7 +137,7 @@ export const Collapse: FunctionComponent<Props> = ({
|
||||
return (
|
||||
<div className={panelClass}>
|
||||
<div className={headerClass} onClick={onClickToggle}>
|
||||
{collapsible && <Icon name={isOpen ? 'angle-up' : 'angle-down'} />}
|
||||
{collapsible && <Icon className={style.icon} name={isOpen ? 'angle-up' : 'angle-down'} />}
|
||||
<div className={cx([style.headerLabel])}>{label}</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
|
||||
@@ -54,45 +54,42 @@ export const PageToolbar: FC<Props> = React.memo(
|
||||
|
||||
return (
|
||||
<div className={mainStyle}>
|
||||
<div className={styles.toolbarLeft}>
|
||||
{pageIcon && !onGoBack && (
|
||||
<div className={styles.pageIcon}>
|
||||
<Icon name={pageIcon} size="lg" />
|
||||
</div>
|
||||
)}
|
||||
{onGoBack && (
|
||||
<div className={styles.goBackButton}>
|
||||
<IconButton
|
||||
name="arrow-left"
|
||||
tooltip="Go back (Esc)"
|
||||
tooltipPlacement="bottom"
|
||||
size="xxl"
|
||||
surface="dashboard"
|
||||
aria-label={selectors.components.BackButton.backArrow}
|
||||
onClick={onGoBack}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.titleWrapper}>
|
||||
{parent && onClickParent && (
|
||||
<button onClick={onClickParent} className={cx(styles.titleLink, styles.parentLink)}>
|
||||
{parent} <span className={styles.parentIcon}>/</span>
|
||||
</button>
|
||||
)}
|
||||
{onClickTitle && (
|
||||
<button onClick={onClickTitle} className={styles.titleLink}>
|
||||
{title}
|
||||
</button>
|
||||
)}
|
||||
{!onClickTitle && <div className={styles.titleText}>{title}</div>}
|
||||
{pageIcon && !onGoBack && (
|
||||
<div className={styles.pageIcon}>
|
||||
<Icon name={pageIcon} size="lg" />
|
||||
</div>
|
||||
{leftItems?.map((child, index) => (
|
||||
<div className={styles.leftActionItem} key={index}>
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.spacer}></div>
|
||||
)}
|
||||
{onGoBack && (
|
||||
<div className={styles.pageIcon}>
|
||||
<IconButton
|
||||
name="arrow-left"
|
||||
tooltip="Go back (Esc)"
|
||||
tooltipPlacement="bottom"
|
||||
size="xxl"
|
||||
surface="dashboard"
|
||||
aria-label={selectors.components.BackButton.backArrow}
|
||||
onClick={onGoBack}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{parent && onClickParent && (
|
||||
<button onClick={onClickParent} className={cx(styles.titleText, styles.parentLink)}>
|
||||
{parent} <span className={styles.parentIcon}>/</span>
|
||||
</button>
|
||||
)}
|
||||
{onClickTitle && (
|
||||
<button onClick={onClickTitle} className={styles.titleText}>
|
||||
{title}
|
||||
</button>
|
||||
)}
|
||||
{!onClickTitle && <div className={styles.titleText}>{title}</div>}
|
||||
{leftItems?.map((child, index) => (
|
||||
<div className={styles.leftActionItem} key={index}>
|
||||
{child}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className={styles.spacer} />
|
||||
{React.Children.toArray(children)
|
||||
.filter(Boolean)
|
||||
.map((child, index) => {
|
||||
@@ -114,7 +111,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
|
||||
const titleStyles = `
|
||||
font-size: ${typography.size.lg};
|
||||
padding-left: ${spacing(1)};
|
||||
padding: ${spacing(0.5, 1, 0.5, 1)};
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@@ -131,71 +128,51 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
|
||||
return {
|
||||
toolbar: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: ${theme.colors.background.canvas};
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
padding: ${theme.spacing(1, 2, 0, 2)};
|
||||
`,
|
||||
toolbarLeft: css`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
padding: ${theme.spacing(1.5, 2)};
|
||||
`,
|
||||
spacer: css`
|
||||
flex-grow: 1;
|
||||
`,
|
||||
pageIcon: css`
|
||||
padding-top: ${spacing(1)};
|
||||
align-items: center;
|
||||
display: none;
|
||||
|
||||
@media ${styleMixins.mediaUp(theme.v1.breakpoints.md)} {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
titleWrapper: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: ${spacing(1)};
|
||||
padding-right: ${spacing(1)};
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
`,
|
||||
goBackButton: css`
|
||||
position: relative;
|
||||
top: 8px;
|
||||
`,
|
||||
parentIcon: css`
|
||||
margin-left: 4px;
|
||||
margin-left: ${theme.spacing(0.5)};
|
||||
`,
|
||||
titleText: css`
|
||||
${titleStyles};
|
||||
`,
|
||||
titleLink: css`
|
||||
${titleStyles};
|
||||
`,
|
||||
parentLink: css`
|
||||
display: none;
|
||||
|
||||
padding-right: 0;
|
||||
@media ${styleMixins.mediaUp(theme.v1.breakpoints.md)} {
|
||||
display: inline-block;
|
||||
display: unset;
|
||||
}
|
||||
`,
|
||||
actionWrapper: css`
|
||||
padding-left: ${spacing(1)};
|
||||
padding-top: ${spacing(1)};
|
||||
padding: ${spacing(0.5, 0, 0.5, 1)};
|
||||
`,
|
||||
leftActionItem: css`
|
||||
display: none;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
align-items: center;
|
||||
padding-left: ${spacing(0.5)};
|
||||
|
||||
@media ${styleMixins.mediaUp(theme.v1.breakpoints.md)} {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding-left: ${spacing(0.5)};
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ export class UPlotChart extends React.Component<PlotProps, UPlotChartState> {
|
||||
return;
|
||||
}
|
||||
this.props.config.addHook('setSize', (u) => {
|
||||
const canvas = u.root.querySelector<HTMLDivElement>('.u-over');
|
||||
const canvas = u.over;
|
||||
if (!canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
};
|
||||
|
||||
if (plotCtx && plotCtx.plot) {
|
||||
plotCtx.plot.root.querySelector('.u-over')!.addEventListener('mouseleave', plotMouseLeave);
|
||||
plotCtx.plot.over.addEventListener('mouseleave', plotMouseLeave);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (plotCtx && plotCtx.plot) {
|
||||
plotCtx.plot.root.querySelector('.u-over')!.removeEventListener('mouseleave', plotMouseLeave);
|
||||
plotCtx.plot.over.removeEventListener('mouseleave', plotMouseLeave);
|
||||
}
|
||||
};
|
||||
}, [plotCtx.plot?.root, setCoords]);
|
||||
|
||||
@@ -119,8 +119,8 @@ export function findMidPointYPosition(u: uPlot, idx: number) {
|
||||
for (let i = 1; i < u.data.length; i++) {
|
||||
const sData = u.data[i];
|
||||
const sVal = sData[idx];
|
||||
if (sVal !== null) {
|
||||
if (max === null) {
|
||||
if (sVal != null) {
|
||||
if (max == null) {
|
||||
max = sVal;
|
||||
} else {
|
||||
if (sVal > max) {
|
||||
@@ -128,7 +128,7 @@ export function findMidPointYPosition(u: uPlot, idx: number) {
|
||||
sMaxIdx = i;
|
||||
}
|
||||
}
|
||||
if (min === null) {
|
||||
if (min == null) {
|
||||
min = sVal;
|
||||
} else {
|
||||
if (sVal < min) {
|
||||
@@ -139,10 +139,10 @@ export function findMidPointYPosition(u: uPlot, idx: number) {
|
||||
}
|
||||
}
|
||||
|
||||
if (min === null && max === null) {
|
||||
if (min == null && max == null) {
|
||||
// no tooltip to show
|
||||
y = undefined;
|
||||
} else if (min !== null && max !== null) {
|
||||
} else if (min != null && max != null) {
|
||||
// find median position
|
||||
y = (u.valToPos(min, u.series[sMinIdx].scale!) + u.valToPos(max, u.series[sMaxIdx].scale!)) / 2;
|
||||
} else {
|
||||
|
||||
+2
-15
@@ -4,23 +4,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func AdminGetSettings(c *models.ReqContext) response.Response {
|
||||
settings := make(map[string]interface{})
|
||||
|
||||
for _, section := range setting.Raw.Sections() {
|
||||
jsonSec := make(map[string]interface{})
|
||||
settings[section.Name()] = jsonSec
|
||||
|
||||
for _, key := range section.Keys() {
|
||||
keyName := key.Name()
|
||||
jsonSec[keyName] = setting.RedactedValue(keyName, key.Value())
|
||||
}
|
||||
}
|
||||
|
||||
return response.JSON(200, settings)
|
||||
func (hs *HTTPServer) AdminGetSettings(_ *models.ReqContext) response.Response {
|
||||
return response.JSON(200, hs.SettingsProvider.Current())
|
||||
}
|
||||
|
||||
func AdminGetStats(c *models.ReqContext) response.Response {
|
||||
|
||||
+1
-1
@@ -440,7 +440,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// admin api
|
||||
r.Group("/api/admin", func(adminRoute routing.RouteRegister) {
|
||||
adminRoute.Get("/settings", reqGrafanaAdmin, routing.Wrap(AdminGetSettings))
|
||||
adminRoute.Get("/settings", reqGrafanaAdmin, routing.Wrap(hs.AdminGetSettings))
|
||||
adminRoute.Get("/stats", reqGrafanaAdmin, routing.Wrap(AdminGetStats))
|
||||
adminRoute.Post("/pause-all-alerts", reqGrafanaAdmin, bind(dtos.PauseAllAlertsCommand{}), routing.Wrap(PauseAllAlerts))
|
||||
|
||||
|
||||
+24
-1
@@ -135,8 +135,12 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
||||
if dash.FolderId > 0 {
|
||||
query := models.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
if errors.Is(err, models.ErrFolderNotFound) {
|
||||
return response.Error(404, "Folder not found", err)
|
||||
}
|
||||
return response.Error(500, "Dashboard folder could not be read", err)
|
||||
}
|
||||
meta.FolderUid = query.Result.Uid
|
||||
meta.FolderTitle = query.Result.Title
|
||||
meta.FolderUrl = query.Result.GetUrl()
|
||||
}
|
||||
@@ -273,8 +277,27 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboa
|
||||
var err error
|
||||
cmd.OrgId = c.OrgId
|
||||
cmd.UserId = c.UserId
|
||||
trimDefaults := c.QueryBoolWithDefault("trimdefaults", false)
|
||||
if trimDefaults && !hs.LoadSchemaService.IsDisabled() {
|
||||
cmd.Dashboard, err = hs.LoadSchemaService.DashboardApplyDefaults(cmd.Dashboard)
|
||||
if err != nil {
|
||||
return response.Error(500, "Error while applying default value to the dashboard json", err)
|
||||
}
|
||||
}
|
||||
if cmd.FolderUid != "" {
|
||||
folders := dashboards.NewFolderService(c.OrgId, c.SignedInUser, hs.SQLStore)
|
||||
folder, err := folders.GetFolderByUID(cmd.FolderUid)
|
||||
if err != nil {
|
||||
if errors.Is(err, models.ErrFolderNotFound) {
|
||||
return response.Error(400, "Folder not found", err)
|
||||
}
|
||||
return response.Error(500, "Error while checking folder ID", err)
|
||||
}
|
||||
cmd.FolderId = folder.Id
|
||||
}
|
||||
|
||||
dash := cmd.GetDashboardModel()
|
||||
newDashboard := dash.Id == 0 && dash.Uid == ""
|
||||
newDashboard := dash.Id == 0
|
||||
if newDashboard {
|
||||
limitReached, err := hs.QuotaService.QuotaReached(c, "dashboard")
|
||||
if err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
@@ -799,7 +800,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", mock, cmd, func(sc *scenarioContext) {
|
||||
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", mock, nil, cmd, func(sc *scenarioContext) {
|
||||
callPostDashboardShouldReturnSuccess(sc)
|
||||
|
||||
dto := mock.SavedDashboards[0]
|
||||
@@ -819,6 +820,91 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given a correct request for creating a dashboard with folder uid", func(t *testing.T) {
|
||||
const folderUid string = "folderUID"
|
||||
const dashID int64 = 2
|
||||
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
UserId: 5,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
Overwrite: true,
|
||||
FolderUid: folderUid,
|
||||
IsFolder: false,
|
||||
Message: "msg",
|
||||
}
|
||||
|
||||
mock := &dashboards.FakeDashboardService{
|
||||
SaveDashboardResult: &models.Dashboard{
|
||||
Id: dashID,
|
||||
Uid: "uid",
|
||||
Title: "Dash",
|
||||
Slug: "dash",
|
||||
Version: 2,
|
||||
},
|
||||
}
|
||||
|
||||
mockFolder := &fakeFolderService{
|
||||
GetFolderByUIDResult: &models.Folder{Id: 1, Uid: "folderUID", Title: "Folder"},
|
||||
}
|
||||
|
||||
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", mock, mockFolder, cmd, func(sc *scenarioContext) {
|
||||
callPostDashboardShouldReturnSuccess(sc)
|
||||
|
||||
dto := mock.SavedDashboards[0]
|
||||
assert.Equal(t, cmd.OrgId, dto.OrgId)
|
||||
assert.Equal(t, cmd.UserId, dto.User.UserId)
|
||||
assert.Equal(t, "Dash", dto.Dashboard.Title)
|
||||
assert.True(t, dto.Overwrite)
|
||||
assert.Equal(t, "msg", dto.Message)
|
||||
|
||||
result := sc.ToJSON()
|
||||
assert.Equal(t, "success", result.Get("status").MustString())
|
||||
assert.Equal(t, dashID, result.Get("id").MustInt64())
|
||||
assert.Equal(t, "uid", result.Get("uid").MustString())
|
||||
assert.Equal(t, "dash", result.Get("slug").MustString())
|
||||
assert.Equal(t, "/d/uid/dash", result.Get("url").MustString())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Given a request with incorrect folder uid for creating a dashboard with", func(t *testing.T) {
|
||||
const folderUid string = "folderUID"
|
||||
const dashID int64 = 2
|
||||
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
UserId: 5,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": "Dash",
|
||||
}),
|
||||
Overwrite: true,
|
||||
FolderUid: folderUid,
|
||||
IsFolder: false,
|
||||
Message: "msg",
|
||||
}
|
||||
|
||||
mock := &dashboards.FakeDashboardService{
|
||||
SaveDashboardResult: &models.Dashboard{
|
||||
Id: dashID,
|
||||
Uid: "uid",
|
||||
Title: "Dash",
|
||||
Slug: "dash",
|
||||
Version: 2,
|
||||
},
|
||||
}
|
||||
|
||||
mockFolder := &fakeFolderService{
|
||||
GetFolderByUIDError: errors.New("Error while searching Folder ID"),
|
||||
}
|
||||
|
||||
postDashboardScenario(t, "When calling POST on", "/api/dashboards", "/api/dashboards", mock, mockFolder, cmd, func(sc *scenarioContext) {
|
||||
callPostDashboard(sc)
|
||||
assert.Equal(t, 500, sc.resp.Code)
|
||||
})
|
||||
})
|
||||
|
||||
// This tests that invalid requests returns expected error responses
|
||||
t.Run("Given incorrect requests for creating a dashboard", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
@@ -858,7 +944,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
}
|
||||
|
||||
postDashboardScenario(t, fmt.Sprintf("Expect '%s' error when calling POST on", tc.SaveError.Error()),
|
||||
"/api/dashboards", "/api/dashboards", mock, cmd, func(sc *scenarioContext) {
|
||||
"/api/dashboards", "/api/dashboards", mock, nil, cmd, func(sc *scenarioContext) {
|
||||
callPostDashboard(sc)
|
||||
assert.Equal(t, tc.ExpectedStatusCode, sc.resp.Code)
|
||||
})
|
||||
@@ -1207,7 +1293,8 @@ func callPostDashboardShouldReturnSuccess(sc *scenarioContext) {
|
||||
}
|
||||
|
||||
func postDashboardScenario(t *testing.T, desc string, url string, routePattern string,
|
||||
mock *dashboards.FakeDashboardService, cmd models.SaveDashboardCommand, fn scenarioFunc) {
|
||||
mock *dashboards.FakeDashboardService, mockFolder *fakeFolderService, cmd models.SaveDashboardCommand,
|
||||
fn scenarioFunc) {
|
||||
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
||||
t.Cleanup(bus.ClearBusHandlers)
|
||||
|
||||
@@ -1235,14 +1322,17 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
|
||||
|
||||
origNewDashboardService := dashboards.NewService
|
||||
origProvisioningService := dashboards.NewProvisioningService
|
||||
origNewFolderService := dashboards.NewFolderService
|
||||
t.Cleanup(func() {
|
||||
dashboards.NewService = origNewDashboardService
|
||||
dashboards.NewProvisioningService = origProvisioningService
|
||||
dashboards.NewFolderService = origNewFolderService
|
||||
})
|
||||
dashboards.MockDashboardService(mock)
|
||||
dashboards.NewProvisioningService = func(dboards.Store) dashboards.DashboardProvisioningService {
|
||||
return mockDashboardProvisioningService{}
|
||||
}
|
||||
mockFolderService(mockFolder)
|
||||
|
||||
sc.m.Post(routePattern, sc.defaultHandler)
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ type DashboardMeta struct {
|
||||
HasAcl bool `json:"hasAcl"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
FolderUid string `json:"folderUid"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderUrl string `json:"folderUrl"`
|
||||
Provisioned bool `json:"provisioned"`
|
||||
|
||||
@@ -65,6 +65,7 @@ type ImportDashboardCommand struct {
|
||||
Dashboard *simplejson.Json `json:"dashboard"`
|
||||
Inputs []plugins.ImportDashboardInput `json:"inputs"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
FolderUid string `json:"folderUid"`
|
||||
}
|
||||
|
||||
type InstallPluginCommand struct {
|
||||
|
||||
@@ -255,6 +255,9 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
||||
"cloud": hs.Cfg.Azure.Cloud,
|
||||
"managedIdentityEnabled": hs.Cfg.Azure.ManagedIdentityEnabled,
|
||||
},
|
||||
"caching": map[string]bool{
|
||||
"enabled": hs.Cfg.SectionWithEnvOverrides("caching").Key("enabled").MustBool(true),
|
||||
},
|
||||
}
|
||||
|
||||
return jsonObj, nil
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/expr/mathexp"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptr "github.com/xorcare/pointer"
|
||||
@@ -202,7 +203,7 @@ func TestConditionsCmdExecute(t *testing.T) {
|
||||
vars: mathexp.Vars{
|
||||
"A": mathexp.Results{
|
||||
Values: []mathexp.Value{
|
||||
valBasedSeries(ptr.Float64(30), ptr.Float64(40)),
|
||||
valBasedSeriesWithLabels(data.Labels{"h": "1"}, ptr.Float64(30), ptr.Float64(40)),
|
||||
valBasedSeries(ptr.Float64(0), ptr.Float64(10)),
|
||||
},
|
||||
},
|
||||
@@ -218,7 +219,7 @@ func TestConditionsCmdExecute(t *testing.T) {
|
||||
}},
|
||||
resultNumber: func() mathexp.Number {
|
||||
v := valBasedNumber(ptr.Float64(1))
|
||||
v.SetMeta([]EvalMatch{{Value: ptr.Float64(35)}})
|
||||
v.SetMeta([]EvalMatch{{Value: ptr.Float64(35), Labels: data.Labels{"h": "1"}}})
|
||||
return v
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,6 +24,11 @@ func (cr classicReducer) ValidReduceFunc() bool {
|
||||
//nolint: gocyclo
|
||||
func (cr classicReducer) Reduce(series mathexp.Series) mathexp.Number {
|
||||
num := mathexp.NewNumber("", nil)
|
||||
|
||||
if series.GetLabels() != nil {
|
||||
num.SetLabels(series.GetLabels().Copy())
|
||||
}
|
||||
|
||||
num.SetValue(nil)
|
||||
|
||||
if series.Len() == 0 {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/expr/mathexp"
|
||||
"github.com/stretchr/testify/require"
|
||||
ptr "github.com/xorcare/pointer"
|
||||
@@ -408,6 +409,17 @@ func valBasedSeries(vals ...*float64) mathexp.Series {
|
||||
return newSeries
|
||||
}
|
||||
|
||||
func valBasedSeriesWithLabels(l data.Labels, vals ...*float64) mathexp.Series {
|
||||
newSeries := mathexp.NewSeries("", l, 0, false, 1, true, len(vals))
|
||||
for idx, f := range vals {
|
||||
err := newSeries.SetPoint(idx, unixTimePointer(int64(idx)), f)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return newSeries
|
||||
}
|
||||
|
||||
func unixTimePointer(sec int64) *time.Time {
|
||||
t := time.Unix(sec, 0)
|
||||
return &t
|
||||
|
||||
@@ -13,8 +13,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -91,7 +89,7 @@ type TimeRange struct {
|
||||
// or are datasource requests.
|
||||
func (s *Service) TransformData(ctx context.Context, req *Request) (r *backend.QueryDataResponse, err error) {
|
||||
if s.isDisabled() {
|
||||
return nil, status.Error(codes.PermissionDenied, "Expressions are disabled")
|
||||
return nil, fmt.Errorf("server side expressions are disabled")
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
@@ -111,20 +109,20 @@ func (s *Service) TransformData(ctx context.Context, req *Request) (r *backend.Q
|
||||
// and parsing graph nodes from the queries.
|
||||
pipeline, err := s.BuildPipeline(req)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Execute the pipeline
|
||||
responses, err := s.ExecutePipeline(ctx, pipeline)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Unknown, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get which queries have the Hide property so they those queries' results
|
||||
// can be excluded from the response.
|
||||
hidden, err := hiddenRefIDs(req.Queries)
|
||||
if err != nil {
|
||||
return nil, status.Error((codes.Internal), err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(hidden) != 0 {
|
||||
|
||||
@@ -133,129 +133,15 @@ func countProxyRequests(status int) {
|
||||
}
|
||||
|
||||
func sanitizeMethod(m string) string {
|
||||
switch m {
|
||||
case "GET", "get":
|
||||
return "get"
|
||||
case "PUT", "put":
|
||||
return "put"
|
||||
case "HEAD", "head":
|
||||
return "head"
|
||||
case "POST", "post":
|
||||
return "post"
|
||||
case "DELETE", "delete":
|
||||
return "delete"
|
||||
case "CONNECT", "connect":
|
||||
return "connect"
|
||||
case "OPTIONS", "options":
|
||||
return "options"
|
||||
case "NOTIFY", "notify":
|
||||
return "notify"
|
||||
default:
|
||||
return strings.ToLower(m)
|
||||
}
|
||||
return strings.ToLower(m)
|
||||
}
|
||||
|
||||
// If the wrapped http.Handler has not set a status code, i.e. the value is
|
||||
// currently 0, sanitizeCode will return 200, for consistency with behavior in
|
||||
// the stdlib.
|
||||
//nolint: gocyclo
|
||||
func sanitizeCode(s int) string {
|
||||
switch s {
|
||||
case 100:
|
||||
return "100"
|
||||
case 101:
|
||||
return "101"
|
||||
|
||||
case 200, 0:
|
||||
if s == 0 {
|
||||
return "200"
|
||||
case 201:
|
||||
return "201"
|
||||
case 202:
|
||||
return "202"
|
||||
case 203:
|
||||
return "203"
|
||||
case 204:
|
||||
return "204"
|
||||
case 205:
|
||||
return "205"
|
||||
case 206:
|
||||
return "206"
|
||||
|
||||
case 300:
|
||||
return "300"
|
||||
case 301:
|
||||
return "301"
|
||||
case 302:
|
||||
return "302"
|
||||
case 304:
|
||||
return "304"
|
||||
case 305:
|
||||
return "305"
|
||||
case 307:
|
||||
return "307"
|
||||
|
||||
case 400:
|
||||
return "400"
|
||||
case 401:
|
||||
return "401"
|
||||
case 402:
|
||||
return "402"
|
||||
case 403:
|
||||
return "403"
|
||||
case 404:
|
||||
return "404"
|
||||
case 405:
|
||||
return "405"
|
||||
case 406:
|
||||
return "406"
|
||||
case 407:
|
||||
return "407"
|
||||
case 408:
|
||||
return "408"
|
||||
case 409:
|
||||
return "409"
|
||||
case 410:
|
||||
return "410"
|
||||
case 411:
|
||||
return "411"
|
||||
case 412:
|
||||
return "412"
|
||||
case 413:
|
||||
return "413"
|
||||
case 414:
|
||||
return "414"
|
||||
case 415:
|
||||
return "415"
|
||||
case 416:
|
||||
return "416"
|
||||
case 417:
|
||||
return "417"
|
||||
case 418:
|
||||
return "418"
|
||||
|
||||
case 500:
|
||||
return "500"
|
||||
case 501:
|
||||
return "501"
|
||||
case 502:
|
||||
return "502"
|
||||
case 503:
|
||||
return "503"
|
||||
case 504:
|
||||
return "504"
|
||||
case 505:
|
||||
return "505"
|
||||
|
||||
case 428:
|
||||
return "428"
|
||||
case 429:
|
||||
return "429"
|
||||
case 431:
|
||||
return "431"
|
||||
case 511:
|
||||
return "511"
|
||||
|
||||
default:
|
||||
return strconv.Itoa(s)
|
||||
}
|
||||
return strconv.Itoa(s)
|
||||
}
|
||||
|
||||
@@ -339,6 +339,7 @@ type SaveDashboardCommand struct {
|
||||
RestoredFrom int `json:"-"`
|
||||
PluginId string `json:"-"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
FolderUid string `json:"folderUid"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
|
||||
UpdatedAt time.Time
|
||||
|
||||
@@ -110,7 +110,7 @@ func (cp *corePlugin) PublishStream(ctx context.Context, req *backend.PublishStr
|
||||
return nil, backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
func (cp *corePlugin) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
func (cp *corePlugin) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
if cp.StreamHandler != nil {
|
||||
return cp.StreamHandler.RunStream(ctx, req, sender)
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ func (c *clientV2) PublishStream(ctx context.Context, req *backend.PublishStream
|
||||
return backend.FromProto().PublishStreamResponse(protoResp), nil
|
||||
}
|
||||
|
||||
func (c *clientV2) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
func (c *clientV2) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
if c.StreamClient == nil {
|
||||
return backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func (c *clientV2) RunStream(ctx context.Context, req *backend.RunStreamRequest,
|
||||
}
|
||||
|
||||
for {
|
||||
protoResp, err := protoStream.Recv()
|
||||
p, err := protoStream.Recv()
|
||||
if err != nil {
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return backendplugin.ErrMethodNotImplemented
|
||||
@@ -219,7 +219,9 @@ func (c *clientV2) RunStream(ctx context.Context, req *backend.RunStreamRequest,
|
||||
}
|
||||
return fmt.Errorf("error running stream: %w", err)
|
||||
}
|
||||
if err := sender.Send(backend.FromProto().StreamPacket(protoResp)); err != nil {
|
||||
// From GRPC connection we receive already prepared JSON.
|
||||
err = sender.SendJSON(p.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ func (p *grpcPlugin) PublishStream(ctx context.Context, request *backend.Publish
|
||||
return pluginClient.PublishStream(ctx, request)
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
func (p *grpcPlugin) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
pluginClient, ok := p.getPluginClient()
|
||||
if !ok {
|
||||
return backendplugin.ErrPluginUnavailable
|
||||
|
||||
@@ -81,6 +81,8 @@ func (m *manager) Register(pluginID string, factory backendplugin.PluginFactoryF
|
||||
}
|
||||
|
||||
hostEnv = append(hostEnv, m.getAWSEnvironmentVariables()...)
|
||||
hostEnv = append(hostEnv, m.getAzureEnvironmentVariables()...)
|
||||
|
||||
pluginSettings := getPluginSettings(pluginID, m.Cfg)
|
||||
env := pluginSettings.ToEnv("GF_PLUGIN", hostEnv)
|
||||
|
||||
@@ -164,6 +166,21 @@ func (m *manager) getAWSEnvironmentVariables() []string {
|
||||
return variables
|
||||
}
|
||||
|
||||
func (m *manager) getAzureEnvironmentVariables() []string {
|
||||
variables := []string{}
|
||||
if m.Cfg.Azure.Cloud != "" {
|
||||
variables = append(variables, "AZURE_CLOUD="+m.Cfg.Azure.Cloud)
|
||||
}
|
||||
if m.Cfg.Azure.ManagedIdentityClientId != "" {
|
||||
variables = append(variables, "AZURE_MANAGED_IDENTITY_CLIENT_ID="+m.Cfg.Azure.ManagedIdentityClientId)
|
||||
}
|
||||
if m.Cfg.Azure.ManagedIdentityEnabled {
|
||||
variables = append(variables, "AZURE_MANAGED_IDENTITY_ENABLED=true")
|
||||
}
|
||||
|
||||
return variables
|
||||
}
|
||||
|
||||
//nolint: staticcheck // plugins.DataPlugin deprecated
|
||||
func (m *manager) GetDataPlugin(pluginID string) interface{} {
|
||||
p, _ := m.Get(pluginID)
|
||||
|
||||
@@ -63,8 +63,16 @@ func TestManager(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should provide expected host environment variables", func(t *testing.T) {
|
||||
require.Len(t, ctx.env, 4)
|
||||
require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Open Source", fmt.Sprintf("%s=true", awsds.AssumeRoleEnabledEnvVarKeyName), fmt.Sprintf("%s=keys,credentials", awsds.AllowedAuthProvidersEnvVarKeyName)}, ctx.env)
|
||||
require.Len(t, ctx.env, 7)
|
||||
require.EqualValues(t, []string{
|
||||
"GF_VERSION=7.0.0",
|
||||
"GF_EDITION=Open Source",
|
||||
fmt.Sprintf("%s=true", awsds.AssumeRoleEnabledEnvVarKeyName),
|
||||
fmt.Sprintf("%s=keys,credentials", awsds.AllowedAuthProvidersEnvVarKeyName),
|
||||
"AZURE_CLOUD=AzureCloud",
|
||||
"AZURE_MANAGED_IDENTITY_CLIENT_ID=client-id",
|
||||
"AZURE_MANAGED_IDENTITY_ENABLED=true"},
|
||||
ctx.env)
|
||||
})
|
||||
|
||||
t.Run("When manager runs should start and stop plugin", func(t *testing.T) {
|
||||
@@ -282,8 +290,18 @@ func TestManager(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Should provide expected host environment variables", func(t *testing.T) {
|
||||
require.Len(t, ctx.env, 6)
|
||||
require.EqualValues(t, []string{"GF_VERSION=7.0.0", "GF_EDITION=Enterprise", "GF_ENTERPRISE_LICENSE_PATH=/license.txt", "GF_ENTERPRISE_LICENSE_TEXT=testtoken", fmt.Sprintf("%s=true", awsds.AssumeRoleEnabledEnvVarKeyName), fmt.Sprintf("%s=keys,credentials", awsds.AllowedAuthProvidersEnvVarKeyName)}, ctx.env)
|
||||
require.Len(t, ctx.env, 9)
|
||||
require.EqualValues(t, []string{
|
||||
"GF_VERSION=7.0.0",
|
||||
"GF_EDITION=Enterprise",
|
||||
"GF_ENTERPRISE_LICENSE_PATH=/license.txt",
|
||||
"GF_ENTERPRISE_LICENSE_TEXT=testtoken",
|
||||
fmt.Sprintf("%s=true", awsds.AssumeRoleEnabledEnvVarKeyName),
|
||||
fmt.Sprintf("%s=keys,credentials", awsds.AllowedAuthProvidersEnvVarKeyName),
|
||||
"AZURE_CLOUD=AzureCloud",
|
||||
"AZURE_MANAGED_IDENTITY_CLIENT_ID=client-id",
|
||||
"AZURE_MANAGED_IDENTITY_ENABLED=true"},
|
||||
ctx.env)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -304,6 +322,10 @@ func newManagerScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *m
|
||||
cfg.AWSAllowedAuthProviders = []string{"keys", "credentials"}
|
||||
cfg.AWSAssumeRoleEnabled = true
|
||||
|
||||
cfg.Azure.ManagedIdentityEnabled = true
|
||||
cfg.Azure.Cloud = "AzureCloud"
|
||||
cfg.Azure.ManagedIdentityClientId = "client-id"
|
||||
|
||||
license := &testLicensingService{}
|
||||
validator := &testPluginRequestValidator{}
|
||||
ctx := &managerScenarioCtx{
|
||||
@@ -435,7 +457,7 @@ func (tp *testPlugin) PublishStream(ctx context.Context, request *backend.Publis
|
||||
return nil, backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
func (tp *testPlugin) RunStream(ctx context.Context, request *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
func (tp *testPlugin) RunStream(ctx context.Context, request *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
return backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,9 @@ func (ac *OSSAccessControlService) GetUserPermissions(ctx context.Context, user
|
||||
builtinRoles := ac.GetUserBuiltInRoles(user)
|
||||
permissions := make([]*accesscontrol.Permission, 0)
|
||||
for _, builtin := range builtinRoles {
|
||||
if roleNames, ok := accesscontrol.PredefinedRoleGrants[builtin]; ok {
|
||||
if roleNames, ok := accesscontrol.FixedRoleGrants[builtin]; ok {
|
||||
for _, name := range roleNames {
|
||||
r, exists := accesscontrol.PredefinedRoles[name]
|
||||
r, exists := accesscontrol.FixedRoles[name]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -135,13 +135,13 @@ var provisioningAdminRole = RoleDTO{
|
||||
},
|
||||
}
|
||||
|
||||
// PredefinedRoles provides a map of permission sets/roles which can be
|
||||
// FixedRoles provides a map of permission sets/roles which can be
|
||||
// assigned to a set of users. When adding a new resource protected by
|
||||
// Grafana access control the default permissions should be added to a
|
||||
// new predefined role in this set so that users can access the new
|
||||
// resource. PredefinedRoleGrants lists which organization roles are
|
||||
// assigned which predefined roles in this list.
|
||||
var PredefinedRoles = map[string]RoleDTO{
|
||||
// new fixed role in this set so that users can access the new
|
||||
// resource. FixedRoleGrants lists which built-in roles are
|
||||
// assigned which fixed roles in this list.
|
||||
var FixedRoles = map[string]RoleDTO{
|
||||
usersAdminRead: usersAdminReadRole,
|
||||
usersAdminEdit: usersAdminEditRole,
|
||||
|
||||
@@ -155,21 +155,21 @@ var PredefinedRoles = map[string]RoleDTO{
|
||||
}
|
||||
|
||||
const (
|
||||
usersAdminEdit = "grafana:roles:users:admin:edit"
|
||||
usersAdminRead = "grafana:roles:users:admin:read"
|
||||
usersAdminEdit = "fixed:users:admin:edit"
|
||||
usersAdminRead = "fixed:users:admin:read"
|
||||
|
||||
usersOrgEdit = "grafana:roles:users:org:edit"
|
||||
usersOrgRead = "grafana:roles:users:org:read"
|
||||
usersOrgEdit = "fixed:users:org:edit"
|
||||
usersOrgRead = "fixed:users:org:read"
|
||||
|
||||
ldapAdminEdit = "grafana:roles:ldap:admin:edit"
|
||||
ldapAdminRead = "grafana:roles:ldap:admin:read"
|
||||
ldapAdminEdit = "fixed:ldap:admin:edit"
|
||||
ldapAdminRead = "fixed:ldap:admin:read"
|
||||
|
||||
provisioningAdmin = "grafana:roles:provisioning:admin"
|
||||
provisioningAdmin = "fixed:provisioning:admin"
|
||||
)
|
||||
|
||||
// PredefinedRoleGrants specifies which organization roles are assigned
|
||||
// to which set of PredefinedRoles by default. Alphabetically sorted.
|
||||
var PredefinedRoleGrants = map[string][]string{
|
||||
// FixedRoleGrants specifies which built-in roles are assigned
|
||||
// to which set of FixedRoles by default. Alphabetically sorted.
|
||||
var FixedRoleGrants = map[string][]string{
|
||||
RoleGrafanaAdmin: {
|
||||
ldapAdminEdit,
|
||||
ldapAdminRead,
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
)
|
||||
|
||||
func TestPredefinedRoles(t *testing.T) {
|
||||
for name, r := range PredefinedRoles {
|
||||
for name, r := range FixedRoles {
|
||||
assert.Truef(t,
|
||||
strings.HasPrefix(name, "grafana:roles:"),
|
||||
"expected all predefined roles to be prefixed by 'grafana:roles:', found role '%s'", name,
|
||||
strings.HasPrefix(name, "fixed:"),
|
||||
"expected all fixed roles to be prefixed by 'fixed:', found role '%s'", name,
|
||||
)
|
||||
assert.Equal(t, name, r.Name)
|
||||
assert.NotZero(t, r.Version)
|
||||
@@ -21,7 +21,7 @@ func TestPredefinedRoles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPredefinedRoleGrants(t *testing.T) {
|
||||
for _, v := range PredefinedRoleGrants {
|
||||
for _, v := range FixedRoleGrants {
|
||||
assert.True(t,
|
||||
sort.SliceIsSorted(v, func(i, j int) bool {
|
||||
return v[i] < v[j]
|
||||
@@ -29,7 +29,7 @@ func TestPredefinedRoleGrants(t *testing.T) {
|
||||
"require role grant lists to be sorted",
|
||||
)
|
||||
for _, r := range v {
|
||||
assert.Contains(t, PredefinedRoles, r)
|
||||
assert.Contains(t, FixedRoles, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,9 @@ func (r *PluginPathRunner) OnSubscribe(ctx context.Context, user *models.SignedI
|
||||
|
||||
reply := models.SubscribeReply{
|
||||
Presence: true,
|
||||
Data: resp.Data,
|
||||
}
|
||||
if resp.InitialData != nil {
|
||||
reply.Data = resp.InitialData.Data()
|
||||
}
|
||||
return reply, backend.SubscribeStreamStatusOK, nil
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ func (g *GrafanaLive) Init() error {
|
||||
// cfg.LogLevel = centrifuge.LogLevelDebug
|
||||
cfg.LogHandler = handleLog
|
||||
cfg.LogLevel = centrifuge.LogLevelError
|
||||
cfg.MetricsNamespace = "grafana_live"
|
||||
|
||||
// Node is the core object in Centrifuge library responsible for many useful
|
||||
// things. For example Node allows to publish messages to channels from server
|
||||
@@ -155,9 +156,9 @@ func (g *GrafanaLive) Init() error {
|
||||
g.node = node
|
||||
|
||||
g.contextGetter = newPluginContextGetter(g.PluginContextProvider)
|
||||
packetSender := newPluginPacketSender(node)
|
||||
channelSender := newPluginChannelSender(node)
|
||||
presenceGetter := newPluginPresenceGetter(node)
|
||||
g.runStreamManager = runstream.NewManager(packetSender, presenceGetter, g.contextGetter)
|
||||
g.runStreamManager = runstream.NewManager(channelSender, presenceGetter, g.contextGetter)
|
||||
|
||||
// Initialize the main features
|
||||
dash := &features.DashboardHandler{
|
||||
|
||||
@@ -106,11 +106,12 @@ func (s *ManagedStream) ListChannels(orgID int64, prefix string) []util.DynMap {
|
||||
// unstableSchema flag can be set to disable schema caching for a path.
|
||||
func (s *ManagedStream) Push(orgID int64, path string, frame *data.Frame, unstableSchema bool) error {
|
||||
// Keep schema + data for last packet.
|
||||
frameJSON, err := data.FrameToJSON(frame, true, true)
|
||||
frameJSONWrapper, err := data.FrameToJSON(frame)
|
||||
if err != nil {
|
||||
logger.Error("Error marshaling frame with Schema", "error", err)
|
||||
return err
|
||||
}
|
||||
frameJSON := frameJSONWrapper.Bytes(data.IncludeAll)
|
||||
|
||||
if !unstableSchema {
|
||||
// If schema is stable we can safely cache it, and only send values if
|
||||
@@ -128,11 +129,12 @@ func (s *ManagedStream) Push(orgID int64, path string, frame *data.Frame, unstab
|
||||
// frame to keep Schema JSON and Values JSON in frame object
|
||||
// to avoid encoding twice.
|
||||
if exists {
|
||||
frameJSON, err = data.FrameToJSON(frame, false, true)
|
||||
frameJSONWrapper, err = data.FrameToJSON(frame)
|
||||
if err != nil {
|
||||
logger.Error("Error marshaling Frame to JSON", "error", err)
|
||||
return err
|
||||
}
|
||||
frameJSON = frameJSONWrapper.Bytes(data.IncludeDataOnly)
|
||||
}
|
||||
} else {
|
||||
// For unstable schema we always need to send everything to a connection.
|
||||
|
||||
@@ -10,18 +10,18 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/plugincontext"
|
||||
)
|
||||
|
||||
type pluginPacketSender struct {
|
||||
type pluginChannelSender struct {
|
||||
node *centrifuge.Node
|
||||
}
|
||||
|
||||
func newPluginPacketSender(node *centrifuge.Node) *pluginPacketSender {
|
||||
return &pluginPacketSender{node: node}
|
||||
func newPluginChannelSender(node *centrifuge.Node) *pluginChannelSender {
|
||||
return &pluginChannelSender{node: node}
|
||||
}
|
||||
|
||||
func (p *pluginPacketSender) Send(channel string, packet *backend.StreamPacket) error {
|
||||
_, err := p.node.Publish(channel, packet.Data)
|
||||
func (p *pluginChannelSender) Send(channel string, data []byte) error {
|
||||
_, err := p.node.Publish(channel, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error publishing %s: %w", string(packet.Data), err)
|
||||
return fmt.Errorf("error publishing %s: %w", string(data), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ var (
|
||||
logger = log.New("live.runstream")
|
||||
)
|
||||
|
||||
//go:generate mockgen -destination=mock.go -package=runstream github.com/grafana/grafana/pkg/services/live/runstream StreamPacketSender,PresenceGetter,StreamRunner,PluginContextGetter
|
||||
//go:generate mockgen -destination=mock.go -package=runstream github.com/grafana/grafana/pkg/services/live/runstream ChannelSender,PresenceGetter,StreamRunner,PluginContextGetter
|
||||
|
||||
type StreamPacketSender interface {
|
||||
Send(channel string, packet *backend.StreamPacket) error
|
||||
type ChannelSender interface {
|
||||
Send(channel string, data []byte) error
|
||||
}
|
||||
|
||||
type PluginContextGetter interface {
|
||||
@@ -33,23 +33,16 @@ type PresenceGetter interface {
|
||||
}
|
||||
|
||||
type StreamRunner interface {
|
||||
RunStream(ctx context.Context, request *backend.RunStreamRequest, sender backend.StreamPacketSender) error
|
||||
RunStream(ctx context.Context, request *backend.RunStreamRequest, sender *backend.StreamSender) error
|
||||
}
|
||||
|
||||
type streamSender struct {
|
||||
channel string
|
||||
packetSender StreamPacketSender
|
||||
type packetSender struct {
|
||||
channelSender ChannelSender
|
||||
channel string
|
||||
}
|
||||
|
||||
func newStreamSender(channel string, packetSender StreamPacketSender) *streamSender {
|
||||
return &streamSender{
|
||||
channel: channel,
|
||||
packetSender: packetSender,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *streamSender) Send(packet *backend.StreamPacket) error {
|
||||
return p.packetSender.Send(p.channel, packet)
|
||||
func (p *packetSender) Send(packet *backend.StreamPacket) error {
|
||||
return p.channelSender.Send(p.channel, packet.Data)
|
||||
}
|
||||
|
||||
// Manager manages streams from Grafana to plugins (i.e. RunStream method).
|
||||
@@ -60,7 +53,7 @@ type Manager struct {
|
||||
datasourceStreams map[string]map[string]struct{}
|
||||
presenceGetter PresenceGetter
|
||||
pluginContextGetter PluginContextGetter
|
||||
packetSender StreamPacketSender
|
||||
channelSender ChannelSender
|
||||
registerCh chan submitRequest
|
||||
closedCh chan struct{}
|
||||
checkInterval time.Duration
|
||||
@@ -86,11 +79,11 @@ const (
|
||||
)
|
||||
|
||||
// NewManager creates new Manager.
|
||||
func NewManager(packetSender StreamPacketSender, presenceGetter PresenceGetter, pluginContextGetter PluginContextGetter, opts ...ManagerOption) *Manager {
|
||||
func NewManager(channelSender ChannelSender, presenceGetter PresenceGetter, pluginContextGetter PluginContextGetter, opts ...ManagerOption) *Manager {
|
||||
sm := &Manager{
|
||||
streams: make(map[string]streamContext),
|
||||
datasourceStreams: map[string]map[string]struct{}{},
|
||||
packetSender: packetSender,
|
||||
channelSender: channelSender,
|
||||
presenceGetter: presenceGetter,
|
||||
pluginContextGetter: pluginContextGetter,
|
||||
registerCh: make(chan submitRequest),
|
||||
@@ -308,7 +301,7 @@ func (s *Manager) runStream(ctx context.Context, cancelFn func(), sr streamReque
|
||||
PluginContext: pluginCtx,
|
||||
Path: sr.Path,
|
||||
},
|
||||
newStreamSender(sr.Channel, s.packetSender),
|
||||
backend.NewStreamSender(&packetSender{channelSender: s.channelSender, channel: sr.Channel}),
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(ctx.Err(), context.Canceled) {
|
||||
|
||||
@@ -27,11 +27,11 @@ func TestStreamManager_Run(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockPacketSender := NewMockStreamPacketSender(mockCtrl)
|
||||
mockChannelSender := NewMockChannelSender(mockCtrl)
|
||||
mockPresenceGetter := NewMockPresenceGetter(mockCtrl)
|
||||
mockContextGetter := NewMockPluginContextGetter(mockCtrl)
|
||||
|
||||
manager := NewManager(mockPacketSender, mockPresenceGetter, mockContextGetter)
|
||||
manager := NewManager(mockChannelSender, mockPresenceGetter, mockContextGetter)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -48,7 +48,7 @@ func TestStreamManager_SubmitStream_Send(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockPacketSender := NewMockStreamPacketSender(mockCtrl)
|
||||
mockPacketSender := NewMockChannelSender(mockCtrl)
|
||||
mockPresenceGetter := NewMockPresenceGetter(mockCtrl)
|
||||
mockContextGetter := NewMockPluginContextGetter(mockCtrl)
|
||||
|
||||
@@ -84,12 +84,10 @@ func TestStreamManager_SubmitStream_Send(t *testing.T) {
|
||||
mockStreamRunner := NewMockStreamRunner(mockCtrl)
|
||||
mockStreamRunner.EXPECT().RunStream(
|
||||
gomock.Any(), gomock.Any(), gomock.Any(),
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
require.Equal(t, "test", req.Path)
|
||||
close(startedCh)
|
||||
err := sender.Send(&backend.StreamPacket{
|
||||
Data: []byte("test"),
|
||||
})
|
||||
err := sender.SendJSON([]byte("{}"))
|
||||
require.NoError(t, err)
|
||||
<-ctx.Done()
|
||||
close(doneCh)
|
||||
@@ -115,7 +113,7 @@ func TestStreamManager_SubmitStream_DifferentOrgID(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockPacketSender := NewMockStreamPacketSender(mockCtrl)
|
||||
mockPacketSender := NewMockChannelSender(mockCtrl)
|
||||
mockPresenceGetter := NewMockPresenceGetter(mockCtrl)
|
||||
mockContextGetter := NewMockPluginContextGetter(mockCtrl)
|
||||
|
||||
@@ -142,12 +140,10 @@ func TestStreamManager_SubmitStream_DifferentOrgID(t *testing.T) {
|
||||
mockStreamRunner1 := NewMockStreamRunner(mockCtrl)
|
||||
mockStreamRunner1.EXPECT().RunStream(
|
||||
gomock.Any(), gomock.Any(), gomock.Any(),
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
require.Equal(t, "test", req.Path)
|
||||
close(startedCh1)
|
||||
err := sender.Send(&backend.StreamPacket{
|
||||
Data: []byte("test"),
|
||||
})
|
||||
err := sender.SendJSON([]byte("{}"))
|
||||
require.NoError(t, err)
|
||||
<-ctx.Done()
|
||||
close(doneCh1)
|
||||
@@ -157,12 +153,10 @@ func TestStreamManager_SubmitStream_DifferentOrgID(t *testing.T) {
|
||||
mockStreamRunner2 := NewMockStreamRunner(mockCtrl)
|
||||
mockStreamRunner2.EXPECT().RunStream(
|
||||
gomock.Any(), gomock.Any(), gomock.Any(),
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
require.Equal(t, "test", req.Path)
|
||||
close(startedCh2)
|
||||
err := sender.Send(&backend.StreamPacket{
|
||||
Data: []byte("test"),
|
||||
})
|
||||
err := sender.SendJSON([]byte("{}"))
|
||||
require.NoError(t, err)
|
||||
<-ctx.Done()
|
||||
close(doneCh2)
|
||||
@@ -190,7 +184,7 @@ func TestStreamManager_SubmitStream_CloseNoSubscribers(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockPacketSender := NewMockStreamPacketSender(mockCtrl)
|
||||
mockPacketSender := NewMockChannelSender(mockCtrl)
|
||||
mockPresenceGetter := NewMockPresenceGetter(mockCtrl)
|
||||
mockContextGetter := NewMockPluginContextGetter(mockCtrl)
|
||||
|
||||
@@ -218,7 +212,7 @@ func TestStreamManager_SubmitStream_CloseNoSubscribers(t *testing.T) {
|
||||
mockPresenceGetter.EXPECT().GetNumSubscribers("1/test").Return(0, nil).Times(3)
|
||||
|
||||
mockStreamRunner := NewMockStreamRunner(mockCtrl)
|
||||
mockStreamRunner.EXPECT().RunStream(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
mockStreamRunner.EXPECT().RunStream(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
close(startedCh)
|
||||
<-ctx.Done()
|
||||
close(doneCh)
|
||||
@@ -237,7 +231,7 @@ func TestStreamManager_SubmitStream_ErrorRestartsRunStream(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockPacketSender := NewMockStreamPacketSender(mockCtrl)
|
||||
mockPacketSender := NewMockChannelSender(mockCtrl)
|
||||
mockPresenceGetter := NewMockPresenceGetter(mockCtrl)
|
||||
mockContextGetter := NewMockPluginContextGetter(mockCtrl)
|
||||
|
||||
@@ -271,7 +265,7 @@ func TestStreamManager_SubmitStream_ErrorRestartsRunStream(t *testing.T) {
|
||||
mockStreamRunner := NewMockStreamRunner(mockCtrl)
|
||||
mockStreamRunner.EXPECT().RunStream(
|
||||
gomock.Any(), gomock.Any(), gomock.Any(),
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
if currentErrors >= numErrors {
|
||||
return nil
|
||||
}
|
||||
@@ -290,7 +284,7 @@ func TestStreamManager_SubmitStream_NilErrorStopsRunStream(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockPacketSender := NewMockStreamPacketSender(mockCtrl)
|
||||
mockPacketSender := NewMockChannelSender(mockCtrl)
|
||||
mockPresenceGetter := NewMockPresenceGetter(mockCtrl)
|
||||
mockContextGetter := NewMockPluginContextGetter(mockCtrl)
|
||||
|
||||
@@ -309,7 +303,7 @@ func TestStreamManager_SubmitStream_NilErrorStopsRunStream(t *testing.T) {
|
||||
mockStreamRunner := NewMockStreamRunner(mockCtrl)
|
||||
mockStreamRunner.EXPECT().RunStream(
|
||||
gomock.Any(), gomock.Any(), gomock.Any(),
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
return nil
|
||||
}).Times(1)
|
||||
|
||||
@@ -323,7 +317,7 @@ func TestStreamManager_HandleDatasourceUpdate(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockPacketSender := NewMockStreamPacketSender(mockCtrl)
|
||||
mockPacketSender := NewMockChannelSender(mockCtrl)
|
||||
mockPresenceGetter := NewMockPresenceGetter(mockCtrl)
|
||||
mockContextGetter := NewMockPluginContextGetter(mockCtrl)
|
||||
|
||||
@@ -359,7 +353,7 @@ func TestStreamManager_HandleDatasourceUpdate(t *testing.T) {
|
||||
mockStreamRunner := NewMockStreamRunner(mockCtrl)
|
||||
mockStreamRunner.EXPECT().RunStream(
|
||||
gomock.Any(), gomock.Any(), gomock.Any(),
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
if isFirstCall {
|
||||
// first RunStream will wait till context done.
|
||||
isFirstCall = false
|
||||
@@ -389,7 +383,7 @@ func TestStreamManager_HandleDatasourceDelete(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
defer mockCtrl.Finish()
|
||||
|
||||
mockPacketSender := NewMockStreamPacketSender(mockCtrl)
|
||||
mockPacketSender := NewMockChannelSender(mockCtrl)
|
||||
mockPresenceGetter := NewMockPresenceGetter(mockCtrl)
|
||||
mockContextGetter := NewMockPluginContextGetter(mockCtrl)
|
||||
|
||||
@@ -422,7 +416,7 @@ func TestStreamManager_HandleDatasourceDelete(t *testing.T) {
|
||||
mockStreamRunner := NewMockStreamRunner(mockCtrl)
|
||||
mockStreamRunner.EXPECT().RunStream(
|
||||
gomock.Any(), gomock.Any(), gomock.Any(),
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender backend.StreamPacketSender) error {
|
||||
).DoAndReturn(func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
close(doneCh)
|
||||
<-ctx.Done()
|
||||
return ctx.Err()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/grafana/grafana/pkg/services/live/runstream (interfaces: StreamPacketSender,PresenceGetter,StreamRunner,PluginContextGetter)
|
||||
// Source: github.com/grafana/grafana/pkg/services/live/runstream (interfaces: ChannelSender,PresenceGetter,StreamRunner,PluginContextGetter)
|
||||
|
||||
// Package runstream is a generated GoMock package.
|
||||
package runstream
|
||||
@@ -13,31 +13,31 @@ import (
|
||||
models "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// MockStreamPacketSender is a mock of StreamPacketSender interface.
|
||||
type MockStreamPacketSender struct {
|
||||
// MockChannelSender is a mock of ChannelSender interface.
|
||||
type MockChannelSender struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockStreamPacketSenderMockRecorder
|
||||
recorder *MockChannelSenderMockRecorder
|
||||
}
|
||||
|
||||
// MockStreamPacketSenderMockRecorder is the mock recorder for MockStreamPacketSender.
|
||||
type MockStreamPacketSenderMockRecorder struct {
|
||||
mock *MockStreamPacketSender
|
||||
// MockChannelSenderMockRecorder is the mock recorder for MockChannelSender.
|
||||
type MockChannelSenderMockRecorder struct {
|
||||
mock *MockChannelSender
|
||||
}
|
||||
|
||||
// NewMockStreamPacketSender creates a new mock instance.
|
||||
func NewMockStreamPacketSender(ctrl *gomock.Controller) *MockStreamPacketSender {
|
||||
mock := &MockStreamPacketSender{ctrl: ctrl}
|
||||
mock.recorder = &MockStreamPacketSenderMockRecorder{mock}
|
||||
// NewMockChannelSender creates a new mock instance.
|
||||
func NewMockChannelSender(ctrl *gomock.Controller) *MockChannelSender {
|
||||
mock := &MockChannelSender{ctrl: ctrl}
|
||||
mock.recorder = &MockChannelSenderMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockStreamPacketSender) EXPECT() *MockStreamPacketSenderMockRecorder {
|
||||
func (m *MockChannelSender) EXPECT() *MockChannelSenderMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Send mocks base method.
|
||||
func (m *MockStreamPacketSender) Send(arg0 string, arg1 *backend.StreamPacket) error {
|
||||
func (m *MockChannelSender) Send(arg0 string, arg1 []byte) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Send", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
@@ -45,9 +45,9 @@ func (m *MockStreamPacketSender) Send(arg0 string, arg1 *backend.StreamPacket) e
|
||||
}
|
||||
|
||||
// Send indicates an expected call of Send.
|
||||
func (mr *MockStreamPacketSenderMockRecorder) Send(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockChannelSenderMockRecorder) Send(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockStreamPacketSender)(nil).Send), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockChannelSender)(nil).Send), arg0, arg1)
|
||||
}
|
||||
|
||||
// MockPresenceGetter is a mock of PresenceGetter interface.
|
||||
@@ -112,7 +112,7 @@ func (m *MockStreamRunner) EXPECT() *MockStreamRunnerMockRecorder {
|
||||
}
|
||||
|
||||
// RunStream mocks base method.
|
||||
func (m *MockStreamRunner) RunStream(arg0 context.Context, arg1 *backend.RunStreamRequest, arg2 backend.StreamPacketSender) error {
|
||||
func (m *MockStreamRunner) RunStream(arg0 context.Context, arg1 *backend.RunStreamRequest, arg2 *backend.StreamSender) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "RunStream", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
|
||||
@@ -74,7 +74,7 @@ func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodel
|
||||
http.MethodGet,
|
||||
queryURL,
|
||||
nil,
|
||||
jsonExtractor(nil),
|
||||
instantQueryResultsExtractor,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
cortex_util "github.com/cortexproject/cortex/pkg/util"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/promql/parser"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type instantQueryResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data queryData `json:"data,omitempty"`
|
||||
ErrorType string `json:"errorType,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type queryData struct {
|
||||
ResultType parser.ValueType `json:"resultType"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
vector vector `json:"-"`
|
||||
scalar scalar `json:"-"`
|
||||
}
|
||||
|
||||
type scalar promql.Scalar
|
||||
|
||||
func (s *scalar) UnmarshalJSON(b []byte) error {
|
||||
var xs []interface{}
|
||||
if err := json.Unmarshal(b, &xs); err != nil {
|
||||
return err
|
||||
}
|
||||
// scalars are encoded like `[ts/1000, "value"]`
|
||||
if len(xs) != 2 {
|
||||
return fmt.Errorf("unexpected number of scalar encoded values: %d", len(xs))
|
||||
}
|
||||
ts, ok := xs[0].(float64)
|
||||
if !ok {
|
||||
return fmt.Errorf("first value in scalar uncoercible to timestamp: %v", xs[0])
|
||||
}
|
||||
s.T = int64(ts) * 1000
|
||||
v, ok := xs[1].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("second value in scalar not string encoded: %v", xs[1])
|
||||
}
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.V = f
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *queryData) UnmarshalJSON(b []byte) error {
|
||||
type plain queryData
|
||||
if err := json.Unmarshal(b, (*plain)(d)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch d.ResultType {
|
||||
case parser.ValueTypeScalar:
|
||||
return json.Unmarshal(d.Result, &d.scalar)
|
||||
case parser.ValueTypeVector:
|
||||
return json.Unmarshal(d.Result, &d.vector)
|
||||
default:
|
||||
return fmt.Errorf("unexpected response type: %s", d.ResultType)
|
||||
}
|
||||
}
|
||||
|
||||
type sample struct {
|
||||
Metric labels.Labels `json:"metric"`
|
||||
Value scalar `json:"value"`
|
||||
}
|
||||
type vector []sample
|
||||
|
||||
func instantQueryResults(resp instantQueryResponse) (eval.Results, error) {
|
||||
if resp.Error != "" || resp.Status != "success" {
|
||||
return nil, errors.New(resp.Error)
|
||||
}
|
||||
|
||||
switch resp.Data.ResultType {
|
||||
case parser.ValueTypeScalar:
|
||||
return eval.Results{{
|
||||
Instance: map[string]string{},
|
||||
State: eval.Alerting,
|
||||
EvaluatedAt: cortex_util.TimeFromMillis(resp.Data.scalar.T),
|
||||
EvaluationString: extractEvalStringFromProm(sample{
|
||||
Value: resp.Data.scalar,
|
||||
}),
|
||||
}}, nil
|
||||
case parser.ValueTypeVector:
|
||||
results := make(eval.Results, 0, len(resp.Data.vector))
|
||||
for _, s := range resp.Data.vector {
|
||||
results = append(results, eval.Result{
|
||||
Instance: s.Metric.Map(),
|
||||
State: eval.Alerting,
|
||||
EvaluatedAt: cortex_util.TimeFromMillis(s.Value.T),
|
||||
EvaluationString: extractEvalStringFromProm(s),
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected response type: %s", resp.Data.ResultType)
|
||||
}
|
||||
}
|
||||
|
||||
func instantQueryResultsExtractor(b []byte) (interface{}, error) {
|
||||
var resp instantQueryResponse
|
||||
err := json.Unmarshal(b, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := instantQueryResults(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
frame := res.AsDataFrame()
|
||||
|
||||
return util.DynMap{
|
||||
"instances": []*data.Frame{&frame},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// extractEvalStringFromProm is intended to mimic the functionality used in ngalert/eval
|
||||
func extractEvalStringFromProm(s sample) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("[ ")
|
||||
var ls string
|
||||
if len(s.Metric) > 0 {
|
||||
ls = s.Metric.String()
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("labels={%s} ", ls))
|
||||
sb.WriteString(fmt.Sprintf("value=%v ", fmt.Sprintf("%v", s.Value.V)))
|
||||
sb.WriteString("]")
|
||||
return sb.String()
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
"github.com/prometheus/prometheus/promql/parser"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_instantQueryMarshaling(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
in string
|
||||
exp parser.ValueType
|
||||
expScalar *scalar
|
||||
expVector *vector
|
||||
isError bool // successfully unpack an upstream error
|
||||
}{
|
||||
{
|
||||
desc: "scalar",
|
||||
in: `{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "scalar",
|
||||
"result": [
|
||||
12,
|
||||
"2"
|
||||
]
|
||||
}
|
||||
}`,
|
||||
exp: parser.ValueTypeScalar,
|
||||
expScalar: &scalar{
|
||||
T: 12000,
|
||||
V: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "vector",
|
||||
in: `{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "vector",
|
||||
"result": [
|
||||
{
|
||||
"metric": {
|
||||
"__name__": "apiserver_request:burnrate1d"
|
||||
},
|
||||
"value": [
|
||||
12.04,
|
||||
"10.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`,
|
||||
exp: parser.ValueTypeVector,
|
||||
expVector: &vector{
|
||||
sample{
|
||||
Value: scalar{
|
||||
T: 12000, // loses some precision during marshaling
|
||||
V: 10.5,
|
||||
},
|
||||
Metric: []labels.Label{{
|
||||
Name: "__name__",
|
||||
Value: "apiserver_request:burnrate1d",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "successfully parse error",
|
||||
in: `{
|
||||
"status": "failure",
|
||||
"errorType": "someErr",
|
||||
"error": "error doing something"
|
||||
}`,
|
||||
isError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
var out instantQueryResponse
|
||||
err := json.Unmarshal([]byte(tc.in), &out)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.isError {
|
||||
require.Equal(t, out.Status, "failure")
|
||||
require.Greater(t, len(out.ErrorType), 0)
|
||||
require.Greater(t, len(out.Error), 0)
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, tc.exp, out.Data.ResultType)
|
||||
b, err := json.MarshalIndent(out, "", " ")
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.in, string(b))
|
||||
|
||||
if tc.expScalar != nil {
|
||||
require.Equal(t, *tc.expScalar, out.Data.scalar)
|
||||
}
|
||||
if tc.expVector != nil {
|
||||
require.Equal(t, *tc.expVector, out.Data.vector)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -229,7 +229,6 @@ func conditionEval(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand,
|
||||
}
|
||||
|
||||
frame := evalResults.AsDataFrame()
|
||||
|
||||
return response.JSONStreaming(http.StatusOK, util.DynMap{
|
||||
"instances": []*data.Frame{&frame},
|
||||
})
|
||||
|
||||
@@ -331,12 +331,21 @@ func (evalResults Results) AsDataFrame() data.Frame {
|
||||
frame.Fields = append(frame.Fields, data.NewField(lKey, nil, make([]string, fieldLen)))
|
||||
}
|
||||
frame.Fields = append(frame.Fields, data.NewField("State", nil, make([]string, fieldLen)))
|
||||
frame.Fields = append(frame.Fields, data.NewField("Info", nil, make([]string, fieldLen)))
|
||||
|
||||
for evalIdx, evalResult := range evalResults {
|
||||
for lIdx, v := range labelColumns {
|
||||
frame.Set(lIdx, evalIdx, evalResult.Instance[v])
|
||||
}
|
||||
|
||||
frame.Set(len(labelColumns), evalIdx, evalResult.State.String())
|
||||
|
||||
switch {
|
||||
case evalResult.Error != nil:
|
||||
frame.Set(len(labelColumns)+1, evalIdx, evalResult.Error.Error())
|
||||
case evalResult.EvaluationString != "":
|
||||
frame.Set(len(labelColumns)+1, evalIdx, evalResult.EvaluationString)
|
||||
}
|
||||
}
|
||||
return *frame
|
||||
}
|
||||
|
||||
@@ -432,8 +432,6 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
|
||||
n, err = channels.NewSensuGoNotifier(cfg, tmpl)
|
||||
case "discord":
|
||||
n, err = channels.NewDiscordNotifier(cfg, tmpl)
|
||||
case "alertmanager":
|
||||
n, err = channels.NewAlertmanagerNotifier(cfg, tmpl)
|
||||
case "googlechat":
|
||||
n, err = channels.NewGoogleChatNotifier(cfg, tmpl)
|
||||
case "line":
|
||||
|
||||
@@ -12,25 +12,24 @@ import (
|
||||
const DefaultTemplateString = `
|
||||
{{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }}
|
||||
|
||||
{{ define "__text_alert_list" }}{{ range . }}Labels:
|
||||
{{ define "__text_alert_list" }}{{ range . }}
|
||||
Labels:
|
||||
{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }}
|
||||
{{ end }}Annotations:
|
||||
{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }}
|
||||
{{ end }}Source: {{ .GeneratorURL }}
|
||||
{{ end }}{{ end }}
|
||||
{{ end }}{{ if gt (len .GeneratorURL) 0 }}Source: {{ .GeneratorURL }}
|
||||
{{ end }}{{ if gt (len .SilenceURL) 0 }}Silence: {{ .SilenceURL }}
|
||||
{{ end }}{{ if gt (len .DashboardURL) 0 }}Dashboard: {{ .DashboardURL }}
|
||||
{{ end }}{{ if gt (len .PanelURL) 0 }}Panel: {{ .PanelURL }}
|
||||
{{ end }}{{ end }}{{ end }}
|
||||
|
||||
{{ define "default.title" }}{{ template "__subject" . }}{{ end }}
|
||||
|
||||
{{ define "default.message" }}{{ if gt (len .Alerts.Firing) 0 }}
|
||||
**Firing**
|
||||
{{ template "__text_alert_list" .Alerts.Firing }}
|
||||
{{ define "default.message" }}{{ if gt (len .Alerts.Firing) 0 }}**Firing**
|
||||
{{ template "__text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }}
|
||||
|
||||
{{ end }}
|
||||
{{ if gt (len .Alerts.Resolved) 0 }}
|
||||
**Resolved**
|
||||
{{ template "__text_alert_list" .Alerts.Resolved }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved**
|
||||
{{ template "__text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }}
|
||||
`
|
||||
|
||||
func templateForTests(t *testing.T) *template.Template {
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
|
||||
@@ -16,7 +14,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/logging"
|
||||
)
|
||||
|
||||
const defaultDingdingMsgType = "link"
|
||||
@@ -78,9 +75,11 @@ func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
|
||||
// Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9
|
||||
messageURL := "dingtalk://dingtalkclient/page/link?" + q.Encode()
|
||||
|
||||
data := notify.GetTemplateData(ctx, dd.tmpl, as, gokit_log.NewLogfmtLogger(logging.NewWrapper(dd.log)))
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(dd.tmpl, data, &tmplErr)
|
||||
tmpl, _, err := TmplText(ctx, dd.tmpl, as, dd.log, &tmplErr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
message := tmpl(dd.Message)
|
||||
title := tmpl(`{{ template "default.title" . }}`)
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestDingdingNotifier(t *testing.T) {
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -48,7 +48,7 @@ func TestDingdingNotifier(t *testing.T) {
|
||||
"msgtype": "link",
|
||||
"link": map[string]interface{}{
|
||||
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%2Falerting%2Flist",
|
||||
"text": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"text": "**Firing**\n\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matchers=alertname%3Dalert1%2Clbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
|
||||
"title": "[FIRING:1] (val1)",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
|
||||
@@ -58,14 +56,16 @@ func NewDiscordNotifier(model *NotificationChannelConfig, t *template.Template)
|
||||
}
|
||||
|
||||
func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
data := notify.GetTemplateData(ctx, d.tmpl, as, gokit_log.NewNopLogger())
|
||||
alerts := types.Alerts(as...)
|
||||
|
||||
bodyJSON := simplejson.New()
|
||||
bodyJSON.Set("username", "Grafana")
|
||||
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(d.tmpl, data, &tmplErr)
|
||||
tmpl, _, err := TmplText(ctx, d.tmpl, as, d.log, &tmplErr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if d.Content != "" {
|
||||
bodyJSON.Set("content", tmpl(d.Content))
|
||||
}
|
||||
|
||||
@@ -40,12 +40,12 @@ func TestDiscordNotifier(t *testing.T) {
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: map[string]interface{}{
|
||||
"content": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"content": "**Firing**\n\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matchers=alertname%3Dalert1%2Clbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
|
||||
"embeds": []interface{}{map[string]interface{}{
|
||||
"color": 1.4037554e+07,
|
||||
"footer": map[string]interface{}{
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
|
||||
@@ -16,7 +14,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/logging"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@@ -66,13 +63,11 @@ func NewEmailNotifier(model *NotificationChannelConfig, t *template.Template) (*
|
||||
|
||||
// Notify sends the alert notification.
|
||||
func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
// We only need ExternalURL from this template object. This hack should go away with https://github.com/prometheus/alertmanager/pull/2508.
|
||||
data, err := ExtendData(notify.GetTemplateData(ctx, &template.Template{ExternalURL: en.tmpl.ExternalURL}, as, gokit_log.NewLogfmtLogger(logging.NewWrapper(en.log))))
|
||||
var tmplErr error
|
||||
tmpl, data, err := TmplText(ctx, en.tmpl, as, en.log, &tmplErr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var tmplErr error
|
||||
tmpl := TmplText(en.tmpl, data, &tmplErr)
|
||||
|
||||
title := tmpl(`{{ template "default.title" . }}`)
|
||||
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
|
||||
@@ -52,9 +50,11 @@ func NewGoogleChatNotifier(model *NotificationChannelConfig, t *template.Templat
|
||||
func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
gcn.log.Debug("Executing Google Chat notification")
|
||||
|
||||
data := notify.GetTemplateData(ctx, gcn.tmpl, as, gokit_log.NewNopLogger())
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(gcn.tmpl, data, &tmplErr)
|
||||
tmpl, _, err := TmplText(ctx, gcn.tmpl, as, gcn.log, &tmplErr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
widgets := []widget{}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestGoogleChatNotifier(t *testing.T) {
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -58,7 +58,7 @@ func TestGoogleChatNotifier(t *testing.T) {
|
||||
Widgets: []widget{
|
||||
textParagraphWidget{
|
||||
Text: text{
|
||||
Text: "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
Text: "**Firing**\n\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matchers=alertname%3Dalert1%2Clbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
|
||||
},
|
||||
},
|
||||
buttonWidget{
|
||||
@@ -118,7 +118,7 @@ func TestGoogleChatNotifier(t *testing.T) {
|
||||
Widgets: []widget{
|
||||
textParagraphWidget{
|
||||
Text: text{
|
||||
Text: "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \nLabels:\n - alertname = alert1\n - lbl1 = val2\nAnnotations:\n - ann1 = annv2\nSource: \n\n\n\n\n",
|
||||
Text: "**Firing**\n\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matchers=alertname%3Dalert1%2Clbl1%3Dval1\n\nLabels:\n - alertname = alert1\n - lbl1 = val2\nAnnotations:\n - ann1 = annv2\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matchers=alertname%3Dalert1%2Clbl1%3Dval2\n",
|
||||
},
|
||||
},
|
||||
buttonWidget{
|
||||
|
||||
@@ -3,8 +3,8 @@ package channels
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
@@ -66,9 +66,11 @@ func (kn *KafkaNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
|
||||
kn.log.Debug("Notifying Kafka", "alert_state", state)
|
||||
|
||||
data := notify.GetTemplateData(ctx, kn.tmpl, as, gokit_log.NewNopLogger())
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(kn.tmpl, data, &tmplErr)
|
||||
tmpl, _, err := TmplText(ctx, kn.tmpl, as, kn.log, &tmplErr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
bodyJSON := simplejson.New()
|
||||
bodyJSON.Set("alert_state", state)
|
||||
@@ -103,7 +105,7 @@ func (kn *KafkaNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
return false, err
|
||||
}
|
||||
|
||||
topicURL := kn.Endpoint + "/topics/" + kn.Topic
|
||||
topicURL := strings.TrimRight(kn.Endpoint, "/") + "/topics/" + kn.Topic
|
||||
|
||||
cmd := &models.SendWebhookSync{
|
||||
Url: topicURL,
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestKafkaNotifier(t *testing.T) {
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -54,7 +54,7 @@ func TestKafkaNotifier(t *testing.T) {
|
||||
"client": "Grafana",
|
||||
"client_url": "http://localhost/alerting/list",
|
||||
"description": "[FIRING:1] (val1)",
|
||||
"details": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"details": "**Firing**\n\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matchers=alertname%3Dalert1%2Clbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
|
||||
"incident_key": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733"
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func TestKafkaNotifier(t *testing.T) {
|
||||
"client": "Grafana",
|
||||
"client_url": "http://localhost/alerting/list",
|
||||
"description": "[FIRING:2] ",
|
||||
"details": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \nLabels:\n - alertname = alert1\n - lbl1 = val2\nAnnotations:\n - ann1 = annv2\nSource: \n\n\n\n\n",
|
||||
"details": "**Firing**\n\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matchers=alertname%3Dalert1%2Clbl1%3Dval1\n\nLabels:\n - alertname = alert1\n - lbl1 = val2\nAnnotations:\n - ann1 = annv2\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matchers=alertname%3Dalert1%2Clbl1%3Dval2\n",
|
||||
"incident_key": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ import (
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
|
||||
@@ -18,7 +16,7 @@ import (
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
)
|
||||
|
||||
const (
|
||||
var (
|
||||
LineNotifyURL string = "https://notify-api.line.me/api/notify"
|
||||
)
|
||||
|
||||
@@ -58,9 +56,11 @@ func (ln *LineNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e
|
||||
|
||||
ruleURL := path.Join(ln.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
|
||||
data := notify.GetTemplateData(ctx, ln.tmpl, as, gokit_log.NewNopLogger())
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(ln.tmpl, data, &tmplErr)
|
||||
tmpl, _, err := TmplText(ctx, ln.tmpl, as, ln.log, &tmplErr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
body := fmt.Sprintf(
|
||||
"%s\n%s\n\n%s",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user