Merge branch 'v8.4.x' of github.com:grafana/grafana into v8.4.x

This commit is contained in:
Andrej Ocenas
2022-02-16 16:11:07 +01:00
427 changed files with 43532 additions and 3072 deletions
+148 -124
View File
@@ -11,7 +11,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -111,7 +111,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -309,15 +309,18 @@ steps:
- package
image: grafana/build-container:1.4.9
name: copy-packages-for-docker
- depends_on:
- commands:
- ./bin/grabpl build-docker --edition oss -archs amd64
depends_on:
- copy-packages-for-docker
image: grafana/drone-grafana-docker:0.3.2
environment:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: build-docker-images
settings:
archs: amd64
dry_run: true
edition: oss
ubuntu: false
volumes:
- name: docker
path: /var/run/docker.sock
trigger:
event:
- pull_request
@@ -359,7 +362,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -420,7 +423,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -521,7 +524,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -761,30 +764,66 @@ steps:
- package
image: grafana/build-container:1.4.9
name: copy-packages-for-docker
- depends_on:
- commands:
- ./bin/grabpl build-docker --edition oss
depends_on:
- copy-packages-for-docker
image: grafana/drone-grafana-docker:0.3.2
environment:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: build-docker-images
settings:
dry_run: false
edition: oss
password:
from_secret: docker_password
ubuntu: false
username:
from_secret: docker_user
- depends_on:
volumes:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl build-docker --edition oss --ubuntu
depends_on:
- copy-packages-for-docker
image: grafana/drone-grafana-docker:0.3.2
environment:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: build-docker-images-ubuntu
settings:
dry_run: false
edition: oss
password:
volumes:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl artifacts docker publish --dockerhub-repo grafana --base alpine --base
ubuntu --arch amd64 --arch arm64 --arch armv7
depends_on:
- build-docker-images
- build-docker-images-ubuntu
environment:
DOCKER_PASSWORD:
from_secret: docker_password
ubuntu: true
username:
from_secret: docker_user
DOCKER_USER:
from_secret: docker_username
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: publish-images-grafana
volumes:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl artifacts docker publish --dockerhub-repo grafana-oss --base alpine
--base ubuntu --arch amd64 --arch arm64 --arch armv7
depends_on:
- build-docker-images
- build-docker-images-ubuntu
environment:
DOCKER_PASSWORD:
from_secret: docker_password
DOCKER_USER:
from_secret: docker_username
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: publish-images-grafana-oss
volumes:
- name: docker
path: /var/run/docker.sock
- commands:
- ./scripts/circle-release-canary-packages.sh
depends_on:
@@ -805,18 +844,18 @@ steps:
- end-to-end-tests-smoke-tests-suite
- end-to-end-tests-various-suite
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
image: grafana/grafana-ci-deploy:1.3.1
name: upload-packages
- commands:
- ./bin/grabpl upload-cdn --edition oss --bucket "grafana-static-assets"
- ./bin/grabpl upload-cdn --edition oss --src-bucket "grafana-static-assets"
depends_on:
- grafana-server
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -870,7 +909,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -938,7 +977,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/windows/grabpl.exe
-OutFile grabpl.exe
image: grafana/ci-wix:0.1.1
name: initialize
@@ -1021,7 +1060,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -1034,7 +1073,6 @@ steps:
image: grafana/build-container:1.4.9
name: initialize
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- ./bin/grabpl store-packages --edition oss --gcp-key /tmp/gcpkey.json --build-id
${DRONE_BUILD_NUMBER}
depends_on:
@@ -1094,7 +1132,7 @@ type: docker
---
depends_on: []
kind: pipeline
name: oss-build-publish-e2e-release
name: oss-build-e2e-publish-release
node:
type: no-parallel
platform:
@@ -1104,7 +1142,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -1202,8 +1240,6 @@ steps:
image: grafana/build-container:1.4.9
name: copy-packages-for-docker
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
- ./bin/grabpl build-docker --edition oss --shouldSave
depends_on:
- copy-packages-for-docker
@@ -1211,13 +1247,11 @@ steps:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: package-docker-images
name: build-docker-images
volumes:
- name: docker
path: /var/run/docker.sock
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
- ./bin/grabpl build-docker --edition oss --shouldSave --ubuntu
depends_on:
- copy-packages-for-docker
@@ -1225,7 +1259,7 @@ steps:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: package-docker-images-ubuntu
name: build-docker-images-ubuntu
volumes:
- name: docker
path: /var/run/docker.sock
@@ -1319,11 +1353,12 @@ steps:
image: grafana/build-container:1.4.9
name: build-storybook
- commands:
- ./bin/grabpl upload-cdn --edition oss --bucket "$${PRERELEASE_BUCKET}/artifacts/static-assets"
- ./bin/grabpl upload-cdn --edition oss --src-bucket "$${PRERELEASE_BUCKET}" --src-dir
artifacts/static-assets
depends_on:
- grafana-server
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -1337,7 +1372,7 @@ steps:
- end-to-end-tests-smoke-tests-suite
- end-to-end-tests-various-suite
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -1411,7 +1446,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -1532,7 +1567,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -1599,7 +1634,7 @@ volumes:
medium: memory
---
depends_on:
- oss-build-publish-e2e-release
- oss-build-e2e-publish-release
- oss-test-release
- oss-integration-tests-release
kind: pipeline
@@ -1616,7 +1651,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/windows/grabpl.exe
-OutFile grabpl.exe
image: grafana/ci-wix:0.1.1
name: initialize
@@ -1675,7 +1710,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -1807,8 +1842,6 @@ steps:
image: grafana/build-container:1.4.9
name: copy-packages-for-docker
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
- ./bin/grabpl build-docker --edition enterprise --shouldSave
depends_on:
- copy-packages-for-docker
@@ -1816,13 +1849,11 @@ steps:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: package-docker-images
name: build-docker-images
volumes:
- name: docker
path: /var/run/docker.sock
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
- ./bin/grabpl build-docker --edition enterprise --shouldSave --ubuntu
depends_on:
- copy-packages-for-docker
@@ -1830,7 +1861,7 @@ steps:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: package-docker-images-ubuntu
name: build-docker-images-ubuntu
volumes:
- name: docker
path: /var/run/docker.sock
@@ -1916,11 +1947,12 @@ steps:
- success
- failure
- commands:
- ./bin/grabpl upload-cdn --edition enterprise --bucket "$${PRERELEASE_BUCKET}/artifacts/static-assets"
- ./bin/grabpl upload-cdn --edition enterprise --src-bucket "$${PRERELEASE_BUCKET}"
--src-dir artifacts/static-assets
depends_on:
- package
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -1931,7 +1963,7 @@ steps:
depends_on:
- package
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -1959,11 +1991,12 @@ steps:
image: grafana/build-container:1.4.9
name: package-enterprise2
- commands:
- ./bin/grabpl upload-cdn --edition enterprise2 --bucket "$${PRERELEASE_BUCKET}/artifacts/static-assets"
- ./bin/grabpl upload-cdn --edition enterprise2 --src-bucket "$${PRERELEASE_BUCKET}"
--src-dir artifacts/static-assets
depends_on:
- package-enterprise2
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -1974,7 +2007,7 @@ steps:
depends_on:
- package-enterprise2
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -2017,7 +2050,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2190,7 +2223,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2318,7 +2351,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/windows/grabpl.exe
-OutFile grabpl.exe
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
- cd grafana-enterprise
@@ -2393,7 +2426,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2415,8 +2448,8 @@ steps:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl artifacts docker publish --version-tag ${TAG} --dockerhub-repo grafana
--base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7
- ./bin/grabpl artifacts docker publish --dockerhub-repo grafana --base alpine --base
ubuntu --arch amd64 --arch arm64 --arch armv7 --version-tag ${TAG}
depends_on:
- fetch-images-oss
environment:
@@ -2432,8 +2465,8 @@ steps:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl artifacts docker publish --version-tag ${TAG} --dockerhub-repo grafana-oss
--base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7
- ./bin/grabpl artifacts docker publish --dockerhub-repo grafana-oss --base alpine
--base ubuntu --arch amd64 --arch arm64 --arch armv7 --version-tag ${TAG}
depends_on:
- fetch-images-oss
environment:
@@ -2471,7 +2504,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2493,8 +2526,8 @@ steps:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl artifacts docker publish --version-tag ${TAG} --dockerhub-repo grafana-enterprise
--base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7
- ./bin/grabpl artifacts docker publish --dockerhub-repo grafana-enterprise --base
alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7 --version-tag ${TAG}
depends_on:
- fetch-images-enterprise
environment:
@@ -2532,7 +2565,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2554,8 +2587,8 @@ steps:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl artifacts docker publish --security --version-tag ${TAG} --dockerhub-repo
grafana --base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7
- ./bin/grabpl artifacts docker publish --security --dockerhub-repo grafana --base
alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7 --version-tag ${TAG}
depends_on:
- fetch-images-oss
environment:
@@ -2571,8 +2604,9 @@ steps:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl artifacts docker publish --security --version-tag ${TAG} --dockerhub-repo
grafana-oss --base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7
- ./bin/grabpl artifacts docker publish --security --dockerhub-repo grafana-oss
--base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7 --version-tag
${TAG}
depends_on:
- fetch-images-oss
environment:
@@ -2610,7 +2644,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2632,9 +2666,9 @@ steps:
- name: docker
path: /var/run/docker.sock
- commands:
- ./bin/grabpl artifacts docker publish --security --version-tag ${TAG} --dockerhub-repo
grafana-enterprise --base alpine --base ubuntu --arch amd64 --arch arm64 --arch
armv7
- ./bin/grabpl artifacts docker publish --security --dockerhub-repo grafana-enterprise
--base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7 --version-tag
${TAG}
depends_on:
- fetch-images-enterprise
environment:
@@ -2672,7 +2706,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2708,7 +2742,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2755,7 +2789,7 @@ steps:
name: initialize
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2803,12 +2837,11 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- ./bin/grabpl store-packages --edition oss --packages-bucket grafana-downloads
--gcp-key /tmp/gcpkey.json ${DRONE_TAG}
depends_on:
@@ -2827,7 +2860,6 @@ steps:
image: grafana/grafana-ci-deploy:1.3.1
name: store-packages-oss
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- ./bin/grabpl store-packages --edition enterprise --packages-bucket grafana-downloads
--gcp-key /tmp/gcpkey.json ${DRONE_TAG}
depends_on:
@@ -2858,7 +2890,7 @@ volumes:
---
depends_on: []
kind: pipeline
name: oss-build-publish-e2e-release-branch
name: oss-build-e2e-publish-release-branch
node:
type: no-parallel
platform:
@@ -2868,7 +2900,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -2963,8 +2995,6 @@ steps:
image: grafana/build-container:1.4.9
name: copy-packages-for-docker
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
- ./bin/grabpl build-docker --edition oss --shouldSave
depends_on:
- copy-packages-for-docker
@@ -2972,13 +3002,11 @@ steps:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: package-docker-images
name: build-docker-images
volumes:
- name: docker
path: /var/run/docker.sock
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
- ./bin/grabpl build-docker --edition oss --shouldSave --ubuntu
depends_on:
- copy-packages-for-docker
@@ -2986,7 +3014,7 @@ steps:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: package-docker-images-ubuntu
name: build-docker-images-ubuntu
volumes:
- name: docker
path: /var/run/docker.sock
@@ -3080,11 +3108,11 @@ steps:
image: grafana/build-container:1.4.9
name: build-storybook
- commands:
- ./bin/grabpl upload-cdn --edition oss --bucket "grafana-static-assets"
- ./bin/grabpl upload-cdn --edition oss --src-bucket "grafana-static-assets"
depends_on:
- grafana-server
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -3098,7 +3126,7 @@ steps:
- end-to-end-tests-smoke-tests-suite
- end-to-end-tests-various-suite
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -3131,7 +3159,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3245,7 +3273,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3305,7 +3333,7 @@ volumes:
medium: memory
---
depends_on:
- oss-build-publish-e2e-release-branch
- oss-build-e2e-publish-release-branch
- oss-test-release-branch
- oss-integration-tests-release-branch
kind: pipeline
@@ -3322,7 +3350,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/windows/grabpl.exe
-OutFile grabpl.exe
image: grafana/ci-wix:0.1.1
name: initialize
@@ -3370,7 +3398,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3496,8 +3524,6 @@ steps:
image: grafana/build-container:1.4.9
name: copy-packages-for-docker
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
- ./bin/grabpl build-docker --edition enterprise --shouldSave
depends_on:
- copy-packages-for-docker
@@ -3505,13 +3531,11 @@ steps:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: package-docker-images
name: build-docker-images
volumes:
- name: docker
path: /var/run/docker.sock
- commands:
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
- ./bin/grabpl build-docker --edition enterprise --shouldSave --ubuntu
depends_on:
- copy-packages-for-docker
@@ -3519,7 +3543,7 @@ steps:
GCP_KEY:
from_secret: gcp_key
image: google/cloud-sdk
name: package-docker-images-ubuntu
name: build-docker-images-ubuntu
volumes:
- name: docker
path: /var/run/docker.sock
@@ -3614,11 +3638,11 @@ steps:
image: grafana/build-container:1.4.9
name: build-storybook
- commands:
- ./bin/grabpl upload-cdn --edition enterprise --bucket "grafana-static-assets"
- ./bin/grabpl upload-cdn --edition enterprise --src-bucket "grafana-static-assets"
depends_on:
- package
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -3629,7 +3653,7 @@ steps:
depends_on:
- package
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -3657,11 +3681,11 @@ steps:
image: grafana/build-container:1.4.9
name: package-enterprise2
- commands:
- ./bin/grabpl upload-cdn --edition enterprise2 --bucket "grafana-static-assets"
- ./bin/grabpl upload-cdn --edition enterprise2 --src-bucket "grafana-static-assets"
depends_on:
- package-enterprise2
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -3672,7 +3696,7 @@ steps:
depends_on:
- package-enterprise2
environment:
GCP_GRAFANA_UPLOAD_KEY:
GCP_KEY:
from_secret: gcp_key
PRERELEASE_BUCKET:
from_secret: prerelease_bucket
@@ -3709,7 +3733,7 @@ services: []
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3874,7 +3898,7 @@ services:
steps:
- commands:
- mkdir -p bin
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/grabpl
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/grabpl
- chmod +x bin/grabpl
image: byrnedo/alpine-curl:0.1.8
name: grabpl
@@ -3994,7 +4018,7 @@ steps:
name: identify-runner
- commands:
- $$ProgressPreference = "SilentlyContinue"
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.8.7/windows/grabpl.exe
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.0/windows/grabpl.exe
-OutFile grabpl.exe
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
- cd grafana-enterprise
@@ -4191,6 +4215,6 @@ kind: secret
name: gcp_upload_artifacts_key
---
kind: signature
hmac: 4d1a5696bf1e510fb51a021c07e240c50cb913724ce08ed52cce037ff02dd8de
hmac: 713e8fdfc36e773a17e3fc79107bd1dd1647400f83f6d40922f566b1c7f97edb
...
@@ -43,18 +43,18 @@ jobs:
name: buildPr
path: './pr/pr_built_packages.zip'
buildMain:
name: Build Main
buildBase:
name: Build Base
runs-on: ubuntu-latest
defaults:
run:
working-directory: './main'
working-directory: './base'
steps:
- uses: actions/checkout@v2
with:
path: './main'
ref: 'main'
path: './base'
ref: ${{ github.event.pull_request.base.ref }}
- name: Get yarn cache directory path
id: yarn-cache-dir-path
@@ -76,18 +76,18 @@ jobs:
run: yarn packages:build
- name: Zip built packages
run: zip -r ./main_built_packages.zip ./packages/**/dist
run: zip -r ./base_built_packages.zip ./packages/**/dist
- name: Upload build output as artifact
uses: actions/upload-artifact@v2
with:
name: buildMain
path: './main/main_built_packages.zip'
name: buildBase
path: './base/base_built_packages.zip'
Detect:
name: Detect breaking changes
runs-on: ubuntu-latest
needs: ['buildPR', 'buildMain']
needs: ['buildPR', 'buildBase']
env:
GITHUB_STEP_NUMBER: 7
@@ -99,16 +99,16 @@ jobs:
with:
name: buildPr
- name: Get built packages from main
- name: Get built packages from base
uses: actions/download-artifact@v2
with:
name: buildMain
name: buildBase
- name: Unzip artifact from pr
run: unzip pr_built_packages.zip -d ./pr && rm pr_built_packages.zip
- name: Unzip artifact from main
run: unzip main_built_packages.zip -d ./main && rm main_built_packages.zip
- name: Unzip artifact from base
run: unzip base_built_packages.zip -d ./base && rm base_built_packages.zip
- name: Get link for the Github Action job
id: job
+13 -17
View File
@@ -9,28 +9,28 @@ packageExtensions:
"@mdx-js/loader@1.6.22":
peerDependencies:
react: 17.0.1
"@storybook/addon-docs@6.4.13":
"@storybook/addon-docs@6.4.15":
peerDependencies:
"@storybook/manager-webpack5": 6.4.13
"@storybook/addon-essentials@6.4.13":
"@storybook/manager-webpack5": 6.4.15
"@storybook/addon-essentials@6.4.15":
peerDependencies:
"@storybook/components": 6.4.13
"@storybook/core-events": 6.4.13
"@storybook/manager-webpack5": 6.4.13
"@storybook/theming": 6.4.13
"@storybook/core-server@6.4.13":
"@storybook/components": 6.4.15
"@storybook/core-events": 6.4.15
"@storybook/manager-webpack5": 6.4.15
"@storybook/theming": 6.4.15
"@storybook/core-server@6.4.15":
peerDependencies:
"@babel/core": ^7.0.0
"@storybook/core@6.4.13":
"@storybook/core@6.4.15":
peerDependencies:
"@babel/core": ^7.0.0
"@storybook/manager-webpack5": 6.4.13
"@storybook/csf-tools@6.4.13":
"@storybook/manager-webpack5": 6.4.15
"@storybook/csf-tools@6.4.15":
peerDependencies:
"@babel/core": ^7.0.0
"@storybook/react@6.4.13":
"@storybook/react@6.4.15":
peerDependencies:
"@storybook/manager-webpack5": 6.4.13
"@storybook/manager-webpack5": 6.4.15
doctrine@3.0.0:
dependencies:
assert: 2.0.0
@@ -60,10 +60,6 @@ packageExtensions:
react-simple-compat: 1.2.1
peerDependencies:
framework-utils: ^1.1.0
react-dev-utils@11.0.4:
peerDependencies:
typescript: 4.4.3
webpack: 5.51.1
react-docgen-typescript-loader@3.7.2:
peerDependencies:
webpack: 4.41.5
+37
View File
@@ -1,3 +1,32 @@
<!-- 8.4.0-beta1 START -->
# 8.4.0-beta1 (2022-02-02)
### Features and enhancements
- **Alerting:** Support WeCom as a contact point type. [#40975](https://github.com/grafana/grafana/pull/40975), [@smallpath](https://github.com/smallpath)
- **Alerting:** UI for mute timings. [#41578](https://github.com/grafana/grafana/pull/41578), [@nathanrodman](https://github.com/nathanrodman)
- **Alerting:** add settings for peer reconnection in HA mode. [#42300](https://github.com/grafana/grafana/pull/42300), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ)
- **Auth:** implement auto_sign_up for auth.jwt. [#37040](https://github.com/grafana/grafana/pull/37040), [@Roguelazer](https://github.com/Roguelazer)
- **Dashboard:** Add Show unknown variables toggle to dashboard settings. [#41854](https://github.com/grafana/grafana/pull/41854), [@hugohaggmark](https://github.com/hugohaggmark)
- **Instrumentation:** Logger migration from log15 to gokit/log. [#41636](https://github.com/grafana/grafana/pull/41636), [@ying-jeanne](https://github.com/ying-jeanne)
- **MSSQL:** Change regex to validate Provider connection string. [#40248](https://github.com/grafana/grafana/pull/40248), [@ianselmi](https://github.com/ianselmi)
- **MSSQL:** Configuration of certificate verification for TLS connection. [#31865](https://github.com/grafana/grafana/pull/31865), [@mortenaa](https://github.com/mortenaa)
- **Middleware:** Don't require HTTPS for HSTS headers to be emitted. [#35147](https://github.com/grafana/grafana/pull/35147), [@alexmv](https://github.com/alexmv)
- **Navigation:** Implement Keyboard Navigation. [#41618](https://github.com/grafana/grafana/pull/41618), [@axelavargas](https://github.com/axelavargas)
- **News:** Reload feed when changing the time range or refreshing. [#42217](https://github.com/grafana/grafana/pull/42217), [@ashharrison90](https://github.com/ashharrison90)
- **UI/Plot:** Implement keyboard controls for plot cursor. [#42244](https://github.com/grafana/grafana/pull/42244), [@kaydelaney](https://github.com/kaydelaney)
<!-- 8.4.0-beta1 END -->
<!-- 8.3.5 START -->
# 8.3.5 (2022-02-08)
- **Security**: Fixes CVE-2022-21702. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
- **Security**: Fixes CVE-2022-21703. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
- **Security**: Fixes CVE-2022-21713. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
<!-- 8.3.5 END -->
<!-- 8.3.4 START -->
# 8.3.4 (2022-01-17)
@@ -1259,7 +1288,15 @@ Issue [#33352](https://github.com/grafana/grafana/issues/33352)
- **AGPL License:** Update license from Apache 2.0 to the GNU Affero General Public License (AGPL). [#33184](https://github.com/grafana/grafana/pull/33184)
<!-- 8.0.0-beta1 END -->
<!-- 7.5.15 START -->
# 7.5.15 (2022-02-08)
- **Security**: Fixes CVE-2022-21702. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
- **Security**: Fixes CVE-2022-21703. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
- **Security**: Fixes CVE-2022-21713. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
<!-- 7.5.15 END -->
<!-- 7.5.13 START -->
# 7.5.13 (2022-01-18)
+2 -1
View File
@@ -11,7 +11,7 @@ COPY plugins-bundled plugins-bundled
RUN yarn install
COPY tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js babel.config.json ./
COPY tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js babel.config.json .linguirc ./
COPY public public
COPY tools tools
COPY scripts scripts
@@ -30,6 +30,7 @@ COPY go.mod go.sum embed.go Makefile build.go package.json ./
COPY cue cue
COPY packages/grafana-schema packages/grafana-schema
COPY public/app/plugins public/app/plugins
COPY public/api-spec.json public/api-spec.json
COPY pkg pkg
COPY scripts scripts
COPY cue.mod cue.mod
+2 -1
View File
@@ -12,7 +12,7 @@ COPY plugins-bundled plugins-bundled
RUN yarn install
COPY tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js babel.config.json ./
COPY tsconfig.json .eslintrc .editorconfig .browserslistrc .prettierrc.js babel.config.json .linguirc ./
COPY public public
COPY tools tools
COPY scripts scripts
@@ -33,6 +33,7 @@ COPY cue cue/
COPY cue.mod cue.mod/
COPY packages/grafana-schema packages/grafana-schema/
COPY public/app/plugins public/app/plugins/
COPY public/api-spec.json public/api-spec.json
RUN go mod verify
RUN make build-go
+58 -2
View File
@@ -7,11 +7,13 @@ WIRE_TAGS = "oss"
-include local/Makefile
include .bingo/Variables.mk
.PHONY: all deps-go deps-js deps build-go build-server build-cli build-js build build-docker-full lint-go golangci-lint test-go test-js gen-ts test run run-frontend clean devenv devenv-down protobuf drone help
.PHONY: all deps-go deps-js deps build-go build-server build-cli build-js build build-docker-full build-docker-full-ubuntu lint-go golangci-lint test-go test-js gen-ts test run run-frontend clean devenv devenv-down protobuf drone help
GO = go
GO_FILES ?= ./pkg/...
SH_FILES ?= $(shell find ./scripts -name *.sh)
API_DEFINITION_FILES = $(shell find ./pkg/api/docs/definitions -name '*.go' -print)
SWAGGER_TAG ?= latest
all: deps build
@@ -28,13 +30,62 @@ node_modules: package.json yarn.lock ## Install node modules.
@echo "install frontend dependencies"
YARN_ENABLE_PROGRESS_BARS=false yarn install --immutable
##@ Swagger
SPEC_TARGET = public/api-spec.json
MERGED_SPEC_TARGET := public/api-merged.json
NGALERT_SPEC_TARGET = pkg/services/ngalert/api/tooling/post.json
$(SPEC_TARGET): $(API_DEFINITION_FILES) ## Generate API spec
docker run --rm -it \
-e GOPATH=${HOME}/go:/go \
-e SWAGGER_GENERATE_EXTENSION=false \
-v ${HOME}/go:/go \
-v $$(pwd):/grafana \
-w $$(pwd)/pkg/api/docs quay.io/goswagger/swagger:$(SWAGGER_TAG) \
generate spec -m -o /grafana/public/api-spec.json \
-w /grafana/pkg/server \
-x "grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \
-x "github.com/prometheus/alertmanager" \
-i /grafana/pkg/api/docs/tags.json
swagger-api-spec: gen-go $(SPEC_TARGET) $(MERGED_SPEC_TARGET)
$(NGALERT_SPEC_TARGET):
+$(MAKE) -C pkg/services/ngalert/api/tooling post.json
$(MERGED_SPEC_TARGET): $(SPEC_TARGET) $(NGALERT_SPEC_TARGET) ## Merge generated and ngalert API specs
go run pkg/api/docs/merge/merge_specs.go -o=public/api-merged.json $(<) pkg/services/ngalert/api/tooling/post.json
ensure_go-swagger_mac:
@hash swagger &>/dev/null || (brew tap go-swagger/go-swagger && brew install go-swagger)
--swagger-api-spec-mac: ensure_go-swagger_mac $(API_DEFINITION_FILES) ## Generate API spec (for M1 Mac)
swagger generate spec -m -w pkg/server -o public/api-spec.json \
-x "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" \
-x "github.com/prometheus/alertmanager" \
-i pkg/api/docs/tags.json
swagger-api-spec-mac: gen-go --swagger-api-spec-mac $(MERGED_SPEC_TARGET)
validate-api-spec: $(MERGED_SPEC_TARGET) ## Validate API spec
docker run --rm -it \
-e GOPATH=${HOME}/go:/go \
-e SWAGGER_GENERATE_EXTENSION=false \
-v ${HOME}/go:/go \
-v $$(pwd):/grafana \
-w $$(pwd)/pkg/api/docs quay.io/goswagger/swagger:$(SWAGGER_TAG) \
validate /grafana/$(<)
clean-api-spec:
rm $(SPEC_TARGET) $(MERGED_SPEC_TARGET)
##@ Building
gen-go: $(WIRE)
@echo "generate go files"
$(WIRE) gen -tags $(WIRE_TAGS) ./pkg/server ./pkg/cmd/grafana-cli/runner
build-go: gen-go ## Build all Go binaries.
build-go: $(MERGED_SPEC_TARGET) gen-go ## Build all Go binaries.
@echo "build go files"
$(GO) run build.go build
@@ -99,6 +150,11 @@ build-docker-full: ## Build Docker image for development.
@echo "build docker container"
docker build --tag grafana/grafana:dev .
build-docker-full-ubuntu: ## Build Docker image based on Ubuntu for development.
@echo "build docker container"
docker build --tag grafana/grafana:dev-ubuntu -f ./Dockerfile.ubuntu .
##@ Services
# create docker-compose file with provided sources and start them
+6
View File
@@ -122,6 +122,9 @@ path = grafana.db
# For "sqlite3" only. cache mode setting used for connecting to the database
cache_mode = private
# For "mysql" only if lockingMigration feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0.
locking_attempt_timeout_sec = 0
#################################### Cache server #############################
[remote_cache]
# Either "redis", "memcached" or "database" default is "database"
@@ -403,6 +406,9 @@ api_key_max_seconds_to_live = -1
# Set to true to enable SigV4 authentication option for HTTP-based datasources
sigv4_auth_enabled = false
# Set to true to enable verbose logging of SigV4 request signing
sigv4_verbose_logging = false
#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
+6
View File
@@ -123,6 +123,9 @@
# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
;cache_mode = private
# For "mysql" only if lockingMigration feature toggle is set. How many seconds to wait before failing to lock the database for the migrations, default is 0.
;locking_attempt_timeout_sec = 0
################################### Data sources #########################
[datasources]
# Upper limit of data sources that Grafana will return. This limit is a temporary configuration and it will be deprecated when pagination will be introduced on the list data sources API.
@@ -397,6 +400,9 @@
# Set to true to enable SigV4 authentication option for HTTP-based datasources.
;sigv4_auth_enabled = false
# Set to true to enable verbose logging of SigV4 request signing
;sigv4_verbose_logging = false
#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
@@ -314,6 +314,10 @@ The maximum number of open connections to the database.
Sets the maximum amount of time a connection may be reused. The default is 14400 (which means 14400 seconds or 4 hours). For MySQL, this setting should be shorter than the [`wait_timeout`](https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_wait_timeout) variable.
### locking_attempt_timeout_sec
For "mysql", if `lockingMigration` feature toggle is set, specify the time (in seconds) to wait before failing to lock the database for the migrations. Default is 0.
### log_queries
Set to `true` to log the sql calls and execution times.
@@ -757,6 +761,12 @@ Limit of API key seconds to live before expiration. Default is -1 (unlimited).
Set to `true` to enable the AWS Signature Version 4 Authentication option for HTTP-based datasources. Default is `false`.
### sigv4_verbose_logging
> Only available in Grafana 8.4+.
Set to `true` to enable verbose request signature logging when AWS Signature Version 4 Authentication is enabled. Default is `false`.
<hr />
## [auth.anonymous]
+5 -4
View File
@@ -15,10 +15,11 @@ Grafana 8.0 introduced new and improved alerting that centralizes alerting infor
Grafana alerting is enabled by default for new OSS installations. For older installations, it is still an [opt-in]({{< relref "./unified-alerting/opt-in.md" >}}) feature.
| Release | Cloud | Enterprise | OSS |
| ----------- | ------------- | ---------- | -------------------------------- |
| Grafana 8.2 | On by default | Opt-in | Opt-in |
| Grafana 8.3 | On by default | Opt-in | On by default for new installs\* |
| Release | Cloud | Enterprise | OSS |
| ------------------------ | ------------- | ------------- | -------------------------------- |
| Grafana 8.2 | On by default | Opt-in | Opt-in |
| Grafana 8.3 | On by default | Opt-in | On by default for new installs\* |
| Grafana 9.0 (unreleased) | On by default | On by default | On by default |
> **Note:** New installs include existing installs which do not have any alerts configured.
+1 -1
View File
@@ -7,7 +7,7 @@ weight = 114
Grafana alerting is enabled by default for new OSS installations. For older installations, it is still an [opt-in]({{< relref "../unified-alerting/opt-in.md" >}}) feature.
> **Note**: Legacy dashboard alerts is deprecated and will be removed in a future release. We encourage you to migrate to [Grafana alerting]({{< relref "../unified-alerting/_index.md" >}}) for all existing installations.
> **Note**: Legacy dashboard alerts are deprecated and will be removed in Grafana 9. We encourage you to migrate to [Grafana alerting]({{< relref "../unified-alerting/_index.md" >}}) for all existing installations.
Legacy dashboard alerts have two main components:
+1 -1
View File
@@ -43,7 +43,7 @@ Each derived field consists of:
- **Name -** Shown in the log details as a label.
- **Regex -** A Regex pattern that runs on the log message and captures part of it as the value of the new field. Can only contain a single capture group.
- **URL/query -** If the link is external, then enter the full link URL. If the link is internal link, then this input serves as query for the target data source. In both cases, you can interpolate the value from the field with `${__value.raw }` macro.
- **URL/query -** If the link is external, then enter the full link URL. If the link is internal link, then this input serves as query for the target data source. In both cases, you can interpolate the value from the field with `${__value.raw}` macro.
- **URL Label -** (Optional) Set a custom display label for the link. The link label defaults to the full external URL or name of the linked internal data source and is overridden by this setting.
- **Internal link -** Select if the link is internal or external. In case of internal link, a data source selector allows you to select the target data source. Only tracing data sources are supported.
@@ -0,0 +1,32 @@
+++
title = "Angular support deprecation"
keywords = ["grafana", "documentation", "developers", "resources"]
+++
# Angular support deprecation
Angular plugin support is deprecated, and it will be removed in a future release. There are still many community plugins that depend on Grafana’s angular plugin support for them to work. The same is true for many internal (private) plugins developed over the years by Grafana users. Grafana version 9 will have a server configuration option, global for the whole instance, that will control if angular plugin support is available or not. By default, angular plugin support will be disabled.
## Why are we deprecating angular support?
AngularJS is an old frontend framework that stopped active development many years ago. As a result, it is a security risk. AngularJS also requires unsafe-eval in the CSP (Content Security Policy) settings which also reduces the security level of how javascript is executed in the browser.
## When will angular plugins stop working?
In Grafana version 9 coming in June 2022, all angular plugins will stop working unless a new server configuration option is turned on. If you still depend on community or internally developed plugins that require AngularJS then you will have to turn this option on.
This is a good time to start working on migrating plugins to React.
Our plan is to fully remove angular plugin support in version 10 released in 2023. Meaning all plugins that do depend on angular will stop working and this temporary option to enable it introduced in v9 will be removed.
## How do I migrate an angular plugin to React?
Depending on if it’s a data source plugin, panel plugin, or app plugin the process will differ.
For panels, the rendering logic could in some cases be easily preserved but all options need to be redone to use the declarative options framework. For data source plugins the query editor and config options will likely need a total rewrite.
### Links
- [Migrate Angular to React](https://grafana.com/docs/grafana/latest/developers/plugins/migration-guide/#migrate-a-plugin-from-angular-to-react)
- [Build a panel plugin](https://grafana.com/tutorials/build-a-panel-plugin/)
- [Build a data source plugin](https://grafana.com/tutorials/build-a-data-source-plugin/)
@@ -80,26 +80,19 @@ For more information on the available variable formats, refer to [Advanced varia
## Set a variable from your plugin
Not only can you read the value of a variable, you can also update the variable from your plugin. Use LocationSrv.update()
Not only can you read the value of a variable, you can also update the variable from your plugin. Use `locationService.partial(query, replace)`.
The following example shows how to update a variable called `service`.
- `query` contains the query parameters you want to update. Query parameters controlling variables are prefixed with `var-`.
- `partial: true` makes the update only affect the query parameters listed in `query`, and leaves the other query parameters unchanged.
- `replace: true` tells Grafana to update the current URL state, rather than creating a new history entry.
```ts
import { getLocationSrv } from '@grafana/runtime';
import { locationService } from '@grafana/runtime';
```
```ts
getLocationSrv().update({
query: {
'var-service': 'billing',
},
partial: true,
replace: true,
});
locationService.partial({ 'var-service': 'billing' }, true);
```
> **Note:** Grafana queries your data source whenever you update a variable. Excessive updates to variables can slow down Grafana and lead to a poor user experience.
@@ -15,6 +15,8 @@ This guide helps you identify the steps you need to take based on the Grafana ve
- [From version 8.3.x to 8.4.x](#from-version-83x-to-84x)
- [Value Mapping Editor has been removed from @grafana-ui library](#value-mapping-editor-has-been-removed-from-grafana-ui-library)
- [Thresholds Editor has been removed from @grafana-ui library](#thresholds-editor-has-been-removed-from-grafana-ui-library)
- [8.4 Deprecations](#84-deprecations)
- [LocationService replaces getLocationSrv](#locationservice-replaces-getlocationsrv)
- [From version 7.x.x to 8.x.x](#from-version-7xx-to-8xx)
- [Backend plugin v1 support has been dropped](#backend-plugin-v1-support-has-been-dropped)
- [1. Add dependency on grafana-plugin-sdk-go](#1-add-dependency-on-grafana-plugin-sdk-go)
@@ -48,6 +50,81 @@ Removed due to being an internal component.
Removed due to being an internal component.
### 8.4 deprecations
#### LocationService replaces getLocationSrv
In a previous release, we migrated to use a new routing system and introduced a new service for managing locations, navigation, and related information. In this release, we are making that new service the primary service.
**Example:** Import the service.
```ts
// before
import { getLocationSrv } from '@grafana/runtime';
// after
import { locationService } from '@grafana/runtime';
```
**Example:** Navigate to a path and add a new record in the navigation history so that you can navigate back to the previous one.
```ts
// before
getLocationSrv.update({
path: '/route-to-navigate-to',
replace: false,
});
// after
locationService.push('/route-to-navigate-to');
```
**Example:** Navigate to a path and replace the current record in the navigation history.
```ts
// before
getLocationSrv.update({
path: '/route-to-navigate-to',
replace: true,
});
// after
locationService.replace('/route-to-navigate-to');
```
**Example:** Update the search or query parameter for the current route and add a new record in the navigation history so that you can navigate back to the previous one.
```ts
// How to navigate to a new path
// before
getLocationSrv.update({
query: {
value: 1,
},
partial: true,
replace: false,
});
// after
locationService.partial({ value: 1 });
```
**Example:** Update the search or query parameter for the current route and add replacing it in the navigation history.
```ts
// before
getLocationSrv.update({
query: {
'var-variable': 1,
},
partial: true,
replace: true,
});
// after
locationService.partial({ 'var-variable': 1 }, true);
```
## From version 7.x.x to 8.x.x
This section explains how to migrate Grafana v7.x.x plugins to the updated plugin system available in Grafana v8.x.x. Depending on your plugin, you need to perform one or more of the following steps. We have documented the breaking changes in Grafana v8.x.x and the steps you need to take to upgrade your plugin.
@@ -38,13 +38,14 @@ The reference information that follows complements conceptual information about
| `fixed:organization:reader` | `orgs:read`<br>`orgs.quotas:read` | Read an organization and its quotas. |
| `fixed:organization:writer` | All permissions from `fixed:organization:reader` and <br> `orgs:write`<br>`orgs.preferences:read`<br>`orgs.preferences:write` | Read an organization, its quotas, or its preferences. Update organization properties, or its preferences. |
| `fixed:organization:maintainer` | All permissions from `fixed:organization:reader` and <br> `orgs:write`<br>`orgs:create`<br>`orgs:delete`<br>`orgs.quotas:write` | Create, read, write, or delete an organization. Read or write its quotas. This role needs to be assigned globally. |
| |
| `fixed:teams:creator` ` | `teams:create`<br>`org.users:read` | Create a team and list organization users (required to manage the created team). |
| `fixed:teams:writer` | `teams:create`<br>`teams:delete`<br>`teams:read`<br>`teams:write`<br>`teams.permissions:read`<br>`teams.permissions:write` | Create, read, update and delete teams and manage team memberships. |
## Default built-in role assignments
| Built-in role | Associated role | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Grafana Admin | `fixed:roles:reader`<br>`fixed:roles:writer`<br>`fixed:users:reader`<br>`fixed:users:writer`<br>`fixed:org.users:reader`<br>`fixed:org.users:writer`<br>`fixed:ldap:reader`<br>`fixed:ldap:writer`<br>`fixed:stats:reader`<br>`fixed:settings:reader`<br>`fixed:settings:writer`<br>`fixed:provisioning:writer`<br>`fixed:organization:reader`<br>`fixed:organization:maintainer`<br>`fixed:licensing:reader`<br>`fixed:licensing:writer` | Default [Grafana server administrator]({{< relref "../../permissions/_index.md#grafana-server-admin-role" >}}) assignments. |
| Admin | `fixed:reports:reader`<br>`fixed:reports:writer`<br>`fixed:datasources:reader`<br>`fixed:datasources:writer`<br>`fixed:organization:writer`<br>`fixed:datasources.permissions:reader`<br>`fixed:datasources.permissions:writer`<br> | Default [Grafana organization administrator]({{< relref "../../permissions/organization_roles.md" >}}) assignments. |
| Editor | `fixed:datasources:explorer` | Default [Editor]({{< relref "../../permissions/organization_roles.md" >}}) assignments. |
| Admin | `fixed:reports:reader`<br>`fixed:reports:writer`<br>`fixed:datasources:reader`<br>`fixed:datasources:writer`<br>`fixed:organization:writer`<br>`fixed:datasources.permissions:reader`<br>`fixed:datasources.permissions:writer`<br>`fixed:teams:writer`<br> | Default [Grafana organization administrator]({{< relref "../../permissions/organization_roles.md" >}}) assignments. |
| Editor | `fixed:datasources:explorer` and <br> `fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled | Default [Editor]({{< relref "../../permissions/organization_roles.md" >}}) assignments. |
| Viewer | `fixed:datasources:id:reader`<br>`fixed:organization:reader` | Default [Viewer]({{< relref "../../permissions/organization_roles.md" >}}) assignments. |
@@ -91,6 +91,12 @@ The following list contains fine-grained access control actions.
| `licensing:update` | n/a | Update the license token. |
| `licensing:delete` | n/a | Delete the license token. |
| `licensing.reports:read` | n/a | Get custom permission reports. |
| `teams:create` | n/a | Create teams. |
| `teams:read` | `teams:*`<br>`teams:id:*` | Read one or more teams and team preferences. |
| `teams:write` | `teams:*`<br>`teams:id:*` | Update one or more teams and team preferences. |
| `teams:delete` | `teams:*`<br>`teams:id:*` | Delete one or more teams. |
| `teams.permissions:read` | `teams:*`<br>`teams:id:*` | Read members and External Group Synchronization setup for teams. |
| `teams.permissions:write` | `teams:*`<br>`teams:id:*` | Add, remove and update members and manage External Group Synchronization setup for teams. |
## Scope definitions
@@ -501,9 +501,3 @@ The org id of the datasource where the query data will be written.
If all `default_remote_write_*` properties are set, this information will be populated at startup. If a remote write target has
already been configured, nothing will happen.
## [feature_highlights]
### enabled
Whether the feature highlights feature is enabled
+1 -1
View File
@@ -23,7 +23,7 @@ During an infrastructure monitoring and incident response, you can dig deeper in
Results of log queries are shown as histograms in the graph and individual logs are explained in the following sections.
If the data source supports a full range log volume histogram, the graph with log distribution for all entered log queries is shown automatically. This feature is currently supported by Elasticsearch data source.
If the data source supports a full range log volume histogram, the graph with log distribution for all entered log queries is shown automatically. This feature is currently supported by Elasticsearch and Loki data sources.
If the data source does not support loading full range log volume histogram, the logs model computes a time series based on the log row counts bucketed by an automatically calculated time interval, and the first log row's timestamp then anchors the start of the histogram from the result. The end of the time series is anchored to the time picker's **To** range.
+4 -3
View File
@@ -433,9 +433,10 @@ Accept: application/json
#### Query parameters
| Param | Type | Required | Description |
| ----- | ------- | -------- | ----------------------------------------------------------------------- |
| force | boolean | No | When set to `true`, the role will be deleted with all it's assignments. |
| Param | Type | Required | Description |
| ------ | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| force | boolean | No | When set to `true`, the role will be deleted with all it's assignments. |
| global | boolean | No | A flag indicating if the role is global or not. If set to false, the default org ID of the authenticated user will be used from the request. Refer to the [Role scopes]({{< relref "../enterprise/access-control/roles.md#built-in-role-assignments" >}}) for more information. |
#### Example response
@@ -9,10 +9,21 @@ aliases = ["/docs/grafana/latest/http_api/external_group_sync/"]
> External Group Synchronization is only available in Grafana Enterprise. Read more about [Grafana Enterprise]({{< relref "../enterprise" >}}).
> If you have [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) enabled, access to endpoints will be controlled by Fine-grained access control permissions.
> Refer to specific endpoints to understand what permissions are required.
## Get External Groups
`GET /api/teams/:teamId/groups`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ---------------------- | -------- |
| teams.permissions:read | teams:\* |
**Example Request**:
```http
@@ -47,6 +58,14 @@ Status Codes:
`POST /api/teams/:teamId/groups`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ----------------------- | -------- |
| teams.permissions:write | teams:\* |
**Example Request**:
```http
@@ -81,6 +100,14 @@ Status Codes:
`DELETE /api/teams/:teamId/groups/:groupId`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ----------------------- | -------- |
| teams.permissions:write | teams:\* |
**Example Request**:
```http
+2 -2
View File
@@ -269,8 +269,8 @@ Creates a new library element.
JSON Body schema:
- **folderId** – ID of the folder where the library element is stored.
- **name** – Name of the library element.
- **folderId** – Optional, the ID of the folder where the library element is stored.
- **name** – Optional, the name of the library element.
- **model** – The JSON model for the library element.
- **kind** – Kind of element to create, Use `1` for library panels or `2` for library variables.
- **uid** – Optional, the [unique identifier](/http_api/library_element/#identifier-id-vs-unique-identifier-uid).
+107 -15
View File
@@ -7,7 +7,17 @@ aliases = ["/docs/grafana/latest/http_api/team/"]
# Team API
This API can be used to create/update/delete Teams and to add/remove users to Teams. All actions require that the user has the Admin role for the organization.
This API can be used to manage Teams and Team Memberships.
Access to these API endpoints is restricted as follows:
- All authenticated users are able to view details of teams they are a member of.
- Organization Admins are able to manage all teams and team members.
- If you enable `editors_can_admin` configuration flag, then Organization Editors can create teams and manage teams where they are Admin.
- If you enable `editors_can_admin` configuration flag, Editors can find out whether a team that they are not members of exists by trying to create a team with the same name.
> If you are running Grafana Enterprise and have [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) enabled, access to endpoints will be controlled by Fine-grained access control permissions.
> Refer to specific endpoints to understand what permissions are required.
## Team Search With Paging
@@ -17,25 +27,23 @@ or
`GET /api/teams/search?name=myteam`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ---------- | -------- |
| teams:read | teams:\* |
**Example Request**:
```http
GET /api/teams/search?perpage=10&page=1&query=myteam HTTP/1.1
GET /api/teams/search?perpage=10&page=1&query=mytestteam HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
```
### Using the query parameter
Default value for the `perpage` parameter is `1000` and for the `page` parameter is `1`.
The `totalCount` field in the response can be used for pagination of the teams list E.g. if `totalCount` is equal to 100 teams and the `perpage` parameter is set to 10 then there are 10 pages of teams.
The `query` parameter is optional and it will return results where the query value is contained in the `name` field. Query values with spaces need to be URL encoded e.g. `query=my%20team`.
### Using the name parameter
The `name` parameter returns a single team if the parameter matches the `name` field.
**Example Response**:
```http
@@ -59,7 +67,19 @@ Content-Type: application/json
}
```
Status Codes:
### Using the query parameter
Default value for the `perpage` parameter is `1000` and for the `page` parameter is `1`.
The `totalCount` field in the response can be used for pagination of the teams list E.g. if `totalCount` is equal to 100 teams and the `perpage` parameter is set to 10 then there are 10 pages of teams.
The `query` parameter is optional and it will return results where the query value is contained in the `name` field. Query values with spaces need to be URL encoded e.g. `query=my%20team`.
### Using the name parameter
The `name` parameter returns a single team if the parameter matches the `name` field.
#### Status Codes:
- **200** - Ok
- **401** - Unauthorized
@@ -70,6 +90,14 @@ Status Codes:
`GET /api/teams/:id`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ---------- | -------- |
| teams:read | teams:\* |
**Example Request**:
```http
@@ -108,6 +136,14 @@ The Team `name` needs to be unique. `name` is required and `email`,`orgId` is op
`POST /api/teams`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ------------ | ----- |
| teams:create | N/A |
**Example Request**:
```http
@@ -145,6 +181,14 @@ There are two fields that can be updated for a team: `name` and `email`.
`PUT /api/teams/:id`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ----------- | -------- |
| teams:write | teams:\* |
**Example Request**:
```http
@@ -180,6 +224,14 @@ Status Codes:
`DELETE /api/teams/:id`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ------------ | -------- |
| teams:delete | teams:\* |
**Example Request**:
```http
@@ -209,6 +261,14 @@ Status Codes:
`GET /api/teams/:teamId/members`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ---------------------- | -------- |
| teams.permissions:read | teams:\* |
**Example Request**:
```http
@@ -254,6 +314,14 @@ Status Codes:
`POST /api/teams/:teamId/members`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ----------------------- | -------- |
| teams.permissions:write | teams:\* |
**Example Request**:
```http
@@ -288,6 +356,14 @@ Status Codes:
`DELETE /api/teams/:teamId/members/:userId`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ----------------------- | -------- |
| teams.permissions:write | teams:\* |
**Example Request**:
```http
@@ -317,6 +393,14 @@ Status Codes:
`GET /api/teams/:teamId/preferences`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ---------- | -------- |
| teams:read | teams:\* |
**Example Request**:
```http
@@ -343,6 +427,14 @@ Content-Type: application/json
`PUT /api/teams/:teamId/preferences`
#### Required permissions
See note in the [introduction]({{< ref "#team-api" >}}) for an explanation.
| Action | Scope |
| ----------- | -------- |
| teams:write | teams:\* |
**Example Request**:
```http
+3
View File
@@ -8,6 +8,8 @@ weight = 10000
Here you can find detailed release notes that list everything that is included in every release as well as notices
about deprecations, breaking changes as well as changes that relate to plugin development.
- [Release notes for 8.4.0-beta1]({{< relref "release-notes-8-4-0-beta1" >}})
- [Release notes for 8.3.5]({{< relref "release-notes-8-3-5" >}})
- [Release notes for 8.3.4]({{< relref "release-notes-8-3-4" >}})
- [Release notes for 8.3.3]({{< relref "release-notes-8-3-3" >}})
- [Release notes for 8.3.2]({{< relref "release-notes-8-3-2" >}})
@@ -48,6 +50,7 @@ about deprecations, breaking changes as well as changes that relate to plugin de
- [Release notes for 8.0.0-beta3]({{< relref "release-notes-8-0-0-beta3" >}})
- [Release notes for 8.0.0-beta2]({{< relref "release-notes-8-0-0-beta2" >}})
- [Release notes for 8.0.0-beta1]({{< relref "release-notes-8-0-0-beta1" >}})
- [Release notes for 7.5.15]({{< relref "release-notes-7-5-15" >}})
- [Release notes for 7.5.13]({{< relref "release-notes-7-5-13" >}})
- [Release notes for 7.5.12]({{< relref "release-notes-7-5-12" >}})
- [Release notes for 7.5.11]({{< relref "release-notes-7-5-11" >}})
@@ -0,0 +1,11 @@
+++
title = "Release notes for Grafana 7.5.15"
[_build]
list = false
+++
# Release notes for Grafana 7.5.15
- **Security**: Fixes CVE-2022-21702. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
- **Security**: Fixes CVE-2022-21703. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
- **Security**: Fixes CVE-2022-21713. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
@@ -0,0 +1,11 @@
+++
title = "Release notes for Grafana 8.3.5"
[_build]
list = false
+++
# Release notes for Grafana 8.3.5
- **Security**: Fixes CVE-2022-21702. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
- **Security**: Fixes CVE-2022-21703. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
- **Security**: Fixes CVE-2022-21713. For more information, see our [blog](https://grafana.com/blog/2022/02/08/grafana-7.5.15-and-8.3.5-released-with-moderate-severity-security-fixes/)
@@ -0,0 +1,23 @@
+++
title = "Release notes for Grafana 8.4.0-beta1"
hide_menu = true
+++
<!-- Auto generated by update changelog github action -->
# Release notes for Grafana 8.4.0-beta1
### Features and enhancements
- **Alerting:** Support WeCom as a contact point type. [#40975](https://github.com/grafana/grafana/pull/40975), [@smallpath](https://github.com/smallpath)
- **Alerting:** UI for mute timings. [#41578](https://github.com/grafana/grafana/pull/41578), [@nathanrodman](https://github.com/nathanrodman)
- **Alerting:** add settings for peer reconnection in HA mode. [#42300](https://github.com/grafana/grafana/pull/42300), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ)
- **Auth:** implement auto_sign_up for auth.jwt. [#37040](https://github.com/grafana/grafana/pull/37040), [@Roguelazer](https://github.com/Roguelazer)
- **Dashboard:** Add Show unknown variables toggle to dashboard settings. [#41854](https://github.com/grafana/grafana/pull/41854), [@hugohaggmark](https://github.com/hugohaggmark)
- **Instrumentation:** Logger migration from log15 to gokit/log. [#41636](https://github.com/grafana/grafana/pull/41636), [@ying-jeanne](https://github.com/ying-jeanne)
- **MSSQL:** Change regex to validate Provider connection string. [#40248](https://github.com/grafana/grafana/pull/40248), [@ianselmi](https://github.com/ianselmi)
- **MSSQL:** Configuration of certificate verification for TLS connection. [#31865](https://github.com/grafana/grafana/pull/31865), [@mortenaa](https://github.com/mortenaa)
- **Middleware:** Don't require HTTPS for HSTS headers to be emitted. [#35147](https://github.com/grafana/grafana/pull/35147), [@alexmv](https://github.com/alexmv)
- **Navigation:** Implement Keyboard Navigation. [#41618](https://github.com/grafana/grafana/pull/41618), [@axelavargas](https://github.com/axelavargas)
- **News:** Reload feed when changing the time range or refreshing. [#42217](https://github.com/grafana/grafana/pull/42217), [@ashharrison90](https://github.com/ashharrison90)
- **UI/Plot:** Implement keyboard controls for plot cursor. [#42244](https://github.com/grafana/grafana/pull/42244), [@kaydelaney](https://github.com/kaydelaney)
+1 -1
View File
@@ -70,7 +70,7 @@ The markers layer allows you to display data points as different marker shapes s
![Markers Layer Options](/static/img/docs/geomap-panel/geomap-markers-options-8-1-0.png)
- **Marker Color** configures the color of the marker. The default `Fixed size` keeps all points a single color. There is an alternate option to have multiple colors depending on the data point values and the threshold set at the `Thresholds` section.
- **Marker Color** configures the color of the marker. The default `Single color` keeps all points a single color. There is an alternate option to have multiple colors depending on the data point values and the threshold set at the `Thresholds` section.
- **Marker Size** configures the size of the marker. Default is `Fixed size`, making all marker size the same regardless of the data points. However, there is also an option to scale the circles to the corresponding data points. `Min` and `Max` marker size has to be set such that the Marker layer can scale within this range.
- **Marker Shape** allows you to choose the shape, icon, or graphic to aid in providing additional visual context to your data. Choose from assets that are included with Grafana such as simple shapes or the Unicon library. You can also specify a URL containing an image asset. The image must be a scalable vector graphic (SVG).
- **Fill opacity** configures the transparency of each marker.
@@ -35,6 +35,15 @@ Use these options to choose how to display your time series data.
- [Graph stacked time series]({{< relref "./graph-time-series-stacking.md" >}})
- [Graph and color schemes]({{< relref "./graph-color-scheme.md" >}})
### Transform
Use this option to transform the series values without affecting the values shown in the tooltip, context menu, and legend.
- **Negative Y transform -** Flip the results to negative values on the Y axis.
- **Constant -** Show first value as a constant line.
> **Note:** Transform option is only available as an override.
## Axis
For more information about adjusting your time series axes, refer to [Change axis display]({{< relref "change-axis-display.md" >}}).
+1
View File
@@ -11,6 +11,7 @@ as info on deprecations, breaking changes and plugin development read the [relea
## Grafana 8
- [What's new in 8.4]({{< relref "whats-new-in-v8-4" >}})
- [What's new in 8.3]({{< relref "whats-new-in-v8-3" >}})
- [What's new in 8.2]({{< relref "whats-new-in-v8-2" >}})
- [What's new in 8.1]({{< relref "whats-new-in-v8-1" >}})
+144
View File
@@ -0,0 +1,144 @@
+++
title = "What's new in Grafana v8.4"
description = "Feature and improvement highlights for Grafana v8.4"
keywords = ["grafana", "new", "documentation", "8.4", "release notes"]
weight = -33
aliases = ["/docs/grafana/latest/guides/whats-new-in-v8-4/"]
[_build]
list = false
+++
# What’s new in Grafana v8.4
We’re excited to announce Grafana v8.4, with a variety of improvements that focus on Grafana’s usability, performance, and security. Read on to learn about Alerting enhancements like a WeCom contact point, improved Alert panel and custom mute timings, as well as visualization improvements and details to help you share playlists more easily. In Grafana Enterprise, we’ve made caching more powerful to save you time and money while loading dashboards, boosted database encryption to keep secrets safe in your Grafana database, and made usability improvements to Recorded Queries, which allow you to track any data point over time.
We’ve summarized what’s new in the release here, but you might also be interested in the announcement blog post. If you’d like all the details you can check out the complete [changelog](https://github.com/grafana/grafana/blob/master/CHANGELOG.md).
# OSS
## Ease of use
### Share playlists
You can now share links to your playlists the same way as with dashboards, to easily set up the same playlist on multiple devices or kiosks.
{{< figure src="/static/img/docs/dashboards/share-playlists-8-4.png" max-width="1200px" caption="Share playlist" >}}
### $**interval and $**interval_ms in panel title
We’ve expanded the functionality of this existing and popular feature. You can now use $**interval and $**interval_ms in panel titles. This new function displays the interval that’s used in a specific panel without requiring edit mode.
{{< figure src="/static/img/docs/panels/interval-8-4.png" max-width="1200px" caption="Time intervals" >}}
### Accessibility improvements
We’re continuing to improve accessibility across Grafana, particularly focusing on keyboard navigation and screen readers.
- Navigation: We’ve improved our keyboard navigation support in our main navbar, added focus states, and removed keyboard traps.
- General components (tooltips, color pickers, modals, dropdowns, and so forth): we’ve ensured they’re keyboard navigable, improved focus trapping, and improved screen reader support.
- Time series panel: charts are one of our main areas of limited accessibility. As of 8.4, you can also move a panel and make range selections using your keyboard. - Press arrow keys to move the cursor around. - Hold Shift to increase cursor speed. - Hold space to start rage selection.
You can read our accessibility statement [here](https://grafana.com/accessibility/) and reach out to us with accessibility issues using our community Slack or our community forums.
### New option to configure OpenTelemetry
Grafana is used to visualize traces and metrics, but Grafana itself can be traced as well. For example, users running a Grafana instance can export all the traces of endpoints and database requests to Jaeger, which helps you view all Grafana traffic.
We’re currently using OpenTracing for this, but since the repository is being deprecated, we’ve started our work to migrate to OpenTelemetry and remove OpenTracing. This release is the first step toward this goal. It also adds the option to configure OpenTelemetry instead of OpenTracing.
### Rotate your encryption key
In Grafana 8.3, we upgraded Grafana to use envelope encryption, which adds a layer of indirection to the encryption process. Instead of encrypting all secrets in the database with a single key, Grafana uses a set of keys, called data encryption keys (DEKs), to encrypt them. These data encryption keys are themselves encrypted with a single key-encryption key (KEK).
As of 8.4, you can rotate your KEK and quickly re-encrypt your DEKs in case a key is compromised.
Envelope encryption is not enabled by default in version 8.4. You can enable it by adding the term `envelopeEncryption` to the list of feature toggles in your [Grafana configuration](https://grafana.com/docs/grafana/next/administration/configuration/#feature_toggles?mdm=email), or by sending a request to support if you use Grafana Cloud.
{{< figure src="/static/img/docs/encryption/rotate-encryption-8-4.png" max-width="1200px" caption="Rotate encryption" >}}
## Alerting
### Support for mute timings
[Mute timings](https://grafana.com/docs/grafana/next/alerting/unified-alerting/notifications/mute-timings/) have been a popular request from the community. They are a powerful addition to the new alerting feature set and allow you to suppress specific alerts on a recurring interval or schedule, [contrary to](https://grafana.com/docs/grafana/next/alerting/unified-alerting/notifications/mute-timings/#mute-timings-vs-silences) Silences.
Paired with the existing Silences, this gives you even more control over when alerts should be sent and contact points notified.
### Custom grouping for the Alert Panel
The new Alert Panel displays your alerts and associated alert instances, and supports grouping by one or more custom labels. You can also display all alert instances in an ungrouped list by choosing the custom grouping mode without any configured labels.
Traditionally alerts in the Alert Panel were grouped by the alert rule that created them. When you are monitoring a complex resource like an industrial pump, you typically have multiple alerts defined for that resource to observe different metrics. The new custom grouping feature allows you to view all alert instances for individual resource by specifying a label such as “pump identifier”.
{{< figure src="/static/img/docs/alerting/unified/custom-grouping-8-4.png" max-width="1200px" caption="Custom grouping in alerting" >}}
### WeCom contact point
Starting with Grafana 8.4, you can [configure a WeCom](https://grafana.com/docs/grafana/next/alerting/unified-alerting/contact-points/#wecom) contact point, to send alert notifications to WeCom.
## New panel options
### Bar chart
We’ve expanded the bar chart so that you can:
- Use time for the x-axis.
- Color bars using a field property (that is, build success).
- Use labels effectively:
- Skip values when there are too many labels.
- Rotate labels.
{{< figure src="/static/img/docs/bar-chart-panel/bar-chart-8-4.png" max-width="1200px" caption="Updated bar chart" >}}
### Geomap
Geomap now supports tooltips with data-links across multiple layers.
{{< figure src="/static/img/docs/geomap-panel/geomap-tooltips-multiple-layers-8-4.png" max-width="1200px" caption="Assign SAML users role" >}}
## OpenAPI v2 specification
The HTTP API details are now [specified](https://editor.swagger.io/?url=https://raw.githubusercontent.com/grafana/grafana/papagian/api-spec/public/api-merged.json) using OpenAPI v2.
The grafana server serves a [SwaggerUI](https://swagger.io/tools/swagger-ui/) editor via the /swagger-ui endpoint that enables users to make requests to the HTTP API via the browser. This is disabled by default and it’s enabled when the `swaggerUi` feature toggle is set.
# Grafana Enterprise
## Security improvements
### Fine-grained access control works for teams
Occasionally, Viewer, Editor, and Admin roles don’t fit what a certain user needs to do in Grafana. Now you can assign fine-grained roles directly to users so they can create reports, use Explore mode, create data sources, and perform other specific actions in Grafana. Fine-grained access control is currently in beta.
In Grafana 8.4, you can assign roles to teams, which apply to all members of that team. This is a convenient way to grant certain permissions to a group of users all at once. It also makes permissions easier to manage when you synchronize groups from an SSO provider, like Google Oauth or Okta, to teams in Grafana.
In 8.4 you can also control access to Team and API key functionality itself, like viewing or editing API keys and adding members to certain teams.
Enable fine-grained access control by adding the term `accesscontrol` to the list of feature toggles in your [Grafana configuration](https://grafana.com/docs/grafana/next/administration/configuration/#feature_toggles?mdm=email), or by sending a request to support if you use Grafana Cloud. Learn more about fine-grained access control in the [fine-grained access control section of the docs](https://grafana.com/docs/grafana/next/enterprise/access-control/).
{{< figure src="/static/img/docs/enterprise/8-4-fine-grain-access-control.png" max-width="1200px" caption="Assign SAML users role" >}}
### Assign SAML users different roles in different Organizations
You can use Grafana’s SAML integration to map organizations in your SAML service to [Organizations](​​https://grafana.com/docs/grafana/latest/manage-users/server-admin/server-admin-manage-orgs/) in Grafana so that users who authenticate using SAML have the right permissions. Previously, you could only choose a single role (Viewer, Editor, or Admin) for users, which would apply to all of their Organizations. Now, you can map a given SAML user or org to different roles in different Organizations, so that, for example, they can be a Viewer in one Organization and an Admin in another.
Additionally, you can now grant multiple SAML organizations access to Grafana, using the `allowed_organizations` attribute. Previously, you could only map one.
{{< figure src="/static/img/docs/enterprise/8-4-SAML-auth.png" max-width="1200px" caption="Assign SAML users role" >}}
Learn more in our [SAML docs](https://grafana.com/docs/grafana/next/enterprise/saml/).
## Performance improvements
### Recorded queries is more stable and usable
We’ve made stability and usability improvements to Recorded Queries, and removed the feature flag so you can get started with Recorded Queries out of the box.
### Measure query cache hit rate and clear the cache
[Query caching](https://grafana.com/blog/2021/09/02/reduce-costs-and-increase-performance-with-query-caching-in-grafana-cloud/) significantly reduces load times and costs of Grafana dashboards, by temporarily storing query results in a cache. Now you can measure the hit rate of your query cache, to see how many queries (and therefore how much time and money) it is saving. You can also use these measurements to tune the cache time to live (TTL), to balance performance and up-to-date data.
You can also now manually clear the cache for a given data source in case data becomes stale, so that the next set of queries run against the data source itself.
To learn more, refer to [query caching in the Grafana Enterprise docs](https://grafana.com/docs/grafana/next/enterprise/query-caching/)
{{< figure src="/static/img/docs/enterprise/8-4-query-caching.png" max-width="1200px" caption="Grafana Enterprise query caching" >}}
+9 -4
View File
@@ -7,7 +7,7 @@ e2e.scenario({
itName: 'Tests various Panel edit scenarios',
addScenarioDataSource: false,
addScenarioDashBoard: false,
skipScenario: true,
skipScenario: false,
scenario: () => {
e2e().intercept('/api/ds/query').as('query');
e2e.flows.openDashboard({ uid: 'TkZXxlNG3' });
@@ -46,11 +46,16 @@ e2e.scenario({
// Can change to Alerts tab (graph panel is the default vis so the alerts tab should be rendered)
e2e.components.Tab.title('Alert').should('be.visible').click();
e2e.components.Tab.active().should('have.text', 'Alert0'); // there's no alert so therefore Alert + 0
e2e.components.AlertTab.content().should('not.exist');
// Needs to be disabled until Grafana EE turns unified alerting on by default
// e2e.components.AlertTab.content().should('not.exist');
e2e.components.QueryTab.content().should('not.exist');
e2e.components.TransformTab.content().should('not.exist');
e2e.components.PanelAlertTabContent.content().should('exist');
e2e.components.PanelAlertTabContent.content().should('be.visible');
// Needs to be disabled until Grafana EE turns unified alerting on by default
// e2e.components.PanelAlertTabContent.content().should('exist');
// e2e.components.PanelAlertTabContent.content().should('be.visible');
e2e.components.Tab.title('Query').should('be.visible').click();
});
+60 -56
View File
@@ -1,11 +1,14 @@
<!-- The template body needs to be wrapped in div to prevent Premailer from adding </tr></td> tags which breaks the HTML structure -->
<div>
[[Subject .Subject "[[.Title]]"]]
[[ define "alert" ]]
<tr>
<td colspan="2" class="value">
<span class="value-heading">Value:</span> <span class="value-value">[[ .ValueString ]]</span>
</td>
</tr>
<tr>
<td colspan="2" class="value">
<span class="value-heading">Value:</span> <span class="value-value">[[ .ValueString ]]</span>
</td>
</tr>
[[ if gt (len .Annotations.SortedPairs) 0 ]]
<tr>
<td colspan="2" class="annotations">
@@ -187,62 +190,63 @@
<tr>
<td class="twelve">
<table>
[[ if gt (len .Alerts.Firing) 0 ]]
<tr>
<td colspan="2" class="section-heading">
Firing: [[ .Alerts.Firing | len ]] alert[[ if gt (len .Alerts.Firing) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
[[ range .GroupLabels.SortedPairs ]]
[[ .Name ]]=[[ .Value ]]
[[ end ]][[ end ]]
</td>
</tr>
[[ range .Alerts.Firing ]]
[[ if gt (len .Alerts.Firing) 0 ]]
<tr>
<td colspan="2" class="section-heading">
Firing: [[ .Alerts.Firing | len ]] alert[[ if gt (len .Alerts.Firing) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
[[ range .GroupLabels.SortedPairs ]]
[[ .Name ]]=[[ .Value ]]
[[ end ]][[ end ]]
</td>
</tr>
[[ range .Alerts.Firing ]]
<tr>
<td
class="status-tag status-firing"
width="68"
>
Firing
</td>
<td class="alert-label">
[[ .Labels.alertname ]]
</td>
</tr>
[[ template "alert" . ]]
[[ end ]]
[[ end ]]
[[ if gt (len .Alerts.Resolved) 0 ]]
<tr>
<td colspan="2" class="section-heading">
Resolved: [[ .Alerts.Resolved | len ]] alert[[ if gt (len .Alerts.Resolved) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
[[ range .GroupLabels.SortedPairs ]]
[[ .Name ]]=[[ .Value ]]
[[ end ]][[ end ]]
</td>
</tr>
[[ range .Alerts.Resolved ]]
<tr>
<td
class="status-tag status-resolved"
width="68"
>
Resolved
</td>
<td class="alert-label">
[[ .Labels.alertname ]]
</td>
</tr>
[[ template "alert" . ]]
[[ end ]]
[[ end ]]
<tr>
<td
class="status-tag status-firing"
width="68"
>
Firing
</td>
<td class="alert-label">
[[ .Labels.alertname ]]
<td colspan="2">
<a href="[[ .AlertPageUrl ]]" class="button">Go to alerts page</a>
</td>
</tr>
[[ template "alert" . ]]
[[ end ]]
[[ end ]]
[[ if gt (len .Alerts.Resolved) 0 ]]
<tr>
<td colspan="2" class="section-heading">
Resolved: [[ .Alerts.Resolved | len ]] alert[[ if gt (len .Alerts.Resolved) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
[[ range .GroupLabels.SortedPairs ]]
[[ .Name ]]=[[ .Value ]]
[[ end ]][[ end ]]
</td>
</tr>
[[ range .Alerts.Resolved ]]
<tr>
<td
class="status-tag status-resolved"
width="68"
>
Resolved
</td>
<td class="alert-label">
[[ .Labels.alertname ]]
</td>
</tr>
[[ template "alert" . ]]
[[ end ]]
[[ end ]]
<tr>
<td colspan="2">
<a href="[[ .AlertPageUrl ]]" class="button">Go to alerts page</a>
</td>
</tr>
</table>
</table>
</td>
</tr>
</table>
[[ end ]]
</div>
+11 -8
View File
@@ -53,7 +53,7 @@ require (
github.com/gorilla/websocket v1.4.2
github.com/gosimple/slug v1.9.0
github.com/grafana/cuetsy v0.0.0-20211119211437-8c25464cc9bf
github.com/grafana/grafana-aws-sdk v0.9.1
github.com/grafana/grafana-aws-sdk v0.10.0
github.com/grafana/grafana-plugin-sdk-go v0.125.0
github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
@@ -77,7 +77,7 @@ require (
github.com/ohler55/ojg v1.12.9
github.com/opentracing/opentracing-go v1.2.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/browser v0.0.0-20210904010418-6d279e18f982 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/alertmanager v0.23.1-0.20211116083607-e2a10119aaf7
github.com/prometheus/client_golang v1.12.1
@@ -105,7 +105,7 @@ require (
go.opentelemetry.io/otel/trace v1.2.0
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
golang.org/x/exp v0.0.0-20210220032938-85be41e4509f // indirect
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
@@ -167,10 +167,10 @@ require (
github.com/go-openapi/analysis v0.20.1 // indirect
github.com/go-openapi/errors v0.20.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/loads v0.20.2 // indirect
github.com/go-openapi/runtime v0.19.29 // indirect
github.com/go-openapi/spec v0.20.3 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-openapi/validate v0.20.2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
@@ -239,9 +239,9 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/weaveworks/promrus v1.2.0 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
go.mongodb.org/mongo-driver v1.5.2 // indirect
go.mongodb.org/mongo-driver v1.7.0 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/atomic v1.9.0
go.uber.org/goleak v1.1.10 // indirect
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
@@ -252,7 +252,10 @@ require (
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
)
require cloud.google.com/go/kms v1.1.0
require (
cloud.google.com/go/kms v1.1.0
github.com/golang-migrate/migrate/v4 v4.7.0
)
require (
github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect
+13 -4
View File
@@ -867,6 +867,8 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
@@ -907,6 +909,8 @@ github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFut
github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ=
github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ=
github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
@@ -1040,6 +1044,7 @@ github.com/gogo/status v1.1.0 h1:+eIkrewn5q6b30y+g/BJINVVdi2xH7je5MPJ3ZPK3JA=
github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs=
github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@@ -1238,6 +1243,8 @@ github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5J
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/grafana/grafana-aws-sdk v0.9.1 h1:jMZlsLsWnqOwLt2UNcLUsJ2z6289hLYlscK35QgS158=
github.com/grafana/grafana-aws-sdk v0.9.1/go.mod h1:6KaQ8uUD4KpXr/b7bAC7zbfSXTVOiTk4XhIrwkGWn4w=
github.com/grafana/grafana-aws-sdk v0.10.0 h1:q7+mJtT/vsU5InDN57yM+BJ2z1kJDf1W4WwWPEZ0Cxw=
github.com/grafana/grafana-aws-sdk v0.10.0/go.mod h1:vFIOHEnY1u5nY0/tge1IHQjPuG6DRKr2ISf/HikUdjE=
github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 h1:2ud7NNM7LrGPO4x0NFR8qLq68CqI4SmB7I2yRN2w9oE=
github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE=
github.com/grafana/grafana-plugin-sdk-go v0.94.0/go.mod h1:3VXz4nCv6wH5SfgB3mlW39s+c+LetqSCjFj7xxPC5+M=
@@ -1953,8 +1960,8 @@ github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuR
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20210904010418-6d279e18f982 h1:TdFv+3Gr3GaghJ/o80aulO4ian7GHGWMdLBXoLZH1Is=
github.com/pkg/browser v0.0.0-20210904010418-6d279e18f982/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -2468,8 +2475,9 @@ go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4S
go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
go.mongodb.org/mongo-driver v1.5.2 h1:AsxOLoJTgP6YNM0fXWw4OjdluYmWzQYp+lFJL7xu9fU=
go.mongodb.org/mongo-driver v1.5.2/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
go.mongodb.org/mongo-driver v1.7.0 h1:hHrvOBWlWB2c7+8Gh/Xi5jj82AgidK/t7KVXBZ+IyUA=
go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
@@ -2725,8 +2733,9 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg=
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e h1:Xj+JO91noE97IN6F/7WZxzC5QE6yENAQPrwIYhW3bsA=
golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+2 -2
View File
@@ -1,4 +1,4 @@
{
"stable": "8.3.4",
"testing": "8.3.4"
"stable": "8.3.5",
"testing": "8.4.0-beta1"
}
+3 -2
View File
@@ -35,7 +35,7 @@
"packages:typecheck": "lerna run typecheck",
"packages:clean": "lerna run clean",
"precommit": "yarn run lint-staged",
"prettier:check": "prettier --list-different \"**/*.{scss,md,mdx}\"",
"prettier:check": "prettier --check --list-different=false --loglevel=warn \"**/*.{scss,md,mdx}\"",
"prettier:write": "prettier --list-different \"**/*.{scss,md,mdx}\" --write",
"start": "yarn themes:generate && yarn dev --watch",
"start:noTsCheck": "yarn start --env noTsCheck=1",
@@ -248,6 +248,7 @@
"@opentelemetry/semantic-conventions": "1.0.1",
"@popperjs/core": "2.11.2",
"@react-aria/button": "3.3.4",
"@react-aria/dialog": "3.1.4",
"@react-aria/focus": "3.5.0",
"@react-aria/interactions": "3.7.0",
"@react-aria/menu": "3.3.0",
@@ -361,7 +362,7 @@
"tether-drop": "https://github.com/torkelo/drop",
"tinycolor2": "1.4.2",
"tslib": "2.3.1",
"uplot": "1.6.18",
"uplot": "1.6.19",
"uuid": "8.3.2",
"vendor": "link:./public/vendor",
"visjs-network": "4.25.0",
+1 -1
View File
@@ -38,7 +38,7 @@
"regenerator-runtime": "0.13.9",
"rxjs": "7.5.1",
"tslib": "2.3.1",
"uplot": "1.6.18",
"uplot": "1.6.19",
"xss": "1.0.10"
},
"devDependencies": {
@@ -38,14 +38,16 @@ export function getFrameDisplayName(frame: DataFrame, index?: number) {
export function getFieldDisplayName(field: Field, frame?: DataFrame, allFrames?: DataFrame[]): string {
const existingTitle = field.state?.displayName;
const multipleFrames = Boolean(allFrames && allFrames.length > 1);
if (existingTitle) {
if (existingTitle && multipleFrames === field.state?.multipleFrames) {
return existingTitle;
}
const displayName = calculateFieldDisplayName(field, frame, allFrames);
field.state = field.state || {};
field.state.displayName = displayName;
field.state.multipleFrames = multipleFrames;
return displayName;
}
@@ -74,6 +74,8 @@ export const valueMappingsOverrideProcessor = (
export interface SelectFieldConfigSettings<T> {
allowCustomValue?: boolean;
isClearable?: boolean;
/** The default options */
options: Array<SelectableValue<T>>;
@@ -13,7 +13,7 @@ export function createShadows(colors: ThemeColors): ThemeShadows {
return {
z1: '0px 1px 2px rgba(24, 26, 27, 0.75)',
z2: '0px 4px 8px rgba(24, 26, 27, 0.75)',
z3: '0px 10px 20px rgb(20,20,20)',
z3: '0px 8px 24px rgb(1,4,9)',
};
}
@@ -47,6 +47,7 @@ describe('Order Transformer', () => {
labels: undefined,
state: {
displayName: 'temperature',
multipleFrames: false,
},
},
{
@@ -57,6 +58,7 @@ describe('Order Transformer', () => {
labels: undefined,
state: {
displayName: 'humidity',
multipleFrames: false,
},
},
{
@@ -67,6 +69,7 @@ describe('Order Transformer', () => {
labels: undefined,
state: {
displayName: 'time',
multipleFrames: false,
},
},
]);
@@ -108,6 +111,7 @@ describe('Order Transformer', () => {
labels: undefined,
state: {
displayName: 'humidity',
multipleFrames: false,
},
},
{
@@ -118,6 +122,7 @@ describe('Order Transformer', () => {
labels: undefined,
state: {
displayName: 'time',
multipleFrames: false,
},
},
{
@@ -128,6 +133,7 @@ describe('Order Transformer', () => {
labels: undefined,
state: {
displayName: 'pressure',
multipleFrames: false,
},
},
]);
@@ -52,6 +52,7 @@ describe('OrganizeFields Transformer', () => {
name: 'temperature',
state: {
displayName: 'temperature',
multipleFrames: false,
},
type: FieldType.number,
values: new ArrayVector([10.3, 10.4, 10.5, 10.6]),
@@ -64,6 +65,7 @@ describe('OrganizeFields Transformer', () => {
name: 'humidity',
state: {
displayName: 'renamed_humidity',
multipleFrames: false,
},
type: FieldType.number,
values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]),
@@ -113,6 +115,7 @@ describe('OrganizeFields Transformer', () => {
name: 'time',
state: {
displayName: 'renamed_time',
multipleFrames: false,
},
type: FieldType.time,
values: new ArrayVector([3000, 4000, 5000, 6000]),
@@ -123,6 +126,7 @@ describe('OrganizeFields Transformer', () => {
name: 'pressure',
state: {
displayName: 'pressure',
multipleFrames: false,
},
type: FieldType.number,
values: new ArrayVector([10.3, 10.4, 10.5, 10.6]),
@@ -164,7 +164,7 @@ describe('Reducer Transformer', () => {
{
name: 'Field',
type: FieldType.string,
values: new ArrayVector(['A temperature', 'A humidity']),
values: new ArrayVector(['temperature', 'humidity']),
config: {},
},
{
@@ -213,7 +213,7 @@ describe('Reducer Transformer', () => {
{
name: 'Field',
type: FieldType.string,
values: new ArrayVector(['A temperature']),
values: new ArrayVector(['temperature']),
config: {},
},
{
@@ -286,16 +286,16 @@ describe('Reducer Transformer', () => {
const seriesA = toDataFrame({
name: 'a',
fields: [
{ name: 'time', type: FieldType.time, values: [3000, 4000, 5000, 6000] },
{ name: 'value', type: FieldType.number, values: [3, 4, 5, 6], state: { displayName: 'a' } },
{ name: 'Time', type: FieldType.time, values: [3000, 4000, 5000, 6000] },
{ name: 'Value', type: FieldType.number, values: [3, 4, 5, 6] },
],
});
const seriesB = toDataFrame({
name: '2021',
fields: [
{ name: 'time', type: FieldType.time, values: [3000, 4000, 5000, 6000] },
{ name: 'value', type: FieldType.number, values: [7, 8, 9, 10], state: { displayName: '2021' } },
{ name: 'Time', type: FieldType.time, values: [3000, 4000, 5000, 6000] },
{ name: 'Value', type: FieldType.number, values: [7, 8, 9, 10] },
],
});
@@ -48,6 +48,7 @@ describe('Rename Transformer', () => {
name: 'time',
state: {
displayName: 'Total time',
multipleFrames: false,
},
type: FieldType.time,
values: new ArrayVector([3000, 4000, 5000, 6000]),
@@ -60,6 +61,7 @@ describe('Rename Transformer', () => {
name: 'temperature',
state: {
displayName: 'how cold is it?',
multipleFrames: false,
},
type: FieldType.number,
values: new ArrayVector([10.3, 10.4, 10.5, 10.6]),
@@ -72,6 +74,7 @@ describe('Rename Transformer', () => {
labels: undefined,
state: {
displayName: 'Moistness',
multipleFrames: false,
},
type: FieldType.number,
values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]),
@@ -115,6 +118,7 @@ describe('Rename Transformer', () => {
labels: undefined,
state: {
displayName: 'ttl',
multipleFrames: false,
},
type: FieldType.time,
values: new ArrayVector([3000, 4000, 5000, 6000]),
@@ -125,6 +129,7 @@ describe('Rename Transformer', () => {
name: 'pressure',
state: {
displayName: 'pressure',
multipleFrames: false,
},
type: FieldType.number,
values: new ArrayVector([10.3, 10.4, 10.5, 10.6]),
@@ -137,6 +142,7 @@ describe('Rename Transformer', () => {
name: 'humidity',
state: {
displayName: 'hum',
multipleFrames: false,
},
type: FieldType.number,
values: new ArrayVector([10000.3, 10000.4, 10000.5, 10000.6]),
@@ -46,6 +46,7 @@ describe('Rename By Regex Transformer', () => {
"name": "Time",
"state": Object {
"displayName": "Time",
"multipleFrames": false,
},
"type": "time",
"values": Array [
@@ -62,6 +63,7 @@ describe('Rename By Regex Transformer', () => {
"name": "Value",
"state": Object {
"displayName": "web-01",
"multipleFrames": false,
},
"type": "number",
"values": Array [
@@ -96,6 +98,7 @@ describe('Rename By Regex Transformer', () => {
"name": "Time",
"state": Object {
"displayName": "Time",
"multipleFrames": false,
},
"type": "time",
"values": Array [
@@ -112,6 +115,7 @@ describe('Rename By Regex Transformer', () => {
"name": "Value",
"state": Object {
"displayName": "web-01.example.com",
"multipleFrames": false,
},
"type": "number",
"values": Array [
@@ -147,6 +151,7 @@ describe('Rename By Regex Transformer', () => {
"name": "Time",
"state": Object {
"displayName": "Time",
"multipleFrames": false,
},
"type": "time",
"values": Array [
@@ -163,6 +168,7 @@ describe('Rename By Regex Transformer', () => {
"name": "Value",
"state": Object {
"displayName": "web-01.example.com",
"multipleFrames": false,
},
"type": "number",
"values": Array [
@@ -170,7 +170,9 @@ describe('SeriesToColumns Transformer', () => {
"name": "even",
},
"name": "time",
"state": Object {},
"state": Object {
"multipleFrames": true,
},
"type": "time",
"values": Array [
3000,
@@ -208,7 +210,9 @@ describe('SeriesToColumns Transformer', () => {
"name": "odd",
},
"name": "time",
"state": Object {},
"state": Object {
"multipleFrames": true,
},
"type": "time",
"values": Array [
undefined,
@@ -267,7 +271,9 @@ describe('SeriesToColumns Transformer', () => {
Object {
"config": Object {},
"name": "time",
"state": Object {},
"state": Object {
"multipleFrames": true,
},
"type": "time",
"values": Array [
1000,
@@ -67,6 +67,12 @@ export interface FieldConfig<TOptions = any> {
min?: number | null;
max?: number | null;
// Interval indicates the expected regular step between values in the series.
// When an interval exists, consumers can identify "missing" values when the expected value is not present.
// The grafana timeseries visualization will render disconnected values when missing values are found it the time field.
// The interval uses the same units as the values. For time.Time, this is defined in milliseconds.
interval?: number | null;
// Convert input values into a display string
mappings?: ValueMapping[];
@@ -169,6 +175,12 @@ export interface FieldState {
* @internal -- we will try to make this unnecessary
*/
origin?: DataFrameFieldIndex;
/**
* Boolean value is true if field is in a larger data set with multiple frames.
* This is only related to the cached displayName property above.
*/
multipleFrames?: boolean;
}
/** @public */
@@ -36,4 +36,7 @@ export interface FeatureToggles {
showFeatureFlagsInUI?: boolean;
disable_http_request_histogram?: boolean;
validatedQueries?: boolean;
swaggerUi?: boolean;
featureHighlights?: boolean;
migrationLocking?: boolean;
}
@@ -5,7 +5,7 @@ import { attachDebugger, createLogger } from '@grafana/ui';
import { config } from '../config';
/**
* @alpha
* @public
* A wrapper to help work with browser location and history
*/
export interface LocationService {
@@ -120,7 +120,7 @@ export class HistoryWrapper implements LocationService {
}
/**
* @alpha
* @public
* Parses a location search string to an object
* */
export function locationSearchToObject(search: string | number): UrlQueryMap {
@@ -137,12 +137,13 @@ export function locationSearchToObject(search: string | number): UrlQueryMap {
}
/**
* @alpha
* @public
*/
export let locationService: LocationService = new HistoryWrapper();
/** @internal
/**
* Used for tests only
* @internal
*/
export const setLocationService = (location: LocationService) => {
if (process.env.NODE_ENV !== 'test') {
@@ -1,13 +1,8 @@
/**
* Passed as options to the {@link LocationSrv} to describe how the automatically navigation
* should be performed.
*
* @public
*/
import { UrlQueryMap } from '@grafana/data';
/**
* @public
* @deprecated in favor of {@link locationService} and will be removed in Grafana 9
*/
export interface LocationUpdate {
/**
@@ -47,6 +42,7 @@ export interface LocationUpdate {
* be done via the LocationSrv and it will make sure to update the application state accordingly.
*
* @public
* @deprecated in favor of {@link locationService} and will be removed in Grafana 9
*/
export interface LocationSrv {
update(options: LocationUpdate): void;
@@ -69,6 +65,7 @@ export function setLocationSrv(instance: LocationSrv) {
* the user to a new place in Grafana.
*
* @public
* @deprecated in favor of {@link locationService} and will be removed in Grafana 9
*/
export function getLocationSrv(): LocationSrv {
return singletonInstance;
@@ -40,8 +40,8 @@ export class CopyPanelEvent extends BusEventWithPayload<PanelModel> {
let singletonInstance: EventBus;
/**
* Used during startup by Grafana to set the LocationSrv so it is available
* via the {@link getLocationSrv} to the rest of the application.
* Used during startup by Grafana to set the setAppEvents so it is available
* via the {@link setAppEvents} to the rest of the application.
*
* @internal
*/
@@ -7,6 +7,7 @@ LineInterpolation: "linear" | "smooth" | "stepBefore" | "stepAfter" @cue
ScaleDistribution: "linear" | "log" | "ordinal" @cuetsy(kind="enum")
GraphGradientMode: "none" | "opacity" | "hue" | "scheme" @cuetsy(kind="enum")
StackingMode: "none" | "normal" | "percent" @cuetsy(kind="enum")
GraphTransform: "constant" | "negative-Y" @cuetsy(kind="enum")
BarAlignment: -1 | 0 | 1 @cuetsy(kind="enum",memberNames="Before|Center|After")
ScaleOrientation: 0 | 1 @cuetsy(kind="enum",memberNames="Horizontal|Vertical")
ScaleDirection: 1 | 1 | -1 | -1 @cuetsy(kind="enum",memberNames="Up|Right|Down|Left")
@@ -84,4 +85,5 @@ GraphFieldConfig: {
drawStyle?: GraphDrawStyle
gradientMode?: GraphGradientMode
thresholdsStyle?: GraphThresholdsStyleConfig
transform?: GraphTransform
} @cuetsy(kind="interface")
@@ -24,6 +24,11 @@ export enum GraphDrawStyle {
Points = 'points',
}
export enum GraphTransform {
Constant = 'constant',
NegativeY = 'negative-Y',
}
export enum LineInterpolation {
Linear = 'linear',
Smooth = 'smooth',
@@ -256,6 +261,7 @@ export interface GraphFieldConfig
drawStyle?: GraphDrawStyle;
gradientMode?: GraphGradientMode;
thresholdsStyle?: GraphThresholdsStyleConfig;
transform?: GraphTransform;
}
export interface VizLegendOptions {
+1 -1
View File
@@ -86,7 +86,7 @@
"postcss-loader": "3.0.0",
"postcss-preset-env": "6.7.0",
"prettier": "2.5.1",
"react-dev-utils": "^11.0.4",
"react-dev-utils": "^12.0.0",
"replace-in-file": "^4.1.0",
"replace-in-file-webpack-plugin": "^1.0.6",
"rimraf": "^3.0.0",
-28
View File
@@ -166,34 +166,6 @@ module.exports = {
})
);
config.plugins = fixProcessResolution(config.plugins);
return config;
},
// This webpack config only affects the manager (storybook UI) and generally shouldn't need to be changed.
// If stories are failing to build check `webpackFinal` property above.
managerWebpack: async (config: any) => {
config.plugins = fixProcessResolution(config.plugins);
return config;
},
};
// Storybook 6.4.13 introduced https://github.com/storybookjs/storybook/pull/17213
// which appears to prevent resolving correctly with yarn pnp.
// We can probably remove this in the next patch release.
function fixProcessResolution(plugins: any[]) {
plugins.forEach((p: any, i: number) => {
if (p.constructor.name === 'ProvidePlugin') {
plugins.splice(
i,
1,
new ProvidePlugin({
process: require.resolve('process/browser.js'),
})
);
}
});
return plugins;
}
+15 -14
View File
@@ -40,6 +40,7 @@
"@monaco-editor/react": "4.3.1",
"@popperjs/core": "2.11.2",
"@react-aria/button": "3.3.4",
"@react-aria/dialog": "3.1.4",
"@react-aria/focus": "3.5.0",
"@react-aria/menu": "3.3.0",
"@react-aria/overlays": "3.7.3",
@@ -88,7 +89,7 @@
"slate-plain-serializer": "0.7.10",
"tinycolor2": "1.4.2",
"tslib": "2.3.1",
"uplot": "1.6.18",
"uplot": "1.6.19",
"uuid": "8.3.2"
},
"devDependencies": {
@@ -98,20 +99,20 @@
"@rollup/plugin-commonjs": "21.0.1",
"@rollup/plugin-image": "2.1.1",
"@rollup/plugin-node-resolve": "13.1.3",
"@storybook/addon-a11y": "6.4.13",
"@storybook/addon-actions": "6.4.13",
"@storybook/addon-docs": "6.4.13",
"@storybook/addon-essentials": "6.4.13",
"@storybook/addon-a11y": "6.4.15",
"@storybook/addon-actions": "6.4.15",
"@storybook/addon-docs": "6.4.15",
"@storybook/addon-essentials": "6.4.15",
"@storybook/addon-knobs": "6.4.0",
"@storybook/addon-storysource": "6.4.13",
"@storybook/addons": "6.4.13",
"@storybook/api": "6.4.13",
"@storybook/builder-webpack5": "6.4.13",
"@storybook/components": "6.4.13",
"@storybook/core-events": "6.4.13",
"@storybook/manager-webpack5": "6.4.13",
"@storybook/react": "6.4.13",
"@storybook/theming": "6.4.13",
"@storybook/addon-storysource": "6.4.15",
"@storybook/addons": "6.4.15",
"@storybook/api": "6.4.15",
"@storybook/builder-webpack5": "6.4.15",
"@storybook/components": "6.4.15",
"@storybook/core-events": "6.4.15",
"@storybook/manager-webpack5": "6.4.15",
"@storybook/react": "6.4.15",
"@storybook/theming": "6.4.15",
"@swc/helpers": "0.3.2",
"@testing-library/dom": "8.11.1",
"@testing-library/jest-dom": "5.16.1",
@@ -29,6 +29,8 @@ export interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ToolbarButtonVariant;
/** Hide any children and only show icon */
iconOnly?: boolean;
/** Show highlight dot */
isHighlighted?: boolean;
}
export type ToolbarButtonVariant = 'default' | 'primary' | 'destructive' | 'active';
@@ -48,6 +50,7 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
variant = 'default',
iconOnly,
'aria-label': ariaLabel,
isHighlighted,
...rest
},
ref
@@ -84,6 +87,7 @@ export const ToolbarButton = forwardRef<HTMLButtonElement, Props>(
{children && !iconOnly && <div className={contentStyles}>{children}</div>}
{isOpen === false && <Icon name="angle-down" />}
{isOpen === true && <Icon name="angle-up" />}
{isHighlighted && <div className={styles.highlight} />}
</button>
);
@@ -122,6 +126,7 @@ const getStyles = (theme: GrafanaTheme2) => {
return {
button: css`
label: toolbar-button;
position: relative;
display: flex;
align-items: center;
height: ${theme.spacing(theme.components.height.md)};
@@ -209,5 +214,15 @@ const getStyles = (theme: GrafanaTheme2) => {
contentWithRightIcon: css`
padding-right: ${theme.spacing(0.5)};
`,
highlight: css`
background-color: ${theme.colors.success.main};
border-radius: 50%;
width: 6px;
height: 6px;
position: absolute;
top: -3px;
right: -3px;
z-index: 1;
`,
};
};
@@ -104,9 +104,9 @@ class UnThemedColorPickerPopover<T extends CustomPickersDescriptor> extends Reac
<>
{Object.keys(customPickers).map((key) => {
return (
<div className={this.getTabClassName(key)} onClick={this.onTabChange(key)} key={key}>
<button className={this.getTabClassName(key)} onClick={this.onTabChange(key)} key={key}>
{customPickers[key].name}
</div>
</button>
);
})}
</>
@@ -118,7 +118,11 @@ class UnThemedColorPickerPopover<T extends CustomPickersDescriptor> extends Reac
const styles = getStyles(theme);
return (
<FocusScope contain restoreFocus autoFocus>
<div className={styles.colorPickerPopover}>
{/*
tabIndex=-1 is needed here to support highlighting text within the picker when using FocusScope
see https://github.com/adobe/react-spectrum/issues/1604#issuecomment-781574668
*/}
<div tabIndex={-1} className={styles.colorPickerPopover}>
<div className={styles.colorPickerPopoverTabs}>
<button className={this.getTabClassName('palette')} onClick={this.onTabChange('palette')}>
Colors
@@ -18,6 +18,7 @@ import {
import { Field } from '../../Forms/Field';
import { getInputStyles, Input } from '../../Input/Input';
import { Icon } from '../../Icon/Icon';
import { Tooltip } from '../../Tooltip/Tooltip';
/**
* @internal
@@ -115,14 +116,13 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps): Re
</CustomScrollbar>
<div className={styles.rightSide}>
<div className={styles.title}>
<TimePickerTitle>Specify time range</TimePickerTitle>
<div className={styles.description}>
Specify a relative time range, for more information see{' '}
<a href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/">
docs <Icon name="external-link-alt" />
</a>
.
</div>
<TimePickerTitle>
<Tooltip content={<TooltipContent />} placement="bottom" theme="info">
<div>
Specify time range <Icon name="info-circle" />
</div>
</Tooltip>
</TimePickerTitle>
</div>
<Field label="From" invalid={!from.validation.isValid} error={from.validation.errorMessage}>
<Input
@@ -152,6 +152,38 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps): Re
);
}
const TooltipContent = () => {
const styles = useStyles2(toolTipStyles);
return (
<>
<div className={styles.supported}>
Supported formats: <code className={styles.tooltip}>now-[digit]s/m/h/d/w</code>
</div>
<div>Example: to select a time range from 10 minutes ago to now</div>
<code className={styles.tooltip}>From: now-10m To: now</code>
<div className={styles.link}>
For more information see{' '}
<a href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/">
docs <Icon name="external-link-alt" />
</a>
.
</div>
</>
);
};
const toolTipStyles = (theme: GrafanaTheme2) => ({
supported: css`
margin-bottom: ${theme.spacing(1)};
`,
tooltip: css`
margin: 0;
`,
link: css`
margin-top: ${theme.spacing(1)};
`,
});
const getStyles = (fromError?: string, toError?: string) => (theme: GrafanaTheme2) => {
const inputStyles = getInputStyles({ theme, invalid: false });
const bodyMinimumHeight = 250;
@@ -25,6 +25,7 @@ import { Themeable } from '../../types';
import { quickOptions } from './options';
import { ButtonGroup, ToolbarButton } from '../Button';
import { selectors } from '@grafana/e2e-selectors';
import { useDialog } from '@react-aria/dialog';
import { useOverlay } from '@react-aria/overlays';
import { FocusScope } from '@react-aria/focus';
@@ -86,6 +87,7 @@ export function UnthemedTimeRangePicker(props: TimeRangePickerProps): ReactEleme
const ref = createRef<HTMLElement>();
const { overlayProps } = useOverlay({ onClose, isDismissable: true, isOpen }, ref);
const { dialogProps } = useDialog({}, ref);
const styles = getStyles(theme);
const hasAbsolute = isDateTime(value.raw.from) || isDateTime(value.raw.to);
@@ -118,7 +120,7 @@ export function UnthemedTimeRangePicker(props: TimeRangePickerProps): ReactEleme
</Tooltip>
{isOpen && (
<FocusScope contain autoFocus restoreFocus>
<section ref={ref} {...overlayProps}>
<section ref={ref} {...overlayProps} {...dialogProps}>
<TimePickerContent
timeZone={timeZone}
fiscalYearStartMonth={fiscalYearStartMonth}
@@ -78,6 +78,10 @@ export const getBodyStyles = (theme: GrafanaTheme2) => {
background-color: ${theme.colors.background.primary};
width: 268px;
.react-calendar__navigation {
display: flex;
}
.react-calendar__navigation__label,
.react-calendar__navigation__arrow,
.react-calendar__navigation {
@@ -4,6 +4,7 @@ import { DateTime, GrafanaTheme2, TimeZone } from '@grafana/data';
import { useTheme2 } from '../../../themes';
import { Header } from './CalendarHeader';
import { selectors } from '@grafana/e2e-selectors';
import { useDialog } from '@react-aria/dialog';
import { FocusScope } from '@react-aria/focus';
import { OverlayContainer, useOverlay } from '@react-aria/overlays';
import { Body } from './CalendarBody';
@@ -77,6 +78,12 @@ function TimePickerCalendar(props: TimePickerCalendarProps) {
const styles = getStyles(theme, props.isReversed);
const { isOpen, isFullscreen, onClose } = props;
const ref = React.createRef<HTMLElement>();
const { dialogProps } = useDialog(
{
'aria-label': selectors.components.TimePicker.calendar.label,
},
ref
);
const { overlayProps } = useOverlay(
{
isDismissable: true,
@@ -93,13 +100,7 @@ function TimePickerCalendar(props: TimePickerCalendarProps) {
if (isFullscreen) {
return (
<FocusScope contain restoreFocus autoFocus>
<section
className={styles.container}
onClick={stopPropagation}
aria-label={selectors.components.TimePicker.calendar.label}
ref={ref}
{...overlayProps}
>
<section className={styles.container} onClick={stopPropagation} ref={ref} {...overlayProps} {...dialogProps}>
<Header {...props} />
<Body {...props} />
</section>
@@ -110,7 +111,7 @@ function TimePickerCalendar(props: TimePickerCalendarProps) {
return (
<OverlayContainer>
<FocusScope contain autoFocus restoreFocus>
<section className={styles.modal} onClick={stopPropagation} ref={ref} {...overlayProps}>
<section className={styles.modal} onClick={stopPropagation} ref={ref} {...overlayProps} {...dialogProps}>
<div className={styles.content} aria-label={selectors.components.TimePicker.calendar.label}>
<Header {...props} />
<Body {...props} />
@@ -14,120 +14,6 @@ import { getFocusStyles } from '../../../themes/mixins';
import { selectors } from '@grafana/e2e-selectors';
import { FilterInput } from '../..';
const getStyles = stylesFactory((theme: GrafanaTheme2, isReversed, hideQuickRanges, isContainerTall) => {
return {
container: css`
background: ${theme.colors.background.primary};
box-shadow: ${theme.shadows.z3};
position: absolute;
z-index: ${theme.zIndex.dropdown};
width: 546px;
top: 116%;
border-radius: 2px;
border: 1px solid ${theme.colors.border.weak};
${isReversed ? 'left' : 'right'}: 0;
@media only screen and (max-width: ${theme.breakpoints.values.lg}px) {
width: 262px;
}
`,
body: css`
display: flex;
flex-direction: row-reverse;
height: ${isContainerTall ? '381px' : '217px'};
`,
leftSide: css`
display: flex;
flex-direction: column;
border-right: ${isReversed ? 'none' : `1px solid ${theme.colors.border.weak}`};
width: ${!hideQuickRanges ? '60%' : '100%'};
overflow: hidden;
order: ${isReversed ? 1 : 0};
`,
rightSide: css`
width: 40% !important;
border-right: ${isReversed ? `1px solid ${theme.colors.border.weak}` : 'none'};
display: flex;
flex-direction: column;
@media only screen and (max-width: ${theme.breakpoints.values.lg}px) {
width: 100% !important;
}
`,
timeRangeFilter: css`
padding: ${theme.spacing(1)};
`,
spacing: css`
margin-top: 16px;
`,
};
});
const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme2) => {
return {
header: css`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid ${theme.colors.border.weak};
padding: 7px 9px 7px 9px;
`,
expandButton: css`
background-color: transparent;
border: none;
display: flex;
width: 100%;
&:focus-visible {
${getFocusStyles(theme)}
}
`,
body: css`
border-bottom: 1px solid ${theme.colors.border.weak};
`,
form: css`
padding: 7px 9px 7px 9px;
`,
};
});
const getFullScreenStyles = stylesFactory((theme: GrafanaTheme2, hideQuickRanges?: boolean) => {
return {
container: css`
padding-top: 9px;
padding-left: 11px;
padding-right: ${!hideQuickRanges ? '20%' : '11px'};
`,
title: css`
margin-bottom: 11px;
`,
recent: css`
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-top: ${theme.spacing(1)};
`,
};
});
const getEmptyListStyles = stylesFactory((theme: GrafanaTheme2) => {
return {
container: css`
padding: 12px;
margin: 12px;
a,
span {
font-size: 13px;
}
`,
link: css`
color: ${theme.colors.text.link};
`,
};
});
interface Props {
value: TimeRange;
onChange: (timeRange: TimeRange) => void;
@@ -363,3 +249,117 @@ const useTimeOption = (raw: RawTimeRange, quickOptions: TimeOption[]): TimeOptio
});
}, [raw, quickOptions]);
};
const getStyles = stylesFactory((theme: GrafanaTheme2, isReversed, hideQuickRanges, isContainerTall) => {
return {
container: css`
background: ${theme.colors.background.primary};
box-shadow: ${theme.shadows.z3};
position: absolute;
z-index: ${theme.zIndex.dropdown};
width: 546px;
top: 116%;
border-radius: 2px;
border: 1px solid ${theme.colors.border.weak};
${isReversed ? 'left' : 'right'}: 0;
@media only screen and (max-width: ${theme.breakpoints.values.lg}px) {
width: 262px;
}
`,
body: css`
display: flex;
flex-direction: row-reverse;
height: ${isContainerTall ? '381px' : '217px'};
`,
leftSide: css`
display: flex;
flex-direction: column;
border-right: ${isReversed ? 'none' : `1px solid ${theme.colors.border.weak}`};
width: ${!hideQuickRanges ? '60%' : '100%'};
overflow: hidden;
order: ${isReversed ? 1 : 0};
`,
rightSide: css`
width: 40% !important;
border-right: ${isReversed ? `1px solid ${theme.colors.border.weak}` : 'none'};
display: flex;
flex-direction: column;
@media only screen and (max-width: ${theme.breakpoints.values.lg}px) {
width: 100% !important;
}
`,
timeRangeFilter: css`
padding: ${theme.spacing(1)};
`,
spacing: css`
margin-top: 16px;
`,
};
});
const getNarrowScreenStyles = stylesFactory((theme: GrafanaTheme2) => {
return {
header: css`
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid ${theme.colors.border.weak};
padding: 7px 9px 7px 9px;
`,
expandButton: css`
background-color: transparent;
border: none;
display: flex;
width: 100%;
&:focus-visible {
${getFocusStyles(theme)}
}
`,
body: css`
border-bottom: 1px solid ${theme.colors.border.weak};
`,
form: css`
padding: 7px 9px 7px 9px;
`,
};
});
const getFullScreenStyles = stylesFactory((theme: GrafanaTheme2, hideQuickRanges?: boolean) => {
return {
container: css`
padding-top: 9px;
padding-left: 11px;
padding-right: ${!hideQuickRanges ? '20%' : '11px'};
`,
title: css`
margin-bottom: 11px;
`,
recent: css`
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding-top: ${theme.spacing(1)};
`,
};
});
const getEmptyListStyles = stylesFactory((theme: GrafanaTheme2) => {
return {
container: css`
padding: 12px;
margin: 12px;
a,
span {
font-size: 13px;
}
`,
link: css`
color: ${theme.colors.text.link};
`,
};
});
@@ -8,6 +8,7 @@ import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { IconButton } from '../IconButton/IconButton';
import { stylesFactory, useTheme2 } from '../../themes';
import { FocusScope } from '@react-aria/focus';
import { useDialog } from '@react-aria/dialog';
import { useOverlay } from '@react-aria/overlays';
export interface Props {
@@ -34,6 +35,11 @@ export interface Props {
const getStyles = stylesFactory((theme: GrafanaTheme2, scrollableContent: boolean) => {
return {
container: css`
display: flex;
flex-direction: column;
height: 100%;
`,
drawer: css`
.drawer-content {
background-color: ${theme.colors.background.primary};
@@ -76,10 +82,9 @@ const getStyles = stylesFactory((theme: GrafanaTheme2, scrollableContent: boolea
`,
content: css`
padding: ${theme.spacing(2)};
flex-grow: 1;
flex: 1;
overflow: ${!scrollableContent ? 'hidden' : 'auto'};
z-index: 0;
height: 100%;
`,
};
});
@@ -101,6 +106,7 @@ export const Drawer: FC<Props> = ({
const [isOpen, setIsOpen] = useState(false);
const currentWidth = isExpanded ? '100%' : width;
const overlayRef = React.useRef(null);
const { dialogProps, titleProps } = useDialog({}, overlayRef);
const { overlayProps } = useOverlay(
{
isDismissable: true,
@@ -132,45 +138,47 @@ export const Drawer: FC<Props> = ({
}
>
<FocusScope restoreFocus contain autoFocus>
{typeof title === 'string' && (
<div className={drawerStyles.header} {...overlayProps} ref={overlayRef}>
<div className={drawerStyles.actions}>
{expandable && !isExpanded && (
<div className={drawerStyles.container} {...overlayProps} {...dialogProps} ref={overlayRef}>
{typeof title === 'string' && (
<div className={drawerStyles.header}>
<div className={drawerStyles.actions}>
{expandable && !isExpanded && (
<IconButton
name="angle-left"
size="xl"
onClick={() => setIsExpanded(true)}
surface="header"
aria-label={selectors.components.Drawer.General.expand}
/>
)}
{expandable && isExpanded && (
<IconButton
name="angle-right"
size="xl"
onClick={() => setIsExpanded(false)}
surface="header"
aria-label={selectors.components.Drawer.General.contract}
/>
)}
<IconButton
name="angle-left"
name="times"
size="xl"
onClick={() => setIsExpanded(true)}
onClick={onClose}
surface="header"
aria-label={selectors.components.Drawer.General.expand}
aria-label={selectors.components.Drawer.General.close}
/>
)}
{expandable && isExpanded && (
<IconButton
name="angle-right"
size="xl"
onClick={() => setIsExpanded(false)}
surface="header"
aria-label={selectors.components.Drawer.General.contract}
/>
)}
<IconButton
name="times"
size="xl"
onClick={onClose}
surface="header"
aria-label={selectors.components.Drawer.General.close}
/>
</div>
<div className={drawerStyles.titleWrapper}>
<h3>{title}</h3>
{typeof subtitle === 'string' && <div className="muted">{subtitle}</div>}
{typeof subtitle !== 'string' && subtitle}
</div>
<div className={drawerStyles.titleWrapper}>
<h3 {...titleProps}>{title}</h3>
{typeof subtitle === 'string' && <div className="muted">{subtitle}</div>}
{typeof subtitle !== 'string' && subtitle}
</div>
</div>
)}
{typeof title !== 'string' && title}
<div className={drawerStyles.content}>
{!scrollableContent ? children : <CustomScrollbar>{children}</CustomScrollbar>}
</div>
)}
{typeof title !== 'string' && title}
<div className={drawerStyles.content} {...overlayProps} ref={overlayRef}>
{!scrollableContent ? children : <CustomScrollbar>{children}</CustomScrollbar>}
</div>
</FocusScope>
</RcDrawer>
@@ -57,7 +57,11 @@ const ButtonSelectComponent = <T,>(props: Props<T>) => {
<div className={styles.menuWrapper}>
<ClickOutsideWrapper onClick={state.close} parent={document} includeButtonPress={false}>
<FocusScope contain autoFocus restoreFocus>
<Menu onClose={state.close} {...menuProps}>
{/*
tabIndex=-1 is needed here to support highlighting text within the menu when using FocusScope
see https://github.com/adobe/react-spectrum/issues/1604#issuecomment-781574668
*/}
<Menu tabIndex={-1} onClose={state.close} {...menuProps}>
{options.map((item) => (
<MenuItem
key={`${item.value}`}
@@ -60,12 +60,14 @@ Object {
],
"bands": Array [
Object {
"dir": -1,
"series": Array [
2,
1,
],
},
Object {
"dir": -1,
"series": Array [
4,
3,
@@ -0,0 +1,215 @@
import { ArrayVector, FieldType, MutableDataFrame } from '@grafana/data';
import { applyNullInsertThreshold } from './nullInsertThreshold';
function randInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function genFrame() {
let fieldCount = 10;
let valueCount = 3000;
let step = 1000;
let skipProb = 0.5;
let skipSteps = [1, 5]; // min, max
let allValues = Array(fieldCount);
allValues[0] = Array(valueCount);
for (let i = 0, curStep = Date.now(); i < valueCount; i++) {
curStep = allValues[0][i] = curStep + step * (Math.random() < skipProb ? randInt(skipSteps[0], skipSteps[1]) : 1);
}
for (let fi = 1; fi < fieldCount; fi++) {
let values = Array(valueCount);
for (let i = 0; i < valueCount; i++) {
values[i] = Math.random() * 100;
}
allValues[fi] = values;
}
return {
length: valueCount,
fields: allValues.map((values, i) => {
return {
name: 'A-' + i,
type: i === 0 ? FieldType.time : FieldType.number,
config: {
interval: i === 0 ? step : null,
},
values: new ArrayVector(values),
};
}),
};
}
describe('nullInsertThreshold Transformer', () => {
test('should insert nulls at +threshold between adjacent > threshold: 1', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, values: [1, 3, 10] },
{ name: 'One', type: FieldType.number, config: { custom: { insertNulls: 1 } }, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, config: { custom: { insertNulls: 1 } }, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df);
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 10]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']);
});
test('should insert nulls at +threshold between adjacent > threshold: 2', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, values: [5, 7, 11] },
{ name: 'One', type: FieldType.number, config: { custom: { insertNulls: 2 } }, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, config: { custom: { insertNulls: 2 } }, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df);
expect(result.fields[0].values.toArray()).toStrictEqual([5, 7, 9, 11]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', null, 'c']);
});
test('should insert nulls at +interval between adjacent > interval: 1', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1, 3, 10] },
{ name: 'One', type: FieldType.number, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df);
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 10]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']);
});
// TODO: make this work
test.skip('should insert nulls at +threshold (when defined) instead of +interval', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 2 }, values: [5, 7, 11] },
{ name: 'One', type: FieldType.number, config: { custom: { insertNulls: 1 } }, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, config: { custom: { insertNulls: 1 } }, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df);
expect(result.fields[0].values.toArray()).toStrictEqual([5, 6, 7, 8, 11]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']);
});
test('should insert nulls at midpoints between adjacent > interval: 2', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 2 }, values: [5, 7, 11] },
{ name: 'One', type: FieldType.number, values: [4, 6, 8] },
{ name: 'Two', type: FieldType.string, values: ['a', 'b', 'c'] },
],
});
const result = applyNullInsertThreshold(df);
expect(result.fields[0].values.toArray()).toStrictEqual([5, 7, 9, 11]);
expect(result.fields[1].values.toArray()).toStrictEqual([4, 6, null, 8]);
expect(result.fields[2].values.toArray()).toStrictEqual(['a', 'b', null, 'c']);
});
test('should noop on fewer than two values', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1] },
{ name: 'Value', type: FieldType.number, values: [1] },
],
});
const result = applyNullInsertThreshold(df);
expect(result).toBe(df);
});
test('should noop on invalid threshold', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, values: [1, 2, 4] },
{ name: 'Value', type: FieldType.number, config: { custom: { insertNulls: -1 } }, values: [1, 1, 1] },
],
});
const result = applyNullInsertThreshold(df);
expect(result).toBe(df);
});
test('should noop on invalid interval', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: -1 }, values: [1, 2, 4] },
{ name: 'Value', type: FieldType.number, values: [1, 1, 1] },
],
});
const result = applyNullInsertThreshold(df);
expect(result).toBe(df);
});
test('should noop when no missing steps', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1, 2, 3] },
{ name: 'Value', type: FieldType.number, values: [1, 1, 1] },
],
});
const result = applyNullInsertThreshold(df);
expect(result).toBe(df);
});
test('should noop when refFieldName not found', () => {
const df = new MutableDataFrame({
refId: 'A',
fields: [
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1, 2, 5] },
{ name: 'Value', type: FieldType.number, values: [1, 1, 1] },
],
});
const result = applyNullInsertThreshold(df, 'Time2');
expect(result).toBe(df);
});
test('perf stress test should be <= 10ms', () => {
// 10 fields x 3,000 values with 50% skip (output = 10 fields x 6,000 values)
let bigFrameA = genFrame();
// eslint-disable-next-line no-console
console.time('insertValues-10x3k');
applyNullInsertThreshold(bigFrameA);
// eslint-disable-next-line no-console
console.timeEnd('insertValues-10x3k');
});
});
@@ -0,0 +1,115 @@
import { ArrayVector, DataFrame, FieldType } from '@grafana/data';
type InsertMode = (prev: number, next: number, threshold: number) => number;
const INSERT_MODES = {
threshold: (prev: number, next: number, threshold: number) => prev + threshold,
midpoint: (prev: number, next: number, threshold: number) => (prev + next) / 2,
// previous time + 1ms to prevent StateTimeline from forward-interpolating prior state
plusone: (prev: number, next: number, threshold: number) => prev + 1,
};
export function applyNullInsertThreshold(
frame: DataFrame,
refFieldName?: string | null,
insertMode: InsertMode = INSERT_MODES.threshold
): DataFrame {
if (frame.length < 2) {
return frame;
}
const refField = frame.fields.find((field) => {
// note: getFieldDisplayName() would require full DF[]
return refFieldName != null ? field.name === refFieldName : field.type === FieldType.time;
});
if (refField == null) {
return frame;
}
const thresholds = frame.fields.map((field) => field.config.custom?.insertNulls ?? refField.config.interval ?? null);
const uniqueThresholds = new Set<number>(thresholds);
uniqueThresholds.delete(null as any);
if (uniqueThresholds.size === 0) {
return frame;
}
if (uniqueThresholds.size === 1) {
const threshold = uniqueThresholds.values().next().value;
if (threshold <= 0) {
return frame;
}
const refValues = refField.values.toArray();
const frameValues = frame.fields.map((field) => field.values.toArray());
const filledFieldValues = nullInsertThreshold(refValues, frameValues, threshold, insertMode);
if (filledFieldValues === frameValues) {
return frame;
}
return {
...frame,
length: filledFieldValues[0].length,
fields: frame.fields.map((field, i) => ({
...field,
values: new ArrayVector(filledFieldValues[i]),
})),
};
}
// TODO: unique threshold-per-field (via overrides) is unimplemented
// should be done by processing each (refField + thresholdA-field1 + thresholdA-field2...)
// as a separate nullInsertThreshold() dataset, then re-join into single dataset via join()
return frame;
}
function nullInsertThreshold(refValues: number[], frameValues: any[][], threshold: number, getInsertValue: InsertMode) {
const len = refValues.length;
let prevValue: number = refValues[0];
const refValuesNew: number[] = [prevValue];
for (let i = 1; i < len; i++) {
const curValue = refValues[i];
if (curValue - prevValue > threshold) {
refValuesNew.push(getInsertValue(prevValue, curValue, threshold));
}
refValuesNew.push(curValue);
prevValue = curValue;
}
const filledLen = refValuesNew.length;
if (filledLen === len) {
return frameValues;
}
const filledFieldValues: any[][] = [];
for (let fieldValues of frameValues) {
let filledValues;
if (fieldValues !== refValues) {
filledValues = Array(filledLen);
for (let i = 0, j = 0; i < filledLen; i++) {
filledValues[i] = refValues[j] === refValuesNew[i] ? fieldValues[j++] : null;
}
} else {
filledValues = refValuesNew;
}
filledFieldValues.push(filledValues);
}
return filledFieldValues;
}
@@ -1,6 +1,7 @@
import { XYFieldMatchers } from './types';
import { ArrayVector, DataFrame, FieldConfig, FieldType, outerJoinDataFrames } from '@grafana/data';
import { nullToUndefThreshold } from './nullToUndefThreshold';
import { applyNullInsertThreshold } from './nullInsertThreshold';
import { AxisPlacement, GraphFieldConfig, ScaleDistribution, ScaleDistributionConfig } from '@grafana/schema';
import { FIXED_UNIT } from './GraphNG';
@@ -16,13 +17,11 @@ function applySpanNullsThresholds(frame: DataFrame) {
continue;
}
if (field.type === FieldType.number) {
let spanNulls = field.config.custom?.spanNulls;
let spanNulls = field.config.custom?.spanNulls;
if (typeof spanNulls === 'number') {
if (spanNulls !== -1) {
field.values = new ArrayVector(nullToUndefThreshold(refValues, field.values.toArray(), spanNulls));
}
if (typeof spanNulls === 'number') {
if (spanNulls !== -1) {
field.values = new ArrayVector(nullToUndefThreshold(refValues, field.values.toArray(), spanNulls));
}
}
}
@@ -32,7 +31,7 @@ function applySpanNullsThresholds(frame: DataFrame) {
export function preparePlotFrame(frames: DataFrame[], dimFields: XYFieldMatchers) {
let alignedFrame = outerJoinDataFrames({
frames: frames,
frames: frames.map((frame) => applyNullInsertThreshold(frame)),
joinBy: dimFields.x,
keep: dimFields.y,
keepOriginIndices: true,
@@ -80,7 +80,11 @@ export function Modal(props: PropsWithChildren<Props>) {
onClick={onClickBackdrop || (closeOnBackdropClick ? onDismiss : undefined)}
/>
<FocusScope contain={trapFocus} autoFocus restoreFocus>
<div className={cx(styles.modal, className)}>
{/*
tabIndex=-1 is needed here to support highlighting text within the modal when using FocusScope
see https://github.com/adobe/react-spectrum/issues/1604#issuecomment-781574668
*/}
<div tabIndex={-1} className={cx(styles.modal, className)}>
<div className={headerClass}>
{typeof title === 'string' && <DefaultModalHeader {...props} title={title} />}
{typeof title !== 'string' && title}
@@ -67,7 +67,8 @@ export class SelectValueEditor<T> extends React.PureComponent<Props<T>, State<T>
value={current}
defaultValue={value}
allowCustomValue={settings?.allowCustomValue}
onChange={(e) => onChange(e.value)}
isClearable={settings?.isClearable}
onChange={(e) => onChange(e?.value)}
options={options}
/>
);
@@ -20,7 +20,6 @@ import { useTheme2 } from '../../themes';
import { getSelectStyles } from './getSelectStyles';
import { cleanValue, findSelectedValue } from './utils';
import { ActionMeta, SelectBaseProps, SelectValue } from './types';
import { deprecationWarning } from '@grafana/data';
interface ExtraValuesIndicatorProps {
maxVisibleValues?: number | undefined;
@@ -119,6 +118,7 @@ export function SelectBase<T>({
maxVisibleValues,
menuPlacement = 'auto',
menuPosition,
// TODO change this to default to true for Grafana 9
menuShouldPortal = false,
noOptionsMessage = 'No options found',
onBlur,
@@ -139,9 +139,6 @@ export function SelectBase<T>({
width,
isValidNewOption,
}: SelectBaseProps<T>) {
if (menuShouldPortal === false) {
deprecationWarning('SelectBase', 'menuShouldPortal={false}', 'menuShouldPortal={true}');
}
const theme = useTheme2();
const styles = getSelectStyles(theme);
@@ -47,7 +47,7 @@ const getSelectContainerStyles = stylesFactory((theme: GrafanaTheme2, focused: b
/* The display property is set by the styles prop in SelectBase because it's dependant on the width prop */
flex-direction: row;
flex-wrap: wrap;
align-items: center;
align-items: stretch;
justify-content: space-between;
min-height: 32px;
@@ -46,9 +46,8 @@ export interface SelectCommonProps<T> {
menuPlacement?: 'auto' | 'bottom' | 'top';
menuPosition?: 'fixed' | 'absolute';
/**
* @deprecated
* Setting to true will portal the menu to `document.body`.
* This property will soon be removed and portalling will be the default behavior.
* This property will soon default to true and portalling will be the default behavior.
*/
menuShouldPortal?: boolean;
/** The message to display when no options could be found */
@@ -1,5 +1,6 @@
import { DataFrame, FieldConfig, FieldSparkline, IndexVector } from '@grafana/data';
import { GraphFieldConfig } from '@grafana/schema';
import { applyNullInsertThreshold } from '../GraphNG/nullInsertThreshold';
/** @internal
* Given a sparkline config returns a DataFrame ready to be turned into Plot data set
@@ -11,7 +12,7 @@ export function preparePlotFrame(sparkline: FieldSparkline, config?: FieldConfig
...config,
};
return {
return applyNullInsertThreshold({
refId: 'sparkline',
fields: [
sparkline.x ?? IndexVector.newField(length),
@@ -21,5 +22,5 @@ export function preparePlotFrame(sparkline: FieldSparkline, config?: FieldConfig
},
],
length,
};
});
}
@@ -26,7 +26,7 @@ import {
VizLegendOptions,
StackingMode,
} from '@grafana/schema';
import { collectStackingGroups, orderIdsByCalcs, preparePlotData } from '../uPlot/utils';
import { collectStackingGroups, INTERNAL_NEGATIVE_Y_PREFIX, orderIdsByCalcs, preparePlotData } from '../uPlot/utils';
import uPlot from 'uplot';
import { buildScaleKey } from '../GraphNG/utils';
@@ -332,11 +332,12 @@ export const preparePlotConfigBuilder: UPlotConfigPrepFn<{
}
if (stackingGroups.size !== 0) {
for (const [_, seriesIds] of stackingGroups.entries()) {
for (const [group, seriesIds] of stackingGroups.entries()) {
const seriesIdxs = orderIdsByCalcs({ ids: seriesIds, legend, frame });
for (let j = seriesIdxs.length - 1; j > 0; j--) {
builder.addBand({
series: [seriesIdxs[j], seriesIdxs[j - 1]],
dir: group.startsWith(INTERNAL_NEGATIVE_Y_PREFIX) ? 1 : -1,
});
}
}
@@ -9,7 +9,7 @@ import { useStyles2 } from '../../themes';
*/
export interface SeriesTableRowProps {
color?: string;
label?: string;
label?: React.ReactNode;
value?: string | GraphSeriesValue;
isActive?: boolean;
}
@@ -134,6 +134,10 @@ export function scaleGradient(u: uPlot, scaleKey: string, scaleStops: ScaleValue
let range = minStopPos - maxStopPos;
if (range === 0) {
return scaleStops[maxStopIdx!][1];
}
let x0, y0, x1, y1;
if (u.scales.x!.ori === ScaleOrientation.Horizontal) {
@@ -231,6 +231,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
const v = otherProps.data.fields[i].values.get(focusedPointIdxs[i]!);
const display = field.display!(v);
sortIdx.push([series.length, v]);
series.push({
color: display.color || FALLBACK_COLOR,
@@ -1,6 +1,6 @@
import { orderIdsByCalcs, preparePlotData, timeFormatToTemplate } from './utils';
import { FieldType, MutableDataFrame } from '@grafana/data';
import { StackingMode } from '@grafana/schema';
import { GraphTransform, StackingMode } from '@grafana/schema';
describe('timeFormatToTemplate', () => {
it.each`
@@ -53,6 +53,76 @@ describe('preparePlotData', () => {
`);
});
describe('transforms', () => {
it('negative-y transform', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
{ name: 'a', values: [-10, 20, 10] },
{ name: 'b', values: [10, 10, 10] },
{ name: 'c', values: [20, 20, 20], config: { custom: { transform: GraphTransform.NegativeY } } },
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
Array [
Array [
9997,
9998,
9999,
],
Array [
-10,
20,
10,
],
Array [
10,
10,
10,
],
Array [
-20,
-20,
-20,
],
]
`);
});
it('constant transform', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
{ name: 'a', values: [-10, 20, 10], config: { custom: { transform: GraphTransform.Constant } } },
{ name: 'b', values: [10, 10, 10] },
{ name: 'c', values: [20, 20, 20] },
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
Array [
Array [
9997,
9998,
9999,
],
Array [
-10,
-10,
-10,
],
Array [
10,
10,
10,
],
Array [
20,
20,
20,
],
]
`);
});
});
describe('stacking', () => {
it('none', () => {
const df = new MutableDataFrame({
@@ -148,6 +218,67 @@ describe('preparePlotData', () => {
`);
});
it('standard with negative y transform', () => {
const df = new MutableDataFrame({
fields: [
{ name: 'time', type: FieldType.time, values: [9997, 9998, 9999] },
{
name: 'a',
values: [-10, 20, 10],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
{
name: 'b',
values: [10, 10, 10],
config: { custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' } } },
},
{
name: 'c',
values: [20, 20, 20],
config: {
custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' }, transform: GraphTransform.NegativeY },
},
},
{
name: 'd',
values: [10, 10, 10],
config: {
custom: { stacking: { mode: StackingMode.Normal, group: 'stackA' }, transform: GraphTransform.NegativeY },
},
},
],
});
expect(preparePlotData([df])).toMatchInlineSnapshot(`
Array [
Array [
9997,
9998,
9999,
],
Array [
-10,
20,
10,
],
Array [
0,
30,
20,
],
Array [
-20,
-20,
-20,
],
Array [
-30,
-30,
-30,
],
]
`);
});
it('standard with multiple groups', () => {
const df = new MutableDataFrame({
fields: [
@@ -1,11 +1,12 @@
import { DataFrame, ensureTimeField, Field, FieldType } from '@grafana/data';
import { StackingMode, VizLegendOptions } from '@grafana/schema';
import { GraphFieldConfig, GraphTransform, StackingMode, VizLegendOptions } from '@grafana/schema';
import { orderBy } from 'lodash';
import uPlot, { AlignedData, Options, PaddingSide } from 'uplot';
import { attachDebugger } from '../../utils';
import { createLogger } from '../../utils/logger';
const ALLOWED_FORMAT_STRINGS_REGEX = /\b(YYYY|YY|MMMM|MMM|MM|M|DD|D|WWWW|WWW|HH|H|h|AA|aa|a|mm|m|ss|s|fff)\b/g;
export const INTERNAL_NEGATIVE_Y_PREFIX = '__internalNegY';
export function timeFormatToTemplate(f: string) {
return f.replace(ALLOWED_FORMAT_STRINGS_REGEX, (match) => `{${match}}`);
@@ -60,7 +61,16 @@ export function preparePlotData(
}
collectStackingGroups(f, stackingGroups, seriesIndex);
result.push(f.values.toArray());
const customConfig: GraphFieldConfig = f.config.custom || {};
const values = f.values.toArray();
if (customConfig.transform === GraphTransform.NegativeY) {
result.push(values.map((v) => v * -1));
} else if (customConfig.transform === GraphTransform.Constant) {
result.push(new Array(values.length).fill(values[0]));
} else {
result.push(values);
}
seriesIndex++;
}
@@ -128,10 +138,15 @@ export function collectStackingGroups(f: Field, groups: Map<string, number[]>, s
customConfig.stacking?.group &&
!customConfig.hideFrom?.viz
) {
if (!groups.has(customConfig.stacking.group)) {
groups.set(customConfig.stacking.group, [seriesIdx]);
const group =
customConfig.transform === GraphTransform.NegativeY
? `${INTERNAL_NEGATIVE_Y_PREFIX}-${customConfig.stacking.group}`
: customConfig.stacking.group;
if (!groups.has(group)) {
groups.set(group, [seriesIdx]);
} else {
groups.set(customConfig.stacking.group, groups.get(customConfig.stacking.group)!.concat(seriesIdx));
groups.set(group, groups.get(group)!.concat(seriesIdx));
}
}
}
@@ -177,6 +177,7 @@ export function SuggestionsPlugin({
};
return editor
.snapshotSelection()
.deleteBackward(backward)
.deleteForward(forward)
.insertText(suggestionText)
+3
View File
@@ -316,6 +316,7 @@ func putAdminScenario(t *testing.T, desc string, url string, routePattern string
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
@@ -372,6 +373,7 @@ func adminRevokeUserAuthTokenScenario(t *testing.T, desc string, url string, rou
sc.userAuthTokenService = fakeAuthTokenService
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
@@ -473,6 +475,7 @@ func adminCreateUserScenario(t *testing.T, desc string, url string, routePattern
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.UserId = testUserID
+1
View File
@@ -168,6 +168,7 @@ func postAlertScenario(t *testing.T, desc string, url string, routePattern strin
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
+4
View File
@@ -277,6 +277,7 @@ func postAnnotationScenario(t *testing.T, desc string, url string, routePattern
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
@@ -302,6 +303,7 @@ func putAnnotationScenario(t *testing.T, desc string, url string, routePattern s
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
@@ -326,6 +328,7 @@ func patchAnnotationScenario(t *testing.T, desc string, url string, routePattern
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
@@ -351,6 +354,7 @@ func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePatte
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.UserId = testUserID
sc.context.OrgId = testOrgID
+14 -10
View File
@@ -13,7 +13,6 @@ import (
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
acmiddleware "github.com/grafana/grafana/pkg/services/accesscontrol/middleware"
"github.com/grafana/grafana/pkg/services/featuremgmt"
sa "github.com/grafana/grafana/pkg/services/serviceaccounts/manager"
)
var plog = log.New("api")
@@ -28,7 +27,7 @@ func (hs *HTTPServer) registerRoutes() {
reqEditorRole := middleware.ReqEditorRole
reqOrgAdmin := middleware.ReqOrgAdmin
reqOrgAdminFolderAdminOrTeamAdmin := middleware.OrgAdminFolderAdminOrTeamAdmin
reqCanAccessTeams := middleware.AdminOrFeatureEnabled(hs.Cfg.EditorsCanAdmin)
reqCanAccessTeams := middleware.AdminOrEditorAndFeatureEnabled(hs.Cfg.EditorsCanAdmin)
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
authorize := acmiddleware.Middleware(hs.AccessControl)
@@ -58,8 +57,9 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/org/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), hs.Index)
r.Get("/org/users/new", reqOrgAdmin, hs.Index)
r.Get("/org/users/invite", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), hs.Index)
r.Get("/org/teams", reqCanAccessTeams, hs.Index)
r.Get("/org/teams/*", reqCanAccessTeams, hs.Index)
r.Get("/org/teams", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsRead)), hs.Index)
r.Get("/org/teams/edit/*", authorize(reqCanAccessTeams, teamsEditAccessEvaluator), hs.Index)
r.Get("/org/teams/new", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsCreate)), hs.Index)
r.Get("/org/serviceaccounts", middleware.ReqOrgAdmin, hs.Index)
r.Get("/org/serviceaccounts/:serviceAccountId", middleware.ReqOrgAdmin, hs.Index)
r.Get("/org/apikeys/", reqOrgAdmin, hs.Index)
@@ -140,6 +140,10 @@ func (hs *HTTPServer) registerRoutes() {
// expose plugin file system assets
r.Get("/public/plugins/:pluginId/*", hs.getPluginAssets)
if hs.Features.IsEnabled(featuremgmt.FlagSwaggerUi) {
r.Get("/swagger-ui", swaggerUI)
}
// authed api
r.Group("/api", func(apiRoute routing.RouteRegister) {
// user (signed in)
@@ -194,8 +198,8 @@ func (hs *HTTPServer) registerRoutes() {
// team without requirement of user to be org admin
apiRoute.Group("/teams", func(teamsRoute routing.RouteRegister) {
teamsRoute.Get("/:teamId", routing.Wrap(hs.GetTeamByID))
teamsRoute.Get("/search", routing.Wrap(hs.SearchTeams))
teamsRoute.Get("/:teamId", authorize(reqSignedIn, ac.EvalPermission(ac.ActionTeamsRead, ac.ScopeTeamsID)), routing.Wrap(hs.GetTeamByID))
teamsRoute.Get("/search", authorize(reqSignedIn, ac.EvalPermission(ac.ActionTeamsRead)), routing.Wrap(hs.SearchTeams))
})
// org information available to all users.
@@ -256,10 +260,10 @@ func (hs *HTTPServer) registerRoutes() {
// auth api keys
apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) {
keysRoute.Get("/", authorize(reqOrgAdmin, sa.ActionApikeyListEv), routing.Wrap(GetAPIKeys))
keysRoute.Post("/", authorize(reqOrgAdmin, sa.ActionApikeyAddEv), quota("api_key"), routing.Wrap(hs.AddAPIKey))
keysRoute.Post("/additional", authorize(reqOrgAdmin, sa.ActionApikeyAddAdditionalEv), quota("api_key"), routing.Wrap(hs.AdditionalAPIKey))
keysRoute.Delete("/:id", authorize(reqOrgAdmin, sa.ActionApikeyRemoveEv), routing.Wrap(DeleteAPIKey))
keysRoute.Get("/", routing.Wrap(GetAPIKeys))
keysRoute.Post("/", quota("api_key"), routing.Wrap(hs.AddAPIKey))
keysRoute.Post("/additional", quota("api_key"), routing.Wrap(hs.AdditionalAPIKey))
keysRoute.Delete("/:id", routing.Wrap(DeleteAPIKey))
}, reqOrgAdmin)
// Preferences
+17 -10
View File
@@ -101,6 +101,7 @@ func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
sc.resp = httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil)
require.NoError(sc.t, err)
req.Header.Add("Content-Type", "application/json")
sc.req = req
return sc
@@ -116,6 +117,8 @@ func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map
panic(fmt.Sprintf("Making request failed: %s", err))
}
req.Header.Add("Content-Type", "application/json")
q := req.URL.Query()
for k, v := range queryParams {
q.Add(k, v)
@@ -128,6 +131,7 @@ func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map
func (sc *scenarioContext) fakeReqNoAssertions(method, url string) *scenarioContext {
sc.resp = httptest.NewRecorder()
req, _ := http.NewRequest(method, url, nil)
req.Header.Add("Content-Type", "application/json")
sc.req = req
return sc
@@ -139,7 +143,7 @@ func (sc *scenarioContext) fakeReqNoAssertionsWithCookie(method, url string, coo
req, _ := http.NewRequest(method, url, nil)
req.Header = http.Header{"Cookie": sc.resp.Header()["Set-Cookie"]}
req.Header.Add("Content-Type", "application/json")
sc.req = req
return sc
@@ -272,12 +276,13 @@ type accessControlScenarioContext struct {
}
func setAccessControlPermissions(acmock *accesscontrolmock.Mock, perms []*accesscontrol.Permission, org int64) {
acmock.GetUserPermissionsFunc = func(_ context.Context, u *models.SignedInUser) ([]*accesscontrol.Permission, error) {
if u.OrgId == org {
return perms, nil
acmock.GetUserPermissionsFunc =
func(_ context.Context, u *models.SignedInUser, _ accesscontrol.Options) ([]*accesscontrol.Permission, error) {
if u.OrgId == org {
return perms, nil
}
return nil, nil
}
return nil, nil
}
}
// setInitCtxSignedInUser sets a copy of the user in initCtx
@@ -309,9 +314,10 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer {
cfg.IsFeatureToggleEnabled = features.IsEnabled
return &HTTPServer{
Cfg: cfg,
Features: features,
Bus: bus.GetBus(),
Cfg: cfg,
Features: features,
Bus: bus.GetBus(),
AccessControl: accesscontrolmock.New().WithDisabled(),
}
}
@@ -362,7 +368,8 @@ func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessCont
require.NoError(t, err)
hs.TeamPermissionsService = teamPermissionService
} else {
ac := ossaccesscontrol.ProvideService(hs.Features, &usagestats.UsageStatsMock{T: t}, database.ProvideService(db))
ac := ossaccesscontrol.ProvideService(hs.Features, &usagestats.UsageStatsMock{T: t},
database.ProvideService(db), routing.NewRouteRegister())
hs.AccessControl = ac
// Perform role registration
err := hs.declareFixedRoles()
+1
View File
@@ -434,6 +434,7 @@ func updateDashboardPermissionScenario(t *testing.T, ctx updatePermissionContext
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(ctx.cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.OrgId = testOrgID
sc.context.UserId = testUserID
+4
View File
@@ -35,6 +35,7 @@ import (
func TestGetHomeDashboard(t *testing.T) {
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
httpReq.Header.Add("Content-Type", "application/json")
req := &models.ReqContext{SignedInUser: &models.SignedInUser{}, Context: &web.Context{Req: httpReq}}
cfg := setting.NewCfg()
cfg.StaticRootPath = "../../public/"
@@ -1133,6 +1134,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.SignedInUser = &models.SignedInUser{OrgId: cmd.OrgId, UserId: cmd.UserId}
@@ -1166,6 +1168,7 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.SignedInUser = &models.SignedInUser{
OrgId: testOrgID,
@@ -1201,6 +1204,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout
sc := setupScenarioContext(t, url)
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(cmd)
c.Req.Header.Add("Content-Type", "application/json")
sc.context = c
sc.context.SignedInUser = &models.SignedInUser{
OrgId: testOrgID,
+2 -1
View File
@@ -76,7 +76,8 @@ func (hs *HTTPServer) getDataSourceAccessControlMetadata(c *models.ReqContext, d
return nil, nil
}
userPermissions, err := hs.AccessControl.GetUserPermissions(c.Req.Context(), c.SignedInUser)
userPermissions, err := hs.AccessControl.GetUserPermissions(c.Req.Context(), c.SignedInUser,
accesscontrol.Options{ReloadCache: false})
if err != nil || len(userPermissions) == 0 {
return nil, err
}
+67
View File
@@ -0,0 +1,67 @@
package definitions
import (
"github.com/grafana/grafana/pkg/api/dtos"
)
// swagger:route GET /admin/settings admin getSettings
//
// Fetch settings.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `settings:read` and scopes: `settings:*`, `settings:auth.saml:` and `settings:auth.saml:enabled` (property level).
//
// Security:
// - basic:
//
// Responses:
// 200: getSettingsResponse
// 401: unauthorisedError
// 403: forbiddenError
// swagger:route GET /admin/stats admin getStats
//
// Fetch Grafana Stats.
//
// Only works with Basic Authentication (username and password). See introduction for an explanation.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `server:stats:read`.
//
// Responses:
// 200: getStatsResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route POST /admin/pause-all-alerts admin pauseAllAlerts
//
// Pause/unpause all (legacy) alerts.
//
// Security:
// - basic:
//
// Responses:
// 200: pauseAlertsResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:parameters pauseAllAlerts
type PauseAllAlertsParam struct {
// in:body
// required:true
Body dtos.PauseAllAlertsCommand `json:"body"`
}
// swagger:response pauseAlertsResponse
type PauseAllAlertsResponse struct {
// in:body
Body struct {
// AlertsAffected is the number of the affected alerts.
// required: true
AlertsAffected int64 `json:"alertsAffected"`
// required: true
Message string `json:"message"`
// Alert result state
// required true
State string `json:"state"`
} `json:"body"`
}
+68
View File
@@ -0,0 +1,68 @@
package definitions
// swagger:route POST /admin/ldap/reload admin_ldap reloadLDAP
//
// Reloads the LDAP configuration.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `ldap.config:reload`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route POST /admin/ldap/sync/{user_id} admin_ldap syncLDAPUser
//
// Enables a single Grafana user to be synchronized against LDAP.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `ldap.user:sync`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route GET /admin/ldap/{user_name} admin_ldap getLDAPUser
//
// Finds an user based on a username in LDAP. This helps illustrate how would the particular user be mapped in Grafana when synced.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `ldap.user:read`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route GET /admin/ldap/status admin_ldap getLDAPStatus
//
// Attempts to connect to all the configured LDAP servers and returns information on whenever they're available or not.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `ldap.status:read`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:parameters getLDAPUser
type UserNameParam struct {
// in:path
// required:true
UserID string `json:"user_name"`
}
@@ -0,0 +1,81 @@
package definitions
// swagger:route POST /admin/provisioning/dashboards/reload admin_provisioning reloadProvisionedDashboards
//
// Reload dashboard provisioning configurations.
//
// Reloads the provisioning config files for dashboards again. It won’t return until the new provisioned entities are already stored in the database. In case of dashboards, it will stop polling for changes in dashboard files and then restart it with new configurations after returning.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `provisioning:reload` and scope `provisioners:dashboards`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route POST /admin/provisioning/datasources/reload admin_provisioning reloadProvisionedDatasources
//
// Reload datasource provisioning configurations.
//
// Reloads the provisioning config files for datasources again. It won’t return until the new provisioned entities are already stored in the database. In case of dashboards, it will stop polling for changes in dashboard files and then restart it with new configurations after returning.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `provisioning:reload` and scope `provisioners:datasources`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route POST /admin/provisioning/plugins/reload admin_provisioning reloadProvisionedPlugins
//
// Reload plugin provisioning configurations.
//
// Reloads the provisioning config files for plugins again. It won’t return until the new provisioned entities are already stored in the database. In case of dashboards, it will stop polling for changes in dashboard files and then restart it with new configurations after returning.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `provisioning:reload` and scope `provisioners:plugin`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route POST /admin/provisioning/notifications/reload admin_provisioning reloadProvisionedAlertNotifiers
//
// Reload legacy alert notifier provisioning configurations.
//
// Reloads the provisioning config files for legacy alert notifiers again. It won’t return until the new provisioned entities are already stored in the database. In case of dashboards, it will stop polling for changes in dashboard files and then restart it with new configurations after returning.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `provisioning:reload` and scope `provisioners:notifications`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route POST /admin/provisioning/accesscontrol/reload admin_provisioning reloadProvisionedAccessControl
//
// Reload access control provisioning configurations.
//
// Reloads the provisioning config files for access control again. It won’t return until the new provisioned entities are already stored in the database. In case of dashboards, it will stop polling for changes in dashboard files and then restart it with new configurations after returning.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `provisioning:reload` and scope `provisioners:accesscontrol`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
+255
View File
@@ -0,0 +1,255 @@
package definitions
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
// swagger:route POST /admin/users admin_users createUser
//
// Create new user.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:create`.
// Note that OrgId is an optional parameter that can be used to assign a new user to a different organization when `auto_assign_org` is set to `true`.
//
// Security:
// - basic:
//
// Responses:
// 200: createUserResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 412: preconditionFailedError
// 500: internalServerError
// swagger:route PUT /admin/users/{user_id}/password admin_users setPassword
//
// Set password for user.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.password:update` and scope `global:users:*`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route PUT /admin/users/{user_id}/permissions admin_users setPermissions
//
// Set permissions for user.
//
// Only works with Basic Authentication (username and password). See introduction for an explanation.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.permissions:update` and scope `global:users:*`.
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route DELETE /admin/users/{user_id} admin_users deleteUser
//
// Delete global User.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:delete` and scope `global:users:*`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route POST /admin/users/{user_id}/disable admin_users disableUser
//
// Disable user.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:disable` and scope `global:users:1` (userIDScope).
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route POST /admin/users/{user_id}/enable admin_users enableUser
//
// Enable user.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:enable` and scope `global:users:1` (userIDScope).
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route GET /admin/users/{user_id}/quotas admin_users getUserQuota
//
// Fetch user quota.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:list` and scope `global:users:1` (userIDScope).
//
// Security:
// - basic:
//
// Responses:
// 200: getQuotaResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route PUT /admin/users/{user_id}/quotas/{quota_target} admin_users updateUserQuota
//
// Update user quota.
//
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:update` and scope `global:users:1` (userIDScope).
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route GET /admin/users/{user_id}/auth-tokens admin_users getAuthTokens
//
// Return a list of all auth tokens (devices) that the user currently have logged in from.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:list` and scope `global:users:*`.
//
// Security:
// - basic:
//
// Responses:
// 200: getAuthTokensResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route POST /admin/users/{user_id}/revoke-auth-token admin_users revokeAuthToken
//
// Revoke auth token for user.
//
// Revokes the given auth token (device) for the user. User of issued auth token (device) will no longer be logged in and will be required to authenticate again upon next activity.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:update` and scope `global:users:*`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route POST /admin/users/{user_id}/logout admin_users logoutUser
//
// Logout user revokes all auth tokens (devices) for the user. User of issued auth tokens (devices) will no longer be logged in and will be required to authenticate again upon next activity.
// If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.logout` and scope `global:users:*`.
//
// Security:
// - basic:
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:parameters setPassword
type SetPasswordParam struct {
// in:body
// required:true
Body dtos.AdminUpdateUserPasswordForm `json:"body"`
}
// swagger:parameters revokeAuthToken revokeSignedINAuthTokenCmd
type RevokeAuthTokenParam struct {
// in:body
// required:true
Body models.RevokeAuthTokenCmd `json:"body"`
}
// swagger:parameters createUser
type CreateUserParam struct {
// in:body
// required:true
Body dtos.AdminCreateUserForm `json:"body"`
}
// swagger:parameters updateUserQuota updateOrgQuota
type TargeQuotaParam struct {
// in:path
// required:true
QuotaTarget string `json:"quota_target"`
}
// swagger:parameters updateUserQuota
type UpdateUserQuotaParam struct {
// in:body
// required:true
Body models.UpdateUserQuotaCmd `json:"body"`
}
// swagger:parameters setPermissions
type SetPermissionsParam struct {
// in:body
// required:true
Body dtos.AdminUpdateUserPermissionsForm `json:"body"`
}
// swagger:response createUserResponse
type CreateUserResponse struct {
// in:body
Body models.UserIdDTO `json:"body"`
}
// swagger:response getSettingsResponse
type GetSettingsResponse struct {
// in:body
Body setting.SettingsBag `json:"body"`
}
// swagger:response getStatsResponse
type GetStatsResponse struct {
// in:body
Body models.AdminStats `json:"body"`
}
// swagger:response getAuthTokensResponse
type GetAuthTokensResponse struct {
// in:body
Body []*models.UserToken `json:"body"`
}
// swagger:response getQuotaResponse
type GetQuotaResponseResponse struct {
// in:body
Body []*models.UserQuotaDTO `json:"body"`
}
+240
View File
@@ -0,0 +1,240 @@
package definitions
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/services/annotations"
)
// swagger:route GET /annotations annotations getAnnotations
//
// Find Annotations.
//
// Starting in Grafana v6.4 regions annotations are now returned in one entity that now includes the timeEnd property.
//
// Responses:
// 200: getAnnotationsResponse
// 401: unauthorisedError
// 500: internalServerError
// swagger:route POST /annotations/mass-delete annotations massDeleteAnnotations
//
// Delete multiple annotations.
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 500: internalServerError
// swagger:route POST /annotations annotations createAnnotation
//
// Create Annotation.
//
// Creates an annotation in the Grafana database. The dashboardId and panelId fields are optional. If they are not specified then a global annotation is created and can be queried in any dashboard that adds the Grafana annotations data source. When creating a region annotation include the timeEnd property.
// The format for `time` and `timeEnd` should be epoch numbers in millisecond resolution.
// The response for this HTTP request is slightly different in versions prior to v6.4. In prior versions you would also get an endId if you where creating a region. But in 6.4 regions are represented using a single event with time and timeEnd properties.
//
// Responses:
// 200: createAnnotationResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route POST /annotations/graphite annotations createGraphiteAnnotation
//
// Create Annotation in Graphite format.
//
// Creates an annotation by using Graphite-compatible event format. The `when` and `data` fields are optional. If `when` is not specified then the current time will be used as annotation’s timestamp. The `tags` field can also be in prior to Graphite `0.10.0` format (string with multiple tags being separated by a space).
//
// Responses:
// 200: createAnnotationResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route PUT /annotations/{annotation_id} annotations updateAnnotation
//
// Update Annotation.
//
// Updates all properties of an annotation that matches the specified id. To only update certain property, consider using the Patch Annotation operation.
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route PATCH /annotations/{annotation_id} annotations patchAnnotation
//
// Patch Annotation
//
// Updates one or more properties of an annotation that matches the specified ID.
// This operation currently supports updating of the `text`, `tags`, `time` and `timeEnd` properties.
// This is available in Grafana 6.0.0-beta2 and above.
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route DELETE /annotations/{annotation_id} annotations deleteAnnotation
//
// Delete Annotation By ID.
//
// Deletes the annotation that matches the specified ID.
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route GET /annotations/tags annotations getAnnotationTags
//
// Find Annotations Tags.
//
// Find all the event tags created in the annotations.
//
// Responses:
// 200: getAnnotationTagsResponse
// 401: unauthorisedError
// 500: internalServerError
// swagger:parameters updateAnnotation patchAnnotation deleteAnnotation
type AnnotationIDParam struct {
// in:path
// required:true
AnnotationID string `json:"annotation_id"`
}
// swagger:parameters getAnnotations
type GetAnnotationsParams struct {
// Find annotations created after specific epoch datetime in milliseconds.
// in:query
// required:false
From int64 `json:"from"`
// Find annotations created before specific epoch datetime in milliseconds.
// in:query
// required:false
To int64 `json:"to"`
// Limit response to annotations created by specific user.
// in:query
// required:false
UserID int64 `json:"userId"`
// Find annotations for a specified alert.
// in:query
// required:false
AlertID int64 `json:"alertId"`
// Find annotations that are scoped to a specific dashboard
// in:query
// required:false
DashboardID int64 `json:"dashboardId"`
// Find annotations that are scoped to a specific panel
// in:query
// required:false
PanelID int64 `json:"panelId"`
// Max limit for results returned.
// in:query
// required:false
Limit int64 `json:"limit"`
// Use this to filter global annotations. Global annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. You can filter by multiple tags.
// in:query
// required:false
// type: array
// collectionFormat: multi
Tags []string `json:"tags"`
// Return alerts or user created annotations
// in:query
// required:false
// Description:
// * `alert`
// * `annotation`
// enum: alert,annotation
Type string `json:"type"`
// Match any or all tags
// in:query
// required:false
MatchAny bool `json:"matchAny"`
}
// swagger:parameters getAnnotationTags
type GetAnnotationTagssParams struct {
// Tag is a string that you can use to filter tags.
// in:query
// required:false
Tag string `json:"tag"`
// Max limit for results returned.
// in:query
// required:false
// default: 100
Limit string `json:"limit"`
}
// swagger:parameters massDeleteAnnotations
type MassDeleteAnnotationsParams struct {
// in:body
// required:true
Body dtos.DeleteAnnotationsCmd `json:"body"`
}
// swagger:parameters createAnnotation
type CreateAnnotationParams struct {
// in:body
// required:true
Body dtos.PostAnnotationsCmd `json:"body"`
}
// swagger:parameters createGraphiteAnnotation
type CreateGraphiteAnnotationParams struct {
// in:body
// required:true
Body dtos.PostGraphiteAnnotationsCmd `json:"body"`
}
// swagger:parameters updateAnnotation
type UpdateAnnotationParams struct {
// in:body
// required:true
Body dtos.UpdateAnnotationsCmd `json:"body"`
}
// swagger:parameters patchAnnotation
type PatchAnnotationParams struct {
// in:body
// required:true
Body dtos.PatchAnnotationsCmd `json:"body"`
}
// swagger:response getAnnotationsResponse
type GetAnnotationsResponse struct {
// The response message
// in: body
Body []*annotations.ItemDTO `json:"body"`
}
// swagger:response createAnnotationResponse
type CreateAnnotationResponse struct {
// The response message
// in: body
Body struct {
// ID Identifier of the created annotation.
// required: true
// example: 65
ID int64 `json:"id"`
// Message Message of the created annotation.
// required: true
Message string `json:"message"`
} `json:"body"`
}
// swagger:response getAnnotationTagsResponse
type GetAnnotationTagsResponse struct {
// The response message
// in: body
Body annotations.GetAnnotationTagsResponse `json:"body"`
}
+81
View File
@@ -0,0 +1,81 @@
package definitions
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
)
// swagger:route GET /auth/keys api_keys getAPIkeys
//
// Get auth keys.
//
// Will return auth keys.
//
// Responses:
// 200: getAPIkeyResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route POST /auth/keys api_keys addAPIkey
//
// Creates an API key.
//
// Will return details of the created API key
//
// Responses:
// 200: postAPIkeyResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 409: conflictError
// 500: internalServerError
// swagger:route DELETE /auth/keys/{id} api_keys deleteAPIkey
//
// Delete API key.
//
// Responses:
// 200: okResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:parameters getAPIkeys
type GetAPIkeysParams struct {
// Show expired keys
// in:query
// required:false
// default:false
IncludeExpired bool `json:"includeExpired"`
}
// swagger:parameters addAPIkey
type AddAPIkeyParams struct {
// in:body
// required:true
Body models.AddApiKeyCommand
}
// swagger:parameters deleteAPIkey
type DeleteAPIkeyParams struct {
// in:path
// required:true
ID int64 `json:"id"`
}
// swagger:response getAPIkeyResponse
type GetAPIkeyResponse struct {
// The response message
// in: body
Body []*models.ApiKeyDTO `json:"body"`
}
// swagger:response postAPIkeyResponse
type PostAPIkeyResponse struct {
// The response message
// in: body
Body dtos.NewApiKeyResult `json:"body"`
}
+257
View File
@@ -0,0 +1,257 @@
package definitions
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboardimport"
)
// swagger:route GET /dashboards/uid/{uid} dashboards getDashboardByUID
//
// Get dashboard by uid.
//
// Will return the dashboard given the dashboard unique identifier (uid).
//
// Responses:
// 200: dashboardResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// DeleteDashboardByUID swagger:route DELETE /dashboards/uid/{uid} dashboards deleteDashboardByUID
//
// Delete dashboard by uid.
//
// Will delete the dashboard given the specified unique identifier (uid).
//
// Responses:
// 200: deleteDashboardResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route POST /dashboards/calculate-diff dashboards calcDashboardDiff
//
// Perform diff on two dashboards.
//
// Produces:
// - application/json
// - text/html
//
// Responses:
// 200: dashboardDiffResponse
// 401: unauthorisedError
// 403: forbiddenError
// 500: internalServerError
// swagger:route POST /dashboards/trim dashboards trimDashboard
//
// Trim defaults from dashboard.
//
// Responses:
// 200: trimDashboardResponse
// 401: unauthorisedError
// 500: internalServerError
// swagger:route POST /dashboards/db dashboards postDashboard
//
// Create / Update dashboard
//
// Creates a new dashboard or updates an existing dashboard.
//
// Responses:
// 200: postDashboardResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 412: preconditionFailedError
// 422: unprocessableEntityError
// 500: internalServerError
// swagger:route GET /dashboards/home dashboards getHomeDashboard
//
// Get home dashboard.
//
// Responses:
// 200: getHomeDashboardResponse
// 401: unauthorisedError
// 500: internalServerError
// swagger:route GET /dashboards/tags dashboards getDashboardTags
//
// Get all dashboards tags of an organisation.
//
// Responses:
// 200: dashboardsTagsResponse
// 401: unauthorisedError
// 500: internalServerError
// swagger:route POST /dashboards/import dashboards importDashboard
//
// Import dashboard.
//
// Responses:
// 200: importDashboardResponse
// 400: badRequestError
// 401: unauthorisedError
// 412: preconditionFailedError
// 422: unprocessableEntityError
// 500: internalServerError
// swagger:parameters getDashboardByUID deleteDashboardByUID
type UID struct {
// in:path
// required:true
UID string `json:"uid"`
}
// swagger:parameters postDashboard
type PostDashboardParam struct {
// in:body
// required:true
Body models.SaveDashboardCommand
}
// swagger:parameters calcDashboardDiff
type CalcDashboardDiffOptions struct {
// in:body
// required:true
Body struct {
Base dtos.CalculateDiffTarget `json:"base" binding:"Required"`
New dtos.CalculateDiffTarget `json:"new" binding:"Required"`
// The type of diff to return
// Description:
// * `basic`
// * `json`
// Enum: basic,json
DiffType string `json:"diffType" binding:"Required"`
}
}
// swagger:parameters trimDashboard
type TrimDashboardParam struct {
// in:body
// required:true
Body models.TrimDashboardCommand
}
// swagger:parameters importDashboard
type ImportDashboardParam struct {
// in:body
// required:true
Body dashboardimport.ImportDashboardRequest
}
// swagger:response dashboardResponse
type DashboardResponse struct {
// The response message
// in: body
Body dtos.DashboardFullWithMeta `json:"body"`
}
// swagger:response deleteDashboardResponse
type DeleteDashboardResponse struct {
// The response message
// in: body
Body struct {
// ID Identifier of the deleted dashboard.
// required: true
// example: 65
ID int64 `json:"id"`
// Title Title of the deleted dashboard.
// required: true
// example: My Dashboard
Title string `json:"title"`
// Message Message of the deleted dashboard.
// required: true
// example: Dashboard My Dashboard deleted
Message string `json:"message"`
} `json:"body"`
}
// Create/update dashboard response.
// swagger:response postDashboardResponse
type PostDashboardResponse struct {
// in: body
Body struct {
// Status status of the response.
// required: true
// example: success
Status string `json:"status"`
// Slug The slug of the dashboard.
// required: true
// example: my-dashboard
Slug string `json:"title"`
// Version The version of the dashboard.
// required: true
// example: 2
Verion int64 `json:"version"`
// ID The unique identifier (id) of the created/updated dashboard.
// required: true
// example: 1
ID string `json:"id"`
// UID The unique identifier (uid) of the created/updated dashboard.
// required: true
// example: nHz3SXiiz
UID string `json:"uid"`
// URL The relative URL for accessing the created/updated dashboard.
// required: true
// example: /d/nHz3SXiiz/my-dashboard
URL string `json:"url"`
} `json:"body"`
}
// Calculate dashboard diff response.
// swagger:response dashboardDiffResponse
type DashboardDiffResponse struct {
// in: body
Body []byte `json:"body"`
}
// Trimmed dashboard response.
// swagger:response trimDashboardResponse
type TrimDashboardResponse struct {
// in: body
Body dtos.TrimDashboardFullWithMeta `json:"body"`
}
// Home dashboard response.
// swagger:response getHomeDashboardResponse
type GetHomeDashboardResponse struct {
// in: body
Body GetHomeDashboardResponseBody `json:"body"`
}
// swagger:response dashboardsTagsResponse
type DashboardsTagsResponse struct {
// in: body
Body []*models.DashboardTagCloudItem `json:"body"`
}
// swagger:response importDashboardResponse
type ImportDashboardResponse struct {
// in: body
Body dashboardimport.ImportDashboardResponse `json:"body"`
}
// Get home dashboard response.
// swagger:model GetHomeDashboardResponse
type GetHomeDashboardResponseBody struct {
// swagger:allOf
// required: false
dtos.DashboardFullWithMeta
// swagger:allOf
// required: false
dtos.DashboardRedirect
}
@@ -0,0 +1,44 @@
package definitions
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
)
// swagger:route GET /dashboards/id/{DashboardID}/permissions dashboard_permissions getDashboardPermissions
//
// Gets all existing permissions for the given dashboard.
//
// Responses:
// 200: getDashboardPermissionsResponse
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:route POST /dashboards/id/{DashboardID}/permissions dashboard_permissions postDashboardPermissions
//
// Updates permissions for a dashboard.
//
// This operation will remove existing permissions if they’re not included in the request.
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
// 500: internalServerError
// swagger:parameters postDashboardPermissions updateFolderPermissions
type PostDashboardPermissionsParam struct {
// in:body
// required:true
Body dtos.UpdateDashboardAclCommand
}
// swagger:response getDashboardPermissionsResponse
type GetDashboardPermissionsResponse struct {
// in: body
Body []*models.DashboardAclInfoDTO `json:"body"`
}

Some files were not shown because too many files have changed in this diff Show More