mirror of
https://github.com/grafana/grafana.git
synced 2026-01-07 22:41:10 +08:00
Compare commits
42 Commits
v11.1.5
...
v7.3.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fce221a7d3 | ||
|
|
6c5e139d8f | ||
|
|
194362c302 | ||
|
|
11eb920e87 | ||
|
|
bb555684b1 | ||
|
|
f2fac78c0b | ||
|
|
08be99fa45 | ||
|
|
df2fda3f88 | ||
|
|
983d6c8ef8 | ||
|
|
12dc0ea111 | ||
|
|
103765c349 | ||
|
|
6b4fd2d33c | ||
|
|
1a0500bbeb | ||
|
|
f1b9c6cde1 | ||
|
|
fd851af389 | ||
|
|
8f7eb69db6 | ||
|
|
bf74c1fe3f | ||
|
|
c60ea5f25f | ||
|
|
12dc9ea49f | ||
|
|
e5f12afda2 | ||
|
|
77ab9f4331 | ||
|
|
0f0d860ed3 | ||
|
|
ec0383c9ff | ||
|
|
c510f30eb3 | ||
|
|
f0dafad5cb | ||
|
|
6b056c527f | ||
|
|
c9513c5e81 | ||
|
|
9153d2146b | ||
|
|
f3fa16706c | ||
|
|
9d7eaedb2b | ||
|
|
2221c2bbad | ||
|
|
363d0a9588 | ||
|
|
d18ac27126 | ||
|
|
37327d74f5 | ||
|
|
5757bd80d3 | ||
|
|
f071817b95 | ||
|
|
2110d4e9b4 | ||
|
|
5ac4ae37a2 | ||
|
|
c11c8b0b4a | ||
|
|
0df7b25a49 | ||
|
|
68a3631ed0 | ||
|
|
2e9a0d4755 |
268
.drone.yml
268
.drone.yml
@@ -14,7 +14,7 @@ steps:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
|
||||
- name: initialize
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
|
||||
@@ -27,19 +27,21 @@ steps:
|
||||
DOCKERIZE_VERSION: 0.6.1
|
||||
|
||||
- name: lint-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
|
||||
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
|
||||
- ./scripts/revive-strict
|
||||
- ./scripts/tidy-check.sh
|
||||
- ./grafana-mixin/scripts/lint.sh
|
||||
- ./grafana-mixin/scripts/build.sh
|
||||
environment:
|
||||
CGO_ENABLED: 1
|
||||
depends_on:
|
||||
- initialize
|
||||
|
||||
- name: codespell
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
|
||||
- codespell -I words_to_ignore.txt docs/
|
||||
@@ -47,7 +49,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: shellcheck
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
|
||||
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
|
||||
@@ -62,7 +64,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: test-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl test-backend
|
||||
- ./bin/grabpl integration-tests
|
||||
@@ -71,7 +73,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: test-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn run ci:test-frontend
|
||||
environment:
|
||||
@@ -80,7 +82,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: build-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --variants linux-x64,linux-x64-musl,osx64,win64 --no-pull-enterprise
|
||||
depends_on:
|
||||
@@ -89,7 +91,7 @@ steps:
|
||||
- test-backend
|
||||
|
||||
- name: build-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise
|
||||
depends_on:
|
||||
@@ -97,7 +99,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: build-plugins
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps
|
||||
depends_on:
|
||||
@@ -105,7 +107,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: package
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- . scripts/build/gpg-test-vars.sh && ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --variants linux-x64,linux-x64-musl,osx64,win64
|
||||
depends_on:
|
||||
@@ -118,7 +120,7 @@ steps:
|
||||
- shellcheck
|
||||
|
||||
- name: end-to-end-tests-server
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
detach: true
|
||||
commands:
|
||||
- ./e2e/start-server
|
||||
@@ -136,14 +138,14 @@ steps:
|
||||
- end-to-end-tests-server
|
||||
|
||||
- name: build-storybook
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn storybook:build
|
||||
depends_on:
|
||||
- package
|
||||
|
||||
- name: build-frontend-docs
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./scripts/ci-reference-docs-lint.sh ci
|
||||
depends_on:
|
||||
@@ -160,7 +162,7 @@ steps:
|
||||
- build-frontend-docs
|
||||
|
||||
- name: copy-packages-for-docker
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- cp dist/*.tar.gz* packaging/docker/
|
||||
depends_on:
|
||||
@@ -176,7 +178,7 @@ steps:
|
||||
- copy-packages-for-docker
|
||||
|
||||
- name: postgres-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
@@ -193,7 +195,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: mysql-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
@@ -270,7 +272,7 @@ steps:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
|
||||
- name: initialize
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
|
||||
@@ -295,19 +297,21 @@ steps:
|
||||
from_secret: drone_token
|
||||
|
||||
- name: lint-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
|
||||
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
|
||||
- ./scripts/revive-strict
|
||||
- ./scripts/tidy-check.sh
|
||||
- ./grafana-mixin/scripts/lint.sh
|
||||
- ./grafana-mixin/scripts/build.sh
|
||||
environment:
|
||||
CGO_ENABLED: 1
|
||||
depends_on:
|
||||
- initialize
|
||||
|
||||
- name: codespell
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
|
||||
- codespell -I words_to_ignore.txt docs/
|
||||
@@ -315,7 +319,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: shellcheck
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
|
||||
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
|
||||
@@ -330,7 +334,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: test-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl test-backend
|
||||
- ./bin/grabpl integration-tests
|
||||
@@ -339,7 +343,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: test-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn run ci:test-frontend
|
||||
environment:
|
||||
@@ -348,7 +352,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: publish-frontend-metrics
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./scripts/ci-frontend-metrics.sh | ./bin/grabpl publish-metrics $${GRAFANA_MISC_STATS_API_KEY}
|
||||
environment:
|
||||
@@ -359,7 +363,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: build-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise
|
||||
depends_on:
|
||||
@@ -368,7 +372,7 @@ steps:
|
||||
- test-backend
|
||||
|
||||
- name: build-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --no-install-deps --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise
|
||||
depends_on:
|
||||
@@ -376,7 +380,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: build-plugins
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin
|
||||
environment:
|
||||
@@ -387,7 +391,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: package
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --sign
|
||||
environment:
|
||||
@@ -411,7 +415,7 @@ steps:
|
||||
- shellcheck
|
||||
|
||||
- name: end-to-end-tests-server
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
detach: true
|
||||
commands:
|
||||
- ./e2e/start-server
|
||||
@@ -429,7 +433,7 @@ steps:
|
||||
- end-to-end-tests-server
|
||||
|
||||
- name: build-storybook
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn storybook:build
|
||||
depends_on:
|
||||
@@ -448,7 +452,7 @@ steps:
|
||||
- build-storybook
|
||||
|
||||
- name: build-frontend-docs
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./scripts/ci-reference-docs-lint.sh ci
|
||||
depends_on:
|
||||
@@ -465,7 +469,7 @@ steps:
|
||||
- build-frontend-docs
|
||||
|
||||
- name: copy-packages-for-docker
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- cp dist/*.tar.gz* packaging/docker/
|
||||
depends_on:
|
||||
@@ -495,7 +499,7 @@ steps:
|
||||
- copy-packages-for-docker
|
||||
|
||||
- name: postgres-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
@@ -512,7 +516,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: mysql-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
@@ -528,7 +532,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: release-next-npm-packages
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./node_modules/.bin/lerna bootstrap
|
||||
- echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" >> ~/.npmrc
|
||||
@@ -648,7 +652,7 @@ steps:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
|
||||
- name: initialize
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
|
||||
@@ -723,13 +727,12 @@ steps:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
|
||||
- name: initialize
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./bin/grabpl verify-version $${DRONE_TAG}
|
||||
- ./bin/grabpl verify-version ${DRONE_TAG}
|
||||
- curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
|
||||
- tar -C bin -xzvf dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
|
||||
- rm dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
|
||||
@@ -738,19 +741,21 @@ steps:
|
||||
DOCKERIZE_VERSION: 0.6.1
|
||||
|
||||
- name: lint-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
|
||||
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
|
||||
- ./scripts/revive-strict
|
||||
- ./scripts/tidy-check.sh
|
||||
- ./grafana-mixin/scripts/lint.sh
|
||||
- ./grafana-mixin/scripts/build.sh
|
||||
environment:
|
||||
CGO_ENABLED: 1
|
||||
depends_on:
|
||||
- initialize
|
||||
|
||||
- name: codespell
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
|
||||
- codespell -I words_to_ignore.txt docs/
|
||||
@@ -758,7 +763,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: shellcheck
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
|
||||
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
|
||||
@@ -773,7 +778,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: test-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl test-backend
|
||||
- ./bin/grabpl integration-tests
|
||||
@@ -782,7 +787,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: test-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn run ci:test-frontend
|
||||
environment:
|
||||
@@ -791,10 +796,9 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: build-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise $${DRONE_TAG}
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise ${DRONE_TAG}
|
||||
environment:
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
@@ -804,16 +808,15 @@ steps:
|
||||
- test-backend
|
||||
|
||||
- name: build-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition oss --no-pull-enterprise $${DRONE_TAG}
|
||||
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition oss --no-pull-enterprise ${DRONE_TAG}
|
||||
depends_on:
|
||||
- initialize
|
||||
- test-frontend
|
||||
|
||||
- name: build-plugins
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin
|
||||
environment:
|
||||
@@ -824,10 +827,9 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: package
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign $${DRONE_TAG}
|
||||
- ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign ${DRONE_TAG}
|
||||
environment:
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
@@ -849,7 +851,7 @@ steps:
|
||||
- shellcheck
|
||||
|
||||
- name: end-to-end-tests-server
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
detach: true
|
||||
commands:
|
||||
- ./e2e/start-server
|
||||
@@ -867,7 +869,7 @@ steps:
|
||||
- end-to-end-tests-server
|
||||
|
||||
- name: build-storybook
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn storybook:build
|
||||
depends_on:
|
||||
@@ -876,11 +878,10 @@ steps:
|
||||
- name: publish-storybook
|
||||
image: grafana/grafana-ci-deploy:1.2.6
|
||||
commands:
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- printenv GCP_KEY | base64 -d > /tmp/gcpkey.json
|
||||
- gcloud auth activate-service-account --key-file=/tmp/gcpkey.json
|
||||
- gsutil -m rsync -d -r ./packages/grafana-ui/dist/storybook gs://grafana-storybook/latest
|
||||
- gsutil -m rsync -d -r ./packages/grafana-ui/dist/storybook gs://grafana-storybook/$${DRONE_TAG}
|
||||
- gsutil -m rsync -d -r ./packages/grafana-ui/dist/storybook gs://grafana-storybook/${DRONE_TAG}
|
||||
environment:
|
||||
GCP_KEY:
|
||||
from_secret: gcp_key
|
||||
@@ -888,7 +889,7 @@ steps:
|
||||
- build-storybook
|
||||
|
||||
- name: copy-packages-for-docker
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- cp dist/*.tar.gz* packaging/docker/
|
||||
depends_on:
|
||||
@@ -918,7 +919,7 @@ steps:
|
||||
- copy-packages-for-docker
|
||||
|
||||
- name: postgres-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
@@ -935,7 +936,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: mysql-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
@@ -951,12 +952,11 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: release-npm-packages
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./node_modules/.bin/lerna bootstrap
|
||||
- echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" >> ~/.npmrc
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./scripts/build/release-packages.sh $${DRONE_TAG}
|
||||
- ./scripts/build/release-packages.sh ${DRONE_TAG}
|
||||
environment:
|
||||
NPM_TOKEN:
|
||||
from_secret: npm_token
|
||||
@@ -1029,14 +1029,13 @@ steps:
|
||||
- name: build-windows-installer
|
||||
image: grafana/ci-wix:0.1.1
|
||||
commands:
|
||||
- $$env:DRONE_TAG=$$(.\grabpl.exe parse-tag-ref ${DRONE_TAG})
|
||||
- $$gcpKey = $$env:GCP_KEY
|
||||
- "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) > gcpkey.json"
|
||||
- dos2unix gcpkey.json
|
||||
- gcloud auth activate-service-account --key-file=gcpkey.json
|
||||
- rm gcpkey.json
|
||||
- cp C:\App\nssm-2.24.zip .
|
||||
- .\grabpl.exe windows-installer --edition oss $$env:DRONE_TAG
|
||||
- .\grabpl.exe windows-installer --edition oss ${DRONE_TAG}
|
||||
- $$fname = ((Get-Childitem grafana*.msi -name) -split "`n")[0]
|
||||
- gsutil cp $$fname gs://grafana-downloads/oss/release/
|
||||
- gsutil cp "$$fname.sha256" gs://grafana-downloads/oss/release/
|
||||
@@ -1072,29 +1071,28 @@ steps:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
|
||||
- name: clone
|
||||
image: alpine/git:v2.26.2
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
- git checkout $${DRONE_TAG}
|
||||
- git checkout ${DRONE_TAG}
|
||||
environment:
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
|
||||
- name: initialize
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- mv grabpl /tmp
|
||||
- mv bin/grabpl /tmp/
|
||||
- rmdir bin
|
||||
- mv grafana-enterprise /tmp/
|
||||
- /tmp/grabpl init-enterprise /tmp/grafana-enterprise $${DRONE_TAG}
|
||||
- /tmp/grabpl init-enterprise /tmp/grafana-enterprise ${DRONE_TAG}
|
||||
- mkdir bin
|
||||
- mv /tmp/grabpl bin/
|
||||
- ./bin/grabpl verify-version $${DRONE_TAG}
|
||||
- ./bin/grabpl verify-version ${DRONE_TAG}
|
||||
- curl -fLO https://github.com/jwilder/dockerize/releases/download/v$${DOCKERIZE_VERSION}/dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
|
||||
- tar -C bin -xzvf dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
|
||||
- rm dockerize-linux-amd64-v$${DOCKERIZE_VERSION}.tar.gz
|
||||
@@ -1105,19 +1103,21 @@ steps:
|
||||
- clone
|
||||
|
||||
- name: lint-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
|
||||
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
|
||||
- ./scripts/revive-strict
|
||||
- ./scripts/tidy-check.sh
|
||||
- ./grafana-mixin/scripts/lint.sh
|
||||
- ./grafana-mixin/scripts/build.sh
|
||||
environment:
|
||||
CGO_ENABLED: 1
|
||||
depends_on:
|
||||
- initialize
|
||||
|
||||
- name: codespell
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
|
||||
- codespell -I words_to_ignore.txt docs/
|
||||
@@ -1125,7 +1125,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: shellcheck
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
|
||||
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
|
||||
@@ -1140,7 +1140,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: test-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl test-backend
|
||||
- ./bin/grabpl integration-tests
|
||||
@@ -1149,7 +1149,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: test-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn run ci:test-frontend
|
||||
environment:
|
||||
@@ -1158,10 +1158,9 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: build-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise $${DRONE_TAG}
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise ${DRONE_TAG}
|
||||
environment:
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
@@ -1171,16 +1170,15 @@ steps:
|
||||
- test-backend
|
||||
|
||||
- name: build-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition enterprise --no-pull-enterprise $${DRONE_TAG}
|
||||
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition enterprise --no-pull-enterprise ${DRONE_TAG}
|
||||
depends_on:
|
||||
- initialize
|
||||
- test-frontend
|
||||
|
||||
- name: build-plugins
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-plugins --jobs 8 --edition enterprise --no-install-deps --sign --signing-admin
|
||||
environment:
|
||||
@@ -1191,10 +1189,9 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: package
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign $${DRONE_TAG}
|
||||
- ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign ${DRONE_TAG}
|
||||
environment:
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
@@ -1216,7 +1213,7 @@ steps:
|
||||
- shellcheck
|
||||
|
||||
- name: end-to-end-tests-server
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
detach: true
|
||||
commands:
|
||||
- ./e2e/start-server
|
||||
@@ -1234,7 +1231,7 @@ steps:
|
||||
- end-to-end-tests-server
|
||||
|
||||
- name: copy-packages-for-docker
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- cp dist/*.tar.gz* packaging/docker/
|
||||
depends_on:
|
||||
@@ -1264,7 +1261,7 @@ steps:
|
||||
- copy-packages-for-docker
|
||||
|
||||
- name: postgres-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
@@ -1281,7 +1278,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: mysql-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
@@ -1361,10 +1358,9 @@ steps:
|
||||
commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/windows/grabpl.exe -OutFile grabpl.exe
|
||||
- $$env:DRONE_TAG=$$(.\grabpl.exe parse-tag-ref ${DRONE_TAG})
|
||||
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
- git checkout $$env:DRONE_TAG
|
||||
- git checkout ${DRONE_TAG}
|
||||
environment:
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
@@ -1384,14 +1380,13 @@ steps:
|
||||
- name: build-windows-installer
|
||||
image: grafana/ci-wix:0.1.1
|
||||
commands:
|
||||
- $$env:DRONE_TAG=$$(.\grabpl.exe parse-tag-ref ${DRONE_TAG})
|
||||
- $$gcpKey = $$env:GCP_KEY
|
||||
- "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) > gcpkey.json"
|
||||
- dos2unix gcpkey.json
|
||||
- gcloud auth activate-service-account --key-file=gcpkey.json
|
||||
- rm gcpkey.json
|
||||
- cp C:\App\nssm-2.24.zip .
|
||||
- .\grabpl.exe windows-installer --edition enterprise $$env:DRONE_TAG
|
||||
- .\grabpl.exe windows-installer --edition enterprise ${DRONE_TAG}
|
||||
- $$fname = ((Get-Childitem grafana*.msi -name) -split "`n")[0]
|
||||
- gsutil cp $$fname gs://grafana-downloads/enterprise/release/
|
||||
- gsutil cp "$$fname.sha256" gs://grafana-downloads/enterprise/release/
|
||||
@@ -1424,22 +1419,20 @@ steps:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
|
||||
- name: initialize
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./bin/grabpl verify-version $${DRONE_TAG}
|
||||
- ./bin/grabpl verify-version ${DRONE_TAG}
|
||||
environment:
|
||||
DOCKERIZE_VERSION: 0.6.1
|
||||
|
||||
- name: publish-packages
|
||||
image: grafana/grafana-ci-deploy:1.2.6
|
||||
commands:
|
||||
- export DRONE_TAG=$$(./bin/grabpl parse-tag-ref ${DRONE_COMMIT_REF})
|
||||
- ./bin/grabpl publish-packages --edition oss $${DRONE_TAG}
|
||||
- ./bin/grabpl publish-packages --edition enterprise $${DRONE_TAG}
|
||||
- ./bin/grabpl publish-packages --edition oss ${DRONE_TAG}
|
||||
- ./bin/grabpl publish-packages --edition enterprise ${DRONE_TAG}
|
||||
environment:
|
||||
GRAFANA_COM_API_KEY:
|
||||
from_secret: grafana_api_key
|
||||
@@ -1503,7 +1496,7 @@ steps:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
|
||||
- name: initialize
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
|
||||
@@ -1517,19 +1510,21 @@ steps:
|
||||
DOCKERIZE_VERSION: 0.6.1
|
||||
|
||||
- name: lint-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
|
||||
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
|
||||
- ./scripts/revive-strict
|
||||
- ./scripts/tidy-check.sh
|
||||
- ./grafana-mixin/scripts/lint.sh
|
||||
- ./grafana-mixin/scripts/build.sh
|
||||
environment:
|
||||
CGO_ENABLED: 1
|
||||
depends_on:
|
||||
- initialize
|
||||
|
||||
- name: codespell
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
|
||||
- codespell -I words_to_ignore.txt docs/
|
||||
@@ -1537,7 +1532,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: shellcheck
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
|
||||
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
|
||||
@@ -1552,7 +1547,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: test-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl test-backend
|
||||
- ./bin/grabpl integration-tests
|
||||
@@ -1561,7 +1556,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: test-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn run ci:test-frontend
|
||||
environment:
|
||||
@@ -1570,7 +1565,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: build-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise v7.3.0-test
|
||||
environment:
|
||||
@@ -1582,7 +1577,7 @@ steps:
|
||||
- test-backend
|
||||
|
||||
- name: build-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition oss --no-pull-enterprise v7.3.0-test
|
||||
depends_on:
|
||||
@@ -1590,7 +1585,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: build-plugins
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-plugins --jobs 8 --edition oss --no-install-deps --sign --signing-admin
|
||||
environment:
|
||||
@@ -1601,7 +1596,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: package
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign v7.3.0-test
|
||||
environment:
|
||||
@@ -1625,7 +1620,7 @@ steps:
|
||||
- shellcheck
|
||||
|
||||
- name: end-to-end-tests-server
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
detach: true
|
||||
commands:
|
||||
- ./e2e/start-server
|
||||
@@ -1643,7 +1638,7 @@ steps:
|
||||
- end-to-end-tests-server
|
||||
|
||||
- name: build-storybook
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn storybook:build
|
||||
depends_on:
|
||||
@@ -1660,7 +1655,7 @@ steps:
|
||||
- build-storybook
|
||||
|
||||
- name: copy-packages-for-docker
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- cp dist/*.tar.gz* packaging/docker/
|
||||
depends_on:
|
||||
@@ -1684,7 +1679,7 @@ steps:
|
||||
- copy-packages-for-docker
|
||||
|
||||
- name: postgres-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
@@ -1701,7 +1696,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: mysql-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
@@ -1717,7 +1712,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: release-npm-packages
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./node_modules/.bin/lerna bootstrap
|
||||
- echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" >> ~/.npmrc
|
||||
@@ -1835,7 +1830,7 @@ steps:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
|
||||
- name: clone
|
||||
image: alpine/git:v2.26.2
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
|
||||
@@ -1848,9 +1843,10 @@ steps:
|
||||
from_secret: github_token
|
||||
|
||||
- name: initialize
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mv grabpl /tmp
|
||||
- mv bin/grabpl /tmp/
|
||||
- rmdir bin
|
||||
- mv grafana-enterprise /tmp/
|
||||
- /tmp/grabpl init-enterprise /tmp/grafana-enterprise
|
||||
- mkdir bin
|
||||
@@ -1866,19 +1862,21 @@ steps:
|
||||
- clone
|
||||
|
||||
- name: lint-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- golangci-lint run --config scripts/go/configs/.golangci.toml ./pkg/...
|
||||
- revive -formatter stylish -config scripts/go/configs/revive.toml ./pkg/...
|
||||
- ./scripts/revive-strict
|
||||
- ./scripts/tidy-check.sh
|
||||
- ./grafana-mixin/scripts/lint.sh
|
||||
- ./grafana-mixin/scripts/build.sh
|
||||
environment:
|
||||
CGO_ENABLED: 1
|
||||
depends_on:
|
||||
- initialize
|
||||
|
||||
- name: codespell
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- "echo -e \"unknwon\nreferer\nerrorstring\neror\niam\" > words_to_ignore.txt"
|
||||
- codespell -I words_to_ignore.txt docs/
|
||||
@@ -1886,7 +1884,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: shellcheck
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- curl -fLO http://storage.googleapis.com/grafana-downloads/ci-dependencies/shellcheck-v$${VERSION}.linux.x86_64.tar.xz
|
||||
- echo $$CHKSUM shellcheck-v$${VERSION}.linux.x86_64.tar.xz | sha512sum --check --strict --status
|
||||
@@ -1901,7 +1899,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: test-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl test-backend
|
||||
- ./bin/grabpl integration-tests
|
||||
@@ -1910,7 +1908,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: test-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- yarn run ci:test-frontend
|
||||
environment:
|
||||
@@ -1919,7 +1917,7 @@ steps:
|
||||
- initialize
|
||||
|
||||
- name: build-backend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise v7.3.0-test
|
||||
environment:
|
||||
@@ -1931,7 +1929,7 @@ steps:
|
||||
- test-backend
|
||||
|
||||
- name: build-frontend
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --no-install-deps --edition enterprise --no-pull-enterprise v7.3.0-test
|
||||
depends_on:
|
||||
@@ -1939,7 +1937,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: build-plugins
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl build-plugins --jobs 8 --edition enterprise --no-install-deps --sign --signing-admin
|
||||
environment:
|
||||
@@ -1950,7 +1948,7 @@ steps:
|
||||
- lint-backend
|
||||
|
||||
- name: package
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} --no-pull-enterprise --sign v7.3.0-test
|
||||
environment:
|
||||
@@ -1974,7 +1972,7 @@ steps:
|
||||
- shellcheck
|
||||
|
||||
- name: end-to-end-tests-server
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
detach: true
|
||||
commands:
|
||||
- ./e2e/start-server
|
||||
@@ -1992,7 +1990,7 @@ steps:
|
||||
- end-to-end-tests-server
|
||||
|
||||
- name: copy-packages-for-docker
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- cp dist/*.tar.gz* packaging/docker/
|
||||
depends_on:
|
||||
@@ -2016,7 +2014,7 @@ steps:
|
||||
- copy-packages-for-docker
|
||||
|
||||
- name: postgres-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
@@ -2033,7 +2031,7 @@ steps:
|
||||
- test-frontend
|
||||
|
||||
- name: mysql-integration-tests
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
@@ -2174,7 +2172,7 @@ steps:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
|
||||
- name: initialize
|
||||
image: grafana/build-container:1.2.27
|
||||
image: grafana/build-container:1.2.28
|
||||
commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v0.5.18/grabpl
|
||||
|
||||
66
CHANGELOG.md
66
CHANGELOG.md
@@ -1,9 +1,73 @@
|
||||
# 7.3.0-beta1 (2020-10-14)
|
||||
# 7.3.0-beta1 (2020-10-15)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **CloudWatch**: The AWS CloudWatch data source's authentication scheme has changed. See the [upgrade notes](https://grafana.com/docs/grafana/latest/installation/upgrading/#upgrading-to-v73) for details and how this may affect you.
|
||||
|
||||
### Features / Enhancements
|
||||
* **Alerting**: Add labels to name when converting data frame to series. [#28085](https://github.com/grafana/grafana/pull/28085), [@kylebrandt](https://github.com/kylebrandt)
|
||||
* **Alerting**: Ensuring LINE Notify notifications are sent for all alert states. [#27639](https://github.com/grafana/grafana/pull/27639), [@haraldkubota](https://github.com/haraldkubota)
|
||||
* **Auth**: Add SigV4 auth option to datasources. [#27552](https://github.com/grafana/grafana/pull/27552), [@wbrowne](https://github.com/wbrowne)
|
||||
* **AzureMonitor**: Pass through null values instead of setting 0. [#28126](https://github.com/grafana/grafana/pull/28126), [@kylebrandt](https://github.com/kylebrandt)
|
||||
* **Cloud Monitoring**: Out-of-the-box dashboards. [#27864](https://github.com/grafana/grafana/pull/27864), [@papagian](https://github.com/papagian)
|
||||
* **CloudWatch**: Add support for AWS DirectConnect virtual interface metrics and add missing dimensions. [#28008](https://github.com/grafana/grafana/pull/28008), [@jgulick48](https://github.com/jgulick48)
|
||||
* **CloudWatch**: Adding support for Amazon ElastiCache Redis metrics. [#28040](https://github.com/grafana/grafana/pull/28040), [@jgulick48](https://github.com/jgulick48)
|
||||
* **CloudWatch**: Adding support for additional Amazon CloudFront metrics. [#28069](https://github.com/grafana/grafana/pull/28069), [@darrylsepeda](https://github.com/darrylsepeda)
|
||||
* **CloudWatch**: Re-implement authentication. [#25548](https://github.com/grafana/grafana/pull/25548), [@aknuds1](https://github.com/aknuds1),[@patstrom](https://github.com/patstrom)
|
||||
* **Dashboard**: Allow shortlink generation. [#27409](https://github.com/grafana/grafana/pull/27409), [@MisterSquishy](https://github.com/MisterSquishy)
|
||||
* **Docker**: OpenShift compatability. [#27813](https://github.com/grafana/grafana/pull/27813), [@xlson](https://github.com/xlson)
|
||||
* **Elasticsearch**: Support multiple pipeline aggregations for a query. [#27945](https://github.com/grafana/grafana/pull/27945), [@simianhacker](https://github.com/simianhacker)
|
||||
* **Explore**: Allow shortlink generation. [#28222](https://github.com/grafana/grafana/pull/28222), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Explore**: Remove collapsing of visualisations. [#27026](https://github.com/grafana/grafana/pull/27026), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **FieldColor**: Adds new standard color option for color. [#28039](https://github.com/grafana/grafana/pull/28039), [@torkelo](https://github.com/torkelo)
|
||||
* **Gauge**: Improve text sizing and support non threshold color modes. [#28256](https://github.com/grafana/grafana/pull/28256), [@torkelo](https://github.com/torkelo)
|
||||
* **NamedColors**: Named colors refactors. [#28235](https://github.com/grafana/grafana/pull/28235), [@torkelo](https://github.com/torkelo)
|
||||
* **Panel Inspect**: Allow CSV download for Excel. [#27284](https://github.com/grafana/grafana/pull/27284), [@tomdaly](https://github.com/tomdaly)
|
||||
* **Prometheus**: Add time range parameters to labels API. [#27548](https://github.com/grafana/grafana/pull/27548), [@kakkoyun](https://github.com/kakkoyun)
|
||||
* **Snapshots**: Store dashboard data encrypted in the database. [#28129](https://github.com/grafana/grafana/pull/28129), [@wbrowne](https://github.com/wbrowne)
|
||||
* **Table**: New cell hover behavior and image cell display mode. [#27669](https://github.com/grafana/grafana/pull/27669), [@torkelo](https://github.com/torkelo)
|
||||
* **Timezones**: Include IANA timezone canonical name in TimeZoneInfo. [#27591](https://github.com/grafana/grafana/pull/27591), [@dprokop](https://github.com/dprokop)
|
||||
* **Tracing**: Add Tempo data source. [#28204](https://github.com/grafana/grafana/pull/28204), [@aocenas](https://github.com/aocenas)
|
||||
* **Transformations**: Add Concatenate fields transformer. [#28237](https://github.com/grafana/grafana/pull/28237), [@ryantxu](https://github.com/ryantxu)
|
||||
* **Transformations**: improve the reduce transformer. [#27875](https://github.com/grafana/grafana/pull/27875), [@ryantxu](https://github.com/ryantxu)
|
||||
* **Users**: Expire old user invites. [#27361](https://github.com/grafana/grafana/pull/27361), [@wbrowne](https://github.com/wbrowne)
|
||||
* **Variables**: Adds loading state and indicators. [#27917](https://github.com/grafana/grafana/pull/27917), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
* **Variables**: Adds support for key/value mapping in Custom variable. [#27829](https://github.com/grafana/grafana/pull/27829), [@sartaj10](https://github.com/sartaj10)
|
||||
* **grafana/toolkit**: expose Jest maxWorkers arg for plugin test & build tasks. [#27724](https://github.com/grafana/grafana/pull/27724), [@domasx2](https://github.com/domasx2)
|
||||
|
||||
### Bug Fixes
|
||||
* **Azure Analytics**: FormatAs Time series groups bool columns wrong. [#27713](https://github.com/grafana/grafana/issues/27713)
|
||||
* **Azure**: Fixes cancellation of requests with different Azure sources. [#28180](https://github.com/grafana/grafana/pull/28180), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
* **BackendSrv**: Reloads page instead of redirect on Unauthorized Error. [#28276](https://github.com/grafana/grafana/pull/28276), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
* **Dashboard**: Do not allow users without edit permission to a folder to see new dashboard page. [#28249](https://github.com/grafana/grafana/pull/28249), [@torkelo](https://github.com/torkelo)
|
||||
* **Dashboard**: Fixed issue accessing horizontal table scrollbar when placed at bottom of dashboard. [#28250](https://github.com/grafana/grafana/pull/28250), [@torkelo](https://github.com/torkelo)
|
||||
* **DataProxy**: Add additional settings for dataproxy to help with network proxy timeouts. [#27841](https://github.com/grafana/grafana/pull/27841), [@kahinton](https://github.com/kahinton)
|
||||
* **Database**: Adds new indices to alert_notification_state and alert_rule_tag tables. [#28166](https://github.com/grafana/grafana/pull/28166), [@KarineValenca](https://github.com/KarineValenca)
|
||||
* **Explore**: Fix showing of Prometheus data in Query inspector. [#28128](https://github.com/grafana/grafana/pull/28128), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Explore**: Show results of Prometheus instant queries in formatted table. [#27767](https://github.com/grafana/grafana/pull/27767), [@ivanahuckova](https://github.com/ivanahuckova)
|
||||
* **Graph**: Prevent legend from overflowing container. [#28254](https://github.com/grafana/grafana/pull/28254), [@jackw](https://github.com/jackw)
|
||||
* **OAuth**: Fix token refresh failure when custom SSL settings are configured for OAuth provider. [#27523](https://github.com/grafana/grafana/pull/27523), [@billoley](https://github.com/billoley)
|
||||
* **Plugins**: Let descendant plugins inherit their root's signature. [#27970](https://github.com/grafana/grafana/pull/27970), [@aknuds1](https://github.com/aknuds1)
|
||||
* **Runtime**: Fix handling of short-lived background services. [#28025](https://github.com/grafana/grafana/pull/28025), [@ahlaw](https://github.com/ahlaw)
|
||||
* **TemplateSrv**: Fix interpolating strings with object variables. [#28171](https://github.com/grafana/grafana/pull/28171), [@torkelo](https://github.com/torkelo)
|
||||
* **Variables**: Fixes so constants set from url get completed state. [#28257](https://github.com/grafana/grafana/pull/28257), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
* **Variables**: Prevent adhoc filters from crashing when they are not loaded properly. [#28226](https://github.com/grafana/grafana/pull/28226), [@mckn](https://github.com/mckn)
|
||||
|
||||
# 7.2.2 (2020-10-21)
|
||||
|
||||
### Features / Enhancements
|
||||
|
||||
**Caution:** Please do not use/enable the `database_metrics` feature flag. It will corrupt MySQL database tables. See [#28440](https://github.com/grafana/grafana/issues/28440) for more information.
|
||||
|
||||
~~**Instrumentation**: Add counters and histograms for database queries. [#28236](https://github.com/grafana/grafana/pull/28236), [@bergquist](https://github.com/bergquist)~~
|
||||
|
||||
- **Instrumentation**: Add histogram for request duration. [#28364](https://github.com/grafana/grafana/pull/28364), [@bergquist](https://github.com/bergquist)
|
||||
- **Instrumentation**: Adds environment_info metric. [#28355](https://github.com/grafana/grafana/pull/28355), [@bergquist](https://github.com/bergquist)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **CloudWatch**: Fix custom metrics. [#28391](https://github.com/grafana/grafana/pull/28391), [@aknuds1](https://github.com/aknuds1)
|
||||
|
||||
# 7.2.1 (2020-10-08)
|
||||
|
||||
### Features / Enhancements
|
||||
|
||||
@@ -670,6 +670,12 @@ disable_total_stats = false
|
||||
basic_auth_username =
|
||||
basic_auth_password =
|
||||
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# can expose more information about the Grafana instance.
|
||||
[metrics.environment_info]
|
||||
#exampleLabel1 = exampleValue1
|
||||
#exampleLabel2 = exampleValue2
|
||||
|
||||
# Send internal Grafana metrics to graphite
|
||||
[metrics.graphite]
|
||||
# Enable by setting the address setting (ex localhost:2003)
|
||||
|
||||
@@ -664,6 +664,12 @@
|
||||
; basic_auth_username =
|
||||
; basic_auth_password =
|
||||
|
||||
# Metrics environment info adds dimensions to the `grafana_environment_info` metric, which
|
||||
# can expose more information about the Grafana instance.
|
||||
[metrics.environment_info]
|
||||
#exampleLabel1 = exampleValue1
|
||||
#exampleLabel2 = exampleValue2
|
||||
|
||||
# Send internal metrics to Graphite
|
||||
[metrics.graphite]
|
||||
# Enable by setting the address setting (ex localhost:2003)
|
||||
|
||||
@@ -8,3 +8,6 @@ Learn more about the backend architecture:
|
||||
- Part 2: [Communication](communication.md)
|
||||
- Part 3: [Database](database.md)
|
||||
|
||||
Learn more about the frontend architecture:
|
||||
- Part 1: [Data requests](frontend-data-requests.md)
|
||||
|
||||
|
||||
41
contribute/architecture/frontend-data-requests.md
Normal file
41
contribute/architecture/frontend-data-requests.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Data requests
|
||||
|
||||
[BackendSrv](https://grafana.com/docs/grafana/latest/packages_api/runtime/backendsrv) handles all outgoing HTTP requests from Grafana. This document explains the high-level concepts used by `BackendSrv`.
|
||||
|
||||
## Canceling requests
|
||||
This section describes how canceling requests work in Grafana. While data sources can implement their own cancellation concept, we recommend that you use the method we describe here.
|
||||
|
||||
A data request can take a long time to finish. During the time between when a request starts and finishes, the user can change context. For example, the user may navigate away or issue the same request again.
|
||||
|
||||
If we wait for canceled requests to complete, it might create unnecessary load on data sources.
|
||||
|
||||
Grafana uses a concept called _request cancelation_ to cancel any ongoing request that Grafana doesn't need.
|
||||
|
||||
#### Before Grafana 7.2
|
||||
Before Grafana can cancel any data request, it has to identify that request. Grafana identifies a request using the property `requestId` [passed as options](https://github.com/grafana/grafana/blob/master/docs/sources/packages_api/runtime/backendsrvrequest.md) when you use [BackendSrv](https://grafana.com/docs/grafana/latest/packages_api/runtime/backendsrv).
|
||||
|
||||
The cancellation logic is as follows:
|
||||
- When an ongoing request discovers that an additional request with the same `requestId` has started, then Grafana will cancel the ongoing request.
|
||||
- When an ongoing request discovers that the special "cancel all requests" `requestId` was sent, then Grafana will cancel the ongoing request.
|
||||
|
||||
#### After Grafana 7.2
|
||||
Grafana 7.2 introduced an additional way of canceling requests using [RxJs](https://github.com/ReactiveX/rxjs). To support the new cancellation functionality, the data source needs to use the new `fetch` function in [BackendSrv](https://grafana.com/docs/grafana/latest/packages_api/runtime/backendsrv).
|
||||
|
||||
Migrating the core data sources to the new `fetch` function [is an ongoing process that you can read about in this issue.](https://github.com/grafana/grafana/issues/27222)
|
||||
|
||||
## Request queue
|
||||
Depending on how the web browser implements the protocol for HTTP 1.1, it will limit the number of parallel requests, lets call this limit _max_parallel_browser_request_.
|
||||
|
||||
Unless you have configured Grafana to use HTTP2, the browser limits parallel data requests according to the browser's implementation. For more information on how to enable HTTP2, refer to [Configuration](https://grafana.com/docs/grafana/latest/administration/configuration/#protocol).
|
||||
|
||||
Because there is a _max_parallel_browser_request_ limit, if some of the requests take a long time, they will block later requests and make interacting with Grafana very slow.
|
||||
|
||||
#### Before Grafana 7.2
|
||||
Not supported.
|
||||
|
||||
#### After Grafana 7.2
|
||||
Grafana uses a _request queue_ to process all incoming data requests in order while reserving a free "spot" for any requests to the Grafana API.
|
||||
|
||||
Since the first implementation of the request queue doesn't take into account what browser the user uses, the _request queue_ limit for parallel data source requests is hard-coded to 5.
|
||||
|
||||
> **Note:** Grafana instances [configured with HTTP2 ](https://grafana.com/docs/grafana/latest/administration/configuration/#protocol) will have a hard coded limit of 1000.
|
||||
338
devenv/dev-dashboards/panel-common/color_modes.json
Normal file
338
devenv/dev-dashboards/panel-common/color_modes.json
Normal file
@@ -0,0 +1,338 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": "gdev-testdata",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-BlYlRd"
|
||||
},
|
||||
"custom": {
|
||||
"align": "center",
|
||||
"displayMode": "color-background",
|
||||
"filterable": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "percentage",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "blue",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 70
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "degree"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Field"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.displayMode"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 16,
|
||||
"w": 19,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"options": {
|
||||
"showHeader": true,
|
||||
"sortBy": [
|
||||
{
|
||||
"desc": true,
|
||||
"displayName": "Last"
|
||||
}
|
||||
]
|
||||
},
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvWave": {
|
||||
"timeStep": 60,
|
||||
"valuesCSV": "0,0,2,2,1,1"
|
||||
},
|
||||
"lines": 10,
|
||||
"points": [],
|
||||
"pulseWave": {
|
||||
"offCount": 3,
|
||||
"offValue": 1,
|
||||
"onCount": 3,
|
||||
"onValue": 2,
|
||||
"timeStep": 60
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 15,
|
||||
"stream": {
|
||||
"bands": 1,
|
||||
"noise": 2.2,
|
||||
"speed": 250,
|
||||
"spread": 3.5,
|
||||
"type": "signal"
|
||||
},
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Gradient color schemes",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "reduce",
|
||||
"options": {
|
||||
"reducers": ["max", "mean", "last", "min"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"Field": false
|
||||
},
|
||||
"indexByName": {},
|
||||
"renameByName": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-blues"
|
||||
},
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 20
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 26,
|
||||
"w": 5,
|
||||
"x": 19,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"colorMode": "background",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": ["mean"],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "value"
|
||||
},
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvWave": {
|
||||
"timeStep": 60,
|
||||
"valuesCSV": "0,0,2,2,1,1"
|
||||
},
|
||||
"labels": "",
|
||||
"lines": 10,
|
||||
"points": [],
|
||||
"pulseWave": {
|
||||
"offCount": 3,
|
||||
"offValue": 1,
|
||||
"onCount": 3,
|
||||
"onValue": 2,
|
||||
"timeStep": 60
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 30,
|
||||
"stream": {
|
||||
"bands": 1,
|
||||
"noise": 2.2,
|
||||
"speed": 250,
|
||||
"spread": 3.5,
|
||||
"type": "signal"
|
||||
},
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Stats",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "gdev-testdata",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-GrYlRd"
|
||||
},
|
||||
"custom": {
|
||||
"align": "center",
|
||||
"displayMode": "color-background",
|
||||
"filterable": false
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "percentage",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "blue",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 70
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "degree"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Field"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.displayMode"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 10,
|
||||
"w": 19,
|
||||
"x": 0,
|
||||
"y": 16
|
||||
},
|
||||
"id": 5,
|
||||
"options": {
|
||||
"displayMode": "lcd",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": ["mean"],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showUnfilled": true
|
||||
},
|
||||
"pluginVersion": "7.4.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvWave": {
|
||||
"timeStep": 60,
|
||||
"valuesCSV": "0,0,2,2,1,1"
|
||||
},
|
||||
"lines": 10,
|
||||
"points": [],
|
||||
"pulseWave": {
|
||||
"offCount": 3,
|
||||
"offValue": 1,
|
||||
"onCount": 3,
|
||||
"onValue": 2,
|
||||
"timeStep": 60
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 15,
|
||||
"stream": {
|
||||
"bands": 1,
|
||||
"noise": 2.2,
|
||||
"speed": 250,
|
||||
"spread": 3.5,
|
||||
"type": "signal"
|
||||
},
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Bar Gauge LCD",
|
||||
"transformations": [],
|
||||
"type": "bargauge"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 26,
|
||||
"style": "dark",
|
||||
"tags": ["gdev", "demo"],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Gradient Color modes",
|
||||
"uid": "inxsweKGz",
|
||||
"version": 17
|
||||
}
|
||||
@@ -399,7 +399,7 @@ The length of time that Grafana will wait for a successful TLS handshake with th
|
||||
|
||||
### expect_continue_timeout_seconds
|
||||
|
||||
The length of time that Grafana will wait for a datasource’s first response headers after fully writing the request headers, if the request has an “Expect: 100-continue” header. A value of `0` will result in the body being sent immediately. Default is `1` second. For more details check the [Transport.ExpectContinueTimeout](https://golang.org/pkg/net/http/#Transport.ExpectContinueTimeout) documentation.
|
||||
The length of time that Grafana will wait for a datasource’s first response headers after fully writing the request headers, if the request has an “Expect: 100-continue” header. A value of `0` will result in the body being sent immediately. Default is `1` second. For more details check the [Transport.ExpectContinueTimeout](https://golang.org/pkg/net/http/#Transport.ExpectContinueTimeout) documentation.
|
||||
|
||||
### max_idle_connections
|
||||
|
||||
@@ -549,9 +549,11 @@ Number dashboard versions to keep (per dashboard). Default: `20`, Minimum: `1`.
|
||||
|
||||
> Only available in Grafana v6.7+.
|
||||
|
||||
This prevents users from setting the dashboard refresh interval of a lower than given interval. Per default this is 5 seconds.
|
||||
This feature prevents users from setting the dashboard refresh interval to a lower value than a given interval value. The default interval value is 5 seconds.
|
||||
The interval string is a possibly signed sequence of decimal numbers, followed by a unit suffix (ms, s, m, h, d), e.g. `30s` or `1m`.
|
||||
|
||||
As of Grafana v7.3, this also limits the refresh interval options in Explore.
|
||||
|
||||
### default_home_dashboard_path
|
||||
|
||||
Path to the default home dashboard. If this value is empty, then Grafana uses StaticRootPath + "dashboards/home.json"
|
||||
@@ -623,7 +625,7 @@ Default is `false`.
|
||||
|
||||
### user_invite_max_lifetime_duration
|
||||
|
||||
The duration in time a user invitation remains valid before expiring.
|
||||
The duration in time a user invitation remains valid before expiring.
|
||||
This setting should be expressed as a duration. Examples: 6h (hours), 2d (days), 1w (week).
|
||||
Default is `24h` (24 hours). The minimum supported duration is `15m` (15 minutes).
|
||||
|
||||
@@ -1073,6 +1075,15 @@ If both are set, then basic authentication is required to access the metrics end
|
||||
|
||||
<hr>
|
||||
|
||||
## [metrics.environment_info]
|
||||
|
||||
Adds dimensions to the `grafana_environment_info` metric, which can expose more information about the Grafana instance.
|
||||
|
||||
```
|
||||
; exampleLabel1 = exampleValue1
|
||||
; exampleLabel2 = exampleValue2
|
||||
```
|
||||
|
||||
## [metrics.graphite]
|
||||
|
||||
Use these options if you want to send internal Grafana metrics to Graphite.
|
||||
|
||||
@@ -14,7 +14,7 @@ weight = 500
|
||||
|
||||
SAML authentication integration allows your Grafana users to log in by using an external SAML 2.0 Identity Provider (IdP). To enable this, Grafana becomes a Service Provider (SP) in the authentication flow, interacting with the IdP to exchange user information.
|
||||
|
||||
The SAML single-sign-on (SSO) standard is varied and flexible. Our implementation contains the subset of features needed to provide a smooth authentication experience into Grafana.
|
||||
The SAML single sign-on (SSO) standard is varied and flexible. Our implementation contains a subset of features needed to provide a smooth authentication experience into Grafana.
|
||||
|
||||
> Only available in Grafana Enterprise v6.3+. If you encounter any problems with our implementation, please don't hesitate to contact us.
|
||||
|
||||
@@ -45,12 +45,14 @@ The table below describes all SAML configuration options. Continue reading below
|
||||
| ----------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `enabled` | No | Whether SAML authentication is allowed | `false` |
|
||||
| `single_logout` | No | Whether SAML Single Logout enabled | `false` |
|
||||
| `allow_idp_initiated` | No | Whether SAML IdP-initiated login is allowed | `false` |
|
||||
| `certificate` or `certificate_path` | Yes | Base64-encoded string or Path for the SP X.509 certificate | |
|
||||
| `private_key` or `private_key_path` | Yes | Base64-encoded string or Path for the SP private key | |
|
||||
| `signature_algorithm` | No | Signature algorithm used for signing requests to the IdP. Supported values are rsa-sha1, rsa-sha256, rsa-sha512. | |
|
||||
| `idp_metadata`, `idp_metadata_path`, or `idp_metadata_url` | Yes | Base64-encoded string, Path or URL for the IdP SAML metadata XML | |
|
||||
| `max_issue_delay` | No | Duration, since the IdP issued a response and the SP is allowed to process it | `90s` |
|
||||
| `metadata_valid_duration` | No | Duration, for how long the SP metadata is valid | `48h` |
|
||||
| `relay_state` | No | Relay state for IdP-initiated login. Should match relay state configured in IdP | |
|
||||
| `assertion_attribute_name` | No | Friendly name or name of the attribute within the SAML assertion to use as the user name | `displayName` |
|
||||
| `assertion_attribute_login` | No | Friendly name or name of the attribute within the SAML assertion to use as the user login handle | `mail` |
|
||||
| `assertion_attribute_email` | No | Friendly name or name of the attribute within the SAML assertion to use as the user email | `mail` |
|
||||
@@ -81,7 +83,9 @@ You can only use one form of each configuration option. Using multiple forms, su
|
||||
|
||||
### Signature algorithm
|
||||
|
||||
The SAML standard recommends using digital signature for some types of messages, like authentication or logout requests. If `signature_algorithm` option configured, Grafana will put digital signature into SAML requests. Supported signature types are `rsa-sha1`, `rsa-sha256`, `rsa-sha512`. This option should match your IdP configuration, otherwise, signature won't be validated by the IdP. Grafana uses key and certificate configured with `private_key` and `certificate` options for signing SAML requests.
|
||||
> Only available in Grafana v7.3+
|
||||
|
||||
The SAML standard recommends using a digital signature for some types of messages, like authentication or logout requests. If the `signature_algorithm` option is configured, Grafana will put a digital signature into SAML requests. Supported signature types are `rsa-sha1`, `rsa-sha256`, `rsa-sha512`. This option should match your IdP configuration, otherwise, signature validation will fail. Grafana uses key and certificate configured with `private_key` and `certificate` options for signing SAML requests.
|
||||
|
||||
### IdP metadata
|
||||
|
||||
@@ -113,9 +117,19 @@ The integration provides two key endpoints as part of Grafana:
|
||||
- The `/saml/metadata` endpoint, which contains the SP metadata. You can either download and upload it manually, or youmake the IdP request it directly from the endpoint. Some providers name it Identifier or Entity ID.
|
||||
- The `/saml/acs` endpoint, which is intended to receive the ACS (Assertion Customer Service) callback. Some providers name it SSO URL or Reply URL.
|
||||
|
||||
### Single Logout
|
||||
### IdP-initiated Single Sign-On (SSO)
|
||||
|
||||
Single Logout feature allows user to log out from all applications associated with current IdP session established via SAML SSO. If `single_logout` option set to `true` and user logs out, Grafana requests IdP to terminate user session. Then IdP triggers logout process for all other applications which user logged in with the same IdP session (application should support single logout). And conversely, if another application connected to the same IdP initiates single logout, Grafana gets logout request from IdP and terminates user session.
|
||||
> Only available in Grafana v7.3+
|
||||
|
||||
By default, Grafana allows only service provider (SP) initiated logins (when the user logs in with SAML via Grafana’s login page). If you want users to log in into Grafana directly from your identity provider (IdP), set the `allow_idp_initiated` configuration option to `true` and configure `relay_state` with the same value specified in the IdP configuration.
|
||||
|
||||
IdP-initiated SSO has some security risks, so make sure you understand the risks before enabling this feature. When using IdP-initiated SSO, Grafana receives unsolicited SAML requests and can't verify that login flow was started by the user. This makes it hard to detect whether SAML message has been stolen or replaced. Because of this, IdP-initiated SSO is vulnerable to login cross-site request forgery (CSRF) and man in the middle (MITM) attacks. We do not recommend using IdP-initiated SSO and keeping it disabled whenever possible.
|
||||
|
||||
### Single logout
|
||||
|
||||
> Only available in Grafana v7.3+
|
||||
|
||||
SAML's single logout feature allows users to log out from all applications associated with the current IdP session established via SAML SSO. If the `single_logout` option is set to `true` and a user logs out, Grafana requests IdP to end the user session which in turn triggers logout from all other applications the user is logged into using the same IdP session (applications should support single logout). Conversely, if another application connected to the same IdP logs out using single logout, Grafana receives a logout request from IdP and ends the user session.
|
||||
|
||||
### Assertion mapping
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ e2e.scenario({
|
||||
e2e.components.DashboardLinks.link()
|
||||
.should('be.visible')
|
||||
.and(links => {
|
||||
expect(links).to.have.length(13);
|
||||
expect(links).to.have.length.greaterThan(13);
|
||||
|
||||
for (let index = 0; index < links.length; index++) {
|
||||
expect(Cypress.$(links[index]).attr('href')).contains(`var-custom=${variableValue}`);
|
||||
|
||||
1
go.mod
1
go.mod
@@ -30,6 +30,7 @@ require (
|
||||
github.com/facebookgo/structtag v0.0.0-20150214074306-217e25fb9691 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/gchaincl/sqlhooks v1.3.0
|
||||
github.com/go-macaron/binding v0.0.0-20190806013118-0b4f37bab25b
|
||||
github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07
|
||||
github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659
|
||||
|
||||
2
go.sum
2
go.sum
@@ -317,6 +317,8 @@ github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03D
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
|
||||
github.com/gchaincl/sqlhooks v1.3.0 h1:yKPXxW9a5CjXaVf2HkQn6wn7TZARvbAOAelr3H8vK2Y=
|
||||
github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUCqXC24ra34=
|
||||
github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
|
||||
github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
|
||||
3
grafana-mixin/.gitignore
vendored
Normal file
3
grafana-mixin/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
alerts.yaml
|
||||
rules.yaml
|
||||
dashboards_out
|
||||
13
grafana-mixin/Makefile
Normal file
13
grafana-mixin/Makefile
Normal file
@@ -0,0 +1,13 @@
|
||||
all: fmt lint build clean
|
||||
|
||||
fmt:
|
||||
./scripts/format.sh
|
||||
|
||||
lint:
|
||||
./scripts/lint.sh
|
||||
|
||||
build:
|
||||
./scripts/build.sh
|
||||
|
||||
clean:
|
||||
rm -rf dashboards_out alerts.yaml rules.yaml
|
||||
28
grafana-mixin/README.md
Normal file
28
grafana-mixin/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Grafana Mixin
|
||||
|
||||
_This is a work in progress. We aim for it to become a good role model for alerts
|
||||
and dashboards eventually, but it is not quite there yet._
|
||||
|
||||
The Grafana Mixin is a set of configurable, reusable, and extensible alerts and
|
||||
dashboards based on the metrics exported by Grafana. The mixin creates
|
||||
recording and alerting rules for Prometheus and suitable dashboard descriptions
|
||||
for Grafana.
|
||||
|
||||
To use them, you need to have `mixtool` and `jsonnetfmt` installed. If you
|
||||
have a working Go development environment, it's easiest to run the following:
|
||||
|
||||
```bash
|
||||
$ go get github.com/monitoring-mixins/mixtool/cmd/mixtool
|
||||
$ go get github.com/google/go-jsonnet/cmd/jsonnetfmt
|
||||
```
|
||||
|
||||
You can then build the Prometheus rules files `alerts.yaml` and
|
||||
`rules.yaml` and a directory `dashboard_out` with the JSON dashboard files
|
||||
for Grafana:
|
||||
|
||||
```bash
|
||||
$ make build
|
||||
```
|
||||
|
||||
For more advanced uses of mixins, see
|
||||
https://github.com/monitoring-mixins/docs.
|
||||
14
grafana-mixin/alerts/alerts.yaml
Normal file
14
grafana-mixin/alerts/alerts.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
groups:
|
||||
- name: GrafanaAlerts
|
||||
rules:
|
||||
- alert: GrafanaRequestsFailing
|
||||
for: 5m
|
||||
expr: |
|
||||
100 * namespace_job_handler_statuscode:http_request_total:rate5m{handler!~"/datasources/proxy/:id.*|/ds/query|/tsdb/query", statuscode=~"5.."}
|
||||
/
|
||||
namespace_job_handler_statuscode:http_request_total:rate5m{handler!~"/datasources/proxy/:id.*|/ds/query|/tsdb/query"}
|
||||
> 0.5
|
||||
labels:
|
||||
severity: 'critical'
|
||||
annotations:
|
||||
message: "'{{ $labels.namespace }}' / '{{ $labels.job }}' / '{{ $labels.handler }}' is experiencing {{ $value | humanize }}% errors"
|
||||
528
grafana-mixin/dashboards/grafana-overview.json
Normal file
528
grafana-mixin/dashboards/grafana-overview.json
Normal file
@@ -0,0 +1,528 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": "-- Grafana --",
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": 35,
|
||||
"iteration": 1602761142538,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"noValue": "0",
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 6,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"mean"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "grafana_alerting_result_total{job=~\"$job\", instance=~\"$instance\", state=\"alerting\"}",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Firing Alerts",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 0
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"mean"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
}
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum(grafana_stat_totals_dashboard{job=~\"$job\", instance=~\"$instance\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Dashboards",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"align": null
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"showHeader": true
|
||||
},
|
||||
"pluginVersion": "7.0.4",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "grafana_build_info{job=~\"$job\", instance=~\"$instance\"}",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Build Info",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "labelsToFields",
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"Time": true,
|
||||
"Value": true,
|
||||
"branch": true,
|
||||
"container": true,
|
||||
"goversion": true,
|
||||
"namespace": true,
|
||||
"pod": true,
|
||||
"revision": true
|
||||
},
|
||||
"indexByName": {
|
||||
"Time": 7,
|
||||
"Value": 11,
|
||||
"branch": 4,
|
||||
"container": 8,
|
||||
"edition": 2,
|
||||
"goversion": 6,
|
||||
"instance": 1,
|
||||
"job": 0,
|
||||
"namespace": 9,
|
||||
"pod": 10,
|
||||
"revision": 5,
|
||||
"version": 3
|
||||
},
|
||||
"renameByName": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 5
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 2,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "sum by (statuscode) (irate(http_request_total{job=~\"$job\", instance=~\"$instance\"}[1m])) ",
|
||||
"interval": "",
|
||||
"legendFormat": "{{statuscode}}",
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "RPS",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:157",
|
||||
"format": "reqps",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:158",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"dashLength": 10,
|
||||
"dashes": false,
|
||||
"datasource": "$datasource",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"fill": 1,
|
||||
"fillGradient": 0,
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 5
|
||||
},
|
||||
"hiddenSeries": false,
|
||||
"id": 4,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"nullPointMode": "null",
|
||||
"options": {
|
||||
"dataLinks": []
|
||||
},
|
||||
"percentage": false,
|
||||
"pointradius": 2,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"spaceLength": 10,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "max(http_request_duration_milliseconds{job=~\"$job\", instance=~\"$instance\", quantile=\"0.99\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "max-99th",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"expr": "max(http_request_duration_milliseconds{job=~\"$job\", instance=~\"$instance\", quantile=\"0.9\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "max-90th",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"expr": "sum(irate(http_request_duration_milliseconds_sum{job=~\"$job\", instance=~\"$instance\"}[$__interval])) / sum(irate(http_request_duration_milliseconds_count{job=~\"$job\", instance=~\"$instance\"}[$__interval])) ",
|
||||
"interval": "",
|
||||
"legendFormat": "avg",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeRegions": [],
|
||||
"timeShift": null,
|
||||
"title": "Request Latency",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"buckets": null,
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"$$hashKey": "object:210",
|
||||
"format": "ms",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"$$hashKey": "object:211",
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"yaxis": {
|
||||
"align": false,
|
||||
"alignLevel": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"schemaVersion": 25,
|
||||
"style": "dark",
|
||||
"tags": [],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "prometheus",
|
||||
"value": "prometheus"
|
||||
},
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": null,
|
||||
"multi": false,
|
||||
"name": "datasource",
|
||||
"options": [],
|
||||
"query": "prometheus",
|
||||
"queryValue": "",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"type": "datasource"
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {
|
||||
"selected": true,
|
||||
"tags": [],
|
||||
"text": "All",
|
||||
"value": [
|
||||
"$__all"
|
||||
]
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"definition": "label_values(grafana_build_info, job)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": null,
|
||||
"multi": true,
|
||||
"name": "job",
|
||||
"options": [],
|
||||
"query": "label_values(grafana_build_info, job)",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "All",
|
||||
"value": "$__all"
|
||||
},
|
||||
"datasource": "$datasource",
|
||||
"definition": "label_values(grafana_build_info, instance)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": null,
|
||||
"multi": true,
|
||||
"name": "instance",
|
||||
"options": [],
|
||||
"query": "label_values(grafana_build_info, instance)",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"tagValuesQuery": "",
|
||||
"tags": [],
|
||||
"tagsQuery": "",
|
||||
"type": "query",
|
||||
"useTags": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
]
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "Grafana Overview",
|
||||
"uid": "6be0s85Mk",
|
||||
"version": 4
|
||||
}
|
||||
15
grafana-mixin/mixin.libsonnet
Normal file
15
grafana-mixin/mixin.libsonnet
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
grafanaDashboards: {
|
||||
'grafana-overview.json': (import 'dashboards/grafana-overview.json'),
|
||||
},
|
||||
|
||||
// Helper function to ensure that we don't override other rules, by forcing
|
||||
// the patching of the groups list, and not the overall rules object.
|
||||
local importRules(rules) = {
|
||||
groups+: std.native('parseYaml')(rules)[0].groups,
|
||||
},
|
||||
|
||||
prometheusRules+: importRules(importstr 'rules/rules.yaml'),
|
||||
|
||||
prometheusAlerts+: importRules(importstr 'alerts/alerts.yaml'),
|
||||
}
|
||||
7
grafana-mixin/rules/rules.yaml
Normal file
7
grafana-mixin/rules/rules.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
groups:
|
||||
- name: grafana_rules
|
||||
rules:
|
||||
# Record error rate of http requests excluding dataproxy, /ds/query and /tsdb/query requests
|
||||
- record: namespace_job_handler_statuscode:http_request_total:rate5m
|
||||
expr: |
|
||||
sum by (namespace, job, handler, statuscode) (rate(http_request_total[5m]))
|
||||
6
grafana-mixin/scripts/build.sh
Executable file
6
grafana-mixin/scripts/build.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
mixtool generate all mixin.libsonnet
|
||||
1
grafana-mixin/scripts/common.sh
Normal file
1
grafana-mixin/scripts/common.sh
Normal file
@@ -0,0 +1 @@
|
||||
JSONNET_FMT="jsonnetfmt -n 2 --max-blank-lines 2 --string-style s --comment-style s"
|
||||
9
grafana-mixin/scripts/format.sh
Executable file
9
grafana-mixin/scripts/format.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
. scripts/common.sh
|
||||
|
||||
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
|
||||
xargs -n 1 -- ${JSONNET_FMT} -i
|
||||
13
grafana-mixin/scripts/lint.sh
Executable file
13
grafana-mixin/scripts/lint.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
. scripts/common.sh
|
||||
|
||||
find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | \
|
||||
while read f; do \
|
||||
${JSONNET_FMT} "$f" | diff -u "$f" -; \
|
||||
done
|
||||
|
||||
mixtool lint mixin.libsonnet
|
||||
@@ -2,5 +2,5 @@
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"version": "7.3.0-pre.0"
|
||||
"version": "7.3.0-beta.2"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "7.3.0-pre",
|
||||
"version": "7.3.0-beta2",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||
@@ -45,8 +45,8 @@
|
||||
"ci:test-frontend": "yarn run prettier:check && yarn run packages:typecheck && yarn run typecheck && yarn run test"
|
||||
},
|
||||
"grafana": {
|
||||
"whatsNewUrl": "https://grafana.com/docs/grafana/latest/guides/whats-new-in-v7-2/",
|
||||
"releaseNotesUrl": "https://community.grafana.com/t/release-notes-v7-2-x/36321"
|
||||
"whatsNewUrl": "https://grafana.com/docs/grafana/latest/guides/whats-new-in-v7-3/",
|
||||
"releaseNotesUrl": "https://community.grafana.com/t/release-notes-v7-3-x/37993"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@@ -269,6 +269,7 @@
|
||||
"react-loadable": "5.5.0",
|
||||
"react-popper": "1.3.3",
|
||||
"react-redux": "7.2.0",
|
||||
"react-reverse-portal": "^2.0.1",
|
||||
"react-sizeme": "2.6.12",
|
||||
"react-split-pane": "0.1.89",
|
||||
"react-transition-group": "4.3.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.0-beta.2",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
@@ -3,13 +3,14 @@ import _ from 'lodash';
|
||||
|
||||
// Types
|
||||
import { Field, FieldType } from '../types/dataFrame';
|
||||
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
|
||||
import { GrafanaTheme } from '../types/theme';
|
||||
import { DecimalCount, DecimalInfo, DisplayProcessor, DisplayValue } from '../types/displayValue';
|
||||
import { getValueFormat } from '../valueFormats/valueFormats';
|
||||
import { getMappedValue } from '../utils/valueMappings';
|
||||
import { dateTime } from '../datetime';
|
||||
import { KeyValue, TimeZone } from '../types';
|
||||
import { getScaleCalculator } from './scale';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
interface DisplayProcessorOptions {
|
||||
field: Partial<Field>;
|
||||
@@ -41,7 +42,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
const config = field.config ?? {};
|
||||
|
||||
// Theme should be required or we need access to default theme instance from here
|
||||
const theme = options.theme ?? ({ type: GrafanaThemeType.Dark } as GrafanaTheme);
|
||||
const theme = options.theme ?? getTestTheme();
|
||||
|
||||
let unit = config.unit;
|
||||
let hasDateUnit = unit && (timeFormats[unit] || unit.startsWith('time:'));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Field, GrafanaThemeType, GrafanaTheme, FieldColorModeId } from '../types';
|
||||
import { Field, FieldColorModeId } from '../types';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
import { fieldColorModeRegistry, FieldValueColorCalculator } from './fieldColor';
|
||||
|
||||
describe('fieldColorModeRegistry', () => {
|
||||
@@ -9,10 +10,7 @@ describe('fieldColorModeRegistry', () => {
|
||||
|
||||
function getCalculator(options: GetCalcOptions): FieldValueColorCalculator {
|
||||
const mode = fieldColorModeRegistry.get(options.mode);
|
||||
return mode.getCalculator(
|
||||
{ state: { seriesIndex: options.seriesIndex } } as Field,
|
||||
{ type: GrafanaThemeType.Dark } as GrafanaTheme
|
||||
);
|
||||
return mode.getCalculator({ state: { seriesIndex: options.seriesIndex } } as Field, getTestTheme());
|
||||
}
|
||||
|
||||
it('Schemes should interpolate', () => {
|
||||
|
||||
@@ -54,20 +54,74 @@ export const fieldColorModeRegistry = new Registry<FieldColorMode>(() => {
|
||||
// }),
|
||||
new FieldColorSchemeMode({
|
||||
id: FieldColorModeId.PaletteClassic,
|
||||
name: 'By series / Classic palette',
|
||||
//description: 'Assigns color based on series or field index',
|
||||
name: 'Classic palette',
|
||||
isContinuous: false,
|
||||
isByValue: false,
|
||||
colors: classicColors,
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-GrYlRd',
|
||||
name: 'By value / Green Yellow Red (gradient)',
|
||||
//description: 'Interpolated colors based value, min and max',
|
||||
name: 'Green-Yellow-Red',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['green', 'yellow', 'red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-BlYlRd',
|
||||
name: 'Blue-Yellow-Red',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['dark-blue', 'super-light-yellow', 'dark-red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-YlRd',
|
||||
name: 'Yellow-Red',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['super-light-yellow', 'dark-red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-BlPu',
|
||||
name: 'Blue-Purple',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['blue', 'purple'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-YlBl',
|
||||
name: 'Yellow-Blue',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['super-light-yellow', 'dark-blue'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-blues',
|
||||
name: 'Blues',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-blue'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-reds',
|
||||
name: 'Reds',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-red'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-greens',
|
||||
name: 'Greens',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-green'],
|
||||
}),
|
||||
new FieldColorSchemeMode({
|
||||
id: 'continuous-purples',
|
||||
name: 'Purples',
|
||||
isContinuous: true,
|
||||
isByValue: true,
|
||||
colors: ['panel-bg', 'dark-purple'],
|
||||
}),
|
||||
];
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import merge from 'lodash/merge';
|
||||
import { getFieldDisplayValues, GetFieldDisplayValuesOptions } from './fieldDisplay';
|
||||
import { toDataFrame } from '../dataframe/processDataFrame';
|
||||
import { ReducerID } from '../transformations/fieldReducer';
|
||||
import { GrafanaTheme } from '../types/theme';
|
||||
import { MappingType } from '../types';
|
||||
import { standardFieldConfigEditorRegistry } from './standardFieldConfigEditorRegistry';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
describe('FieldDisplay', () => {
|
||||
beforeAll(() => {
|
||||
@@ -241,7 +241,7 @@ function createDisplayOptions(extend: Partial<GetFieldDisplayValuesOptions> = {}
|
||||
overrides: [],
|
||||
defaults: {},
|
||||
},
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
};
|
||||
|
||||
return merge<GetFieldDisplayValuesOptions, any>(options, extend);
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
FieldConfigPropertyItem,
|
||||
FieldConfigSource,
|
||||
FieldType,
|
||||
GrafanaTheme,
|
||||
InterpolateFunction,
|
||||
ThresholdsMode,
|
||||
FieldColorModeId,
|
||||
@@ -28,6 +27,7 @@ import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
|
||||
import { getFieldDisplayName } from './fieldState';
|
||||
import { ArrayVector } from '../vector';
|
||||
import { getDisplayProcessor } from './displayProcessor';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
const property1: any = {
|
||||
id: 'custom.property1', // Match field properties
|
||||
@@ -136,7 +136,7 @@ describe('applyFieldOverrides', () => {
|
||||
},
|
||||
replaceVariables: (value: any) => value,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
fieldConfigRegistry: new FieldConfigOptionsRegistry(),
|
||||
});
|
||||
|
||||
@@ -199,7 +199,7 @@ describe('applyFieldOverrides', () => {
|
||||
fieldConfigRegistry: customFieldRegistry,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
replaceVariables: v => v,
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
})[0];
|
||||
|
||||
const outField = processed.fields[0];
|
||||
@@ -216,7 +216,7 @@ describe('applyFieldOverrides', () => {
|
||||
fieldConfig: src as FieldConfigSource, // defaults + overrides
|
||||
replaceVariables: (undefined as any) as InterpolateFunction,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: (undefined as any) as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
fieldConfigRegistry: customFieldRegistry,
|
||||
})[0];
|
||||
const valueColumn = data.fields[1];
|
||||
@@ -244,7 +244,7 @@ describe('applyFieldOverrides', () => {
|
||||
fieldConfig: src as FieldConfigSource, // defaults + overrides
|
||||
replaceVariables: (undefined as any) as InterpolateFunction,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: (undefined as any) as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
autoMinMax: true,
|
||||
})[0];
|
||||
const valueColumn = data.fields[1];
|
||||
@@ -268,7 +268,7 @@ describe('applyFieldOverrides', () => {
|
||||
return value;
|
||||
}) as InterpolateFunction,
|
||||
getDataSourceSettingsByUid: undefined as any,
|
||||
theme: (undefined as any) as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
autoMinMax: true,
|
||||
fieldConfigRegistry: customFieldRegistry,
|
||||
})[0];
|
||||
@@ -521,7 +521,7 @@ describe('getLinksSupplier', () => {
|
||||
// this is used only for internal links so isn't needed here
|
||||
() => ({} as any),
|
||||
{
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
}
|
||||
);
|
||||
supplier({});
|
||||
@@ -568,7 +568,7 @@ describe('getLinksSupplier', () => {
|
||||
// We do not need to interpolate anything for this test
|
||||
(value, vars, format) => value,
|
||||
uid => ({ name: 'testDS' } as any),
|
||||
{ theme: {} as GrafanaTheme }
|
||||
{ theme: getTestTheme() }
|
||||
);
|
||||
const links = supplier({ valueRowIndex: 0 });
|
||||
expect(links.length).toBe(1);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getFieldDisplayValuesProxy } from './getFieldDisplayValuesProxy';
|
||||
import { applyFieldOverrides } from './fieldOverrides';
|
||||
import { toDataFrame } from '../dataframe';
|
||||
import { GrafanaTheme } from '../types';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
describe('getFieldDisplayValuesProxy', () => {
|
||||
const data = applyFieldOverrides({
|
||||
@@ -30,7 +31,7 @@ describe('getFieldDisplayValuesProxy', () => {
|
||||
replaceVariables: (val: string) => val,
|
||||
getDataSourceSettingsByUid: (val: string) => ({} as any),
|
||||
timeZone: 'utc',
|
||||
theme: {} as GrafanaTheme,
|
||||
theme: getTestTheme(),
|
||||
autoMinMax: true,
|
||||
})[0];
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ThresholdsMode, Field, FieldType, GrafanaThemeType, GrafanaTheme } from '../types';
|
||||
import { ThresholdsMode, Field, FieldType } from '../types';
|
||||
import { sortThresholds } from './thresholds';
|
||||
import { ArrayVector } from '../vector/ArrayVector';
|
||||
import { getScaleCalculator } from './scale';
|
||||
import { getTestTheme } from '../utils/testdata/testTheme';
|
||||
|
||||
describe('getScaleCalculator', () => {
|
||||
it('should return percent, threshold and color', () => {
|
||||
@@ -18,7 +19,7 @@ describe('getScaleCalculator', () => {
|
||||
values: new ArrayVector([0, 50, 100]),
|
||||
};
|
||||
|
||||
const calc = getScaleCalculator(field, { type: GrafanaThemeType.Dark } as GrafanaTheme);
|
||||
const calc = getScaleCalculator(field, getTestTheme());
|
||||
expect(calc(70)).toEqual({
|
||||
percent: 0.7,
|
||||
threshold: thresholds[1],
|
||||
|
||||
@@ -2,7 +2,7 @@ import { map } from 'rxjs/operators';
|
||||
|
||||
import { DataTransformerID } from './ids';
|
||||
import { DataTransformerInfo } from '../../types/transformations';
|
||||
import { DataFrame, Field } from '../../types/dataFrame';
|
||||
import { DataFrame, Field, TIME_SERIES_VALUE_FIELD_NAME } from '../../types/dataFrame';
|
||||
import { ArrayVector } from '../../vector';
|
||||
|
||||
export enum ConcatenateFrameNameMode {
|
||||
@@ -73,7 +73,7 @@ export function concatenateFields(data: DataFrame[], opts: ConcatenateTransforme
|
||||
} else if (opts.frameNameMode === ConcatenateFrameNameMode.Label) {
|
||||
copy.labels = { ...f.labels };
|
||||
copy.labels[frameNameLabel] = frame.name;
|
||||
} else if (!copy.name || copy.name === 'Value') {
|
||||
} else if (!copy.name || copy.name === TIME_SERIES_VALUE_FIELD_NAME) {
|
||||
copy.name = frame.name;
|
||||
} else {
|
||||
copy.name = `${frame.name} · ${f.name}`;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { SelectableValue } from './select';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
@@ -17,7 +16,7 @@ export enum LiveChannelScope {
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface LiveChannelConfig<TMessage = any> {
|
||||
export interface LiveChannelConfig<TMessage = any, TController = any> {
|
||||
/**
|
||||
* The path definition. either static, or it may contain variables identifed with {varname}
|
||||
*/
|
||||
@@ -28,11 +27,6 @@ export interface LiveChannelConfig<TMessage = any> {
|
||||
*/
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* When variables exist, this list will identify each one
|
||||
*/
|
||||
variables?: Array<SelectableValue<string>>;
|
||||
|
||||
/**
|
||||
* The channel keeps track of who else is connected to the same channel
|
||||
*/
|
||||
@@ -46,6 +40,9 @@ export interface LiveChannelConfig<TMessage = any> {
|
||||
|
||||
/** convert the raw stream message into a message that should be broadcast */
|
||||
processMessage?: (msg: any) => TMessage;
|
||||
|
||||
/** some channels are managed by an explicit interface */
|
||||
getController?: () => TController;
|
||||
}
|
||||
|
||||
export enum LiveChannelConnectionState {
|
||||
|
||||
@@ -27,10 +27,12 @@ export enum LogLevel {
|
||||
unknown = 'unknown',
|
||||
}
|
||||
|
||||
// Used for meta information such as common labels or returned log rows in logs view in Explore
|
||||
export enum LogsMetaKind {
|
||||
Number,
|
||||
String,
|
||||
LabelsMap,
|
||||
Error,
|
||||
}
|
||||
|
||||
export enum LogsSortOrder {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
calculateStats,
|
||||
getLogLevelFromKey,
|
||||
sortLogsResult,
|
||||
checkLogsError,
|
||||
} from './logs';
|
||||
|
||||
describe('getLoglevel()', () => {
|
||||
@@ -352,3 +353,15 @@ describe('sortLogsResult', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkLogsError()', () => {
|
||||
const log = ({
|
||||
labels: {
|
||||
__error__: 'Error Message',
|
||||
foo: 'boo',
|
||||
},
|
||||
} as any) as LogRowModel;
|
||||
test('should return correct error if error is present', () => {
|
||||
expect(checkLogsError(log)).toStrictEqual({ hasError: true, errorMessage: 'Error Message' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -210,3 +210,16 @@ export const sortLogsResult = (logsResult: LogsModel | null, sortOrder: LogsSort
|
||||
|
||||
export const sortLogRows = (logRows: LogRowModel[], sortOrder: LogsSortOrder) =>
|
||||
sortOrder === LogsSortOrder.Ascending ? logRows.sort(sortInAscendingOrder) : logRows.sort(sortInDescendingOrder);
|
||||
|
||||
// Currently supports only error condition in Loki logs
|
||||
export const checkLogsError = (logRow: LogRowModel): { hasError: boolean; errorMessage?: string } => {
|
||||
if (logRow.labels.__error__) {
|
||||
return {
|
||||
hasError: true,
|
||||
errorMessage: logRow.labels.__error__,
|
||||
};
|
||||
}
|
||||
return {
|
||||
hasError: false,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -34,7 +34,8 @@ export type Color =
|
||||
| 'dark-purple'
|
||||
| 'semi-dark-purple'
|
||||
| 'light-purple'
|
||||
| 'super-light-purple';
|
||||
| 'super-light-purple'
|
||||
| 'panel-bg';
|
||||
|
||||
type ThemeVariants = {
|
||||
dark: string;
|
||||
@@ -82,6 +83,8 @@ export function buildColorsMapForTheme(theme: GrafanaTheme): Record<Color, strin
|
||||
}
|
||||
}
|
||||
|
||||
colorsMap['panel-bg'] = theme.colors.panelBg;
|
||||
|
||||
return colorsMap;
|
||||
}
|
||||
|
||||
@@ -118,7 +121,25 @@ export function getColorForTheme(color: string, theme: GrafanaTheme): string {
|
||||
export function getColorFromHexRgbOrName(color: string, type?: GrafanaThemeType): string {
|
||||
const themeType = type ?? GrafanaThemeType.Dark;
|
||||
|
||||
return getColorForTheme(color, ({ type: themeType } as unknown) as GrafanaTheme);
|
||||
if (themeType === GrafanaThemeType.Dark) {
|
||||
const darkTheme = ({
|
||||
type: themeType,
|
||||
colors: {
|
||||
panelBg: '#141619',
|
||||
},
|
||||
} as unknown) as GrafanaTheme;
|
||||
|
||||
return getColorForTheme(color, darkTheme);
|
||||
}
|
||||
|
||||
const lightTheme = ({
|
||||
type: themeType,
|
||||
colors: {
|
||||
panelBg: '#000000',
|
||||
},
|
||||
} as unknown) as GrafanaTheme;
|
||||
|
||||
return getColorForTheme(color, lightTheme);
|
||||
}
|
||||
|
||||
const buildNamedColorsPalette = () => {
|
||||
|
||||
12
packages/grafana-data/src/utils/testdata/testTheme.ts
vendored
Normal file
12
packages/grafana-data/src/utils/testdata/testTheme.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import { GrafanaTheme, GrafanaThemeType } from '../../types/theme';
|
||||
|
||||
export function getTestTheme(type: GrafanaThemeType = GrafanaThemeType.Dark): GrafanaTheme {
|
||||
return ({
|
||||
type,
|
||||
isDark: type === GrafanaThemeType.Dark,
|
||||
isLight: type === GrafanaThemeType.Light,
|
||||
colors: {
|
||||
panelBg: 'white',
|
||||
},
|
||||
} as unknown) as GrafanaTheme;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.0-beta.2",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.0-beta.2",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -44,7 +44,7 @@
|
||||
"types": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@cypress/webpack-preprocessor": "4.1.3",
|
||||
"@grafana/e2e-selectors": "7.3.0-pre.0",
|
||||
"@grafana/e2e-selectors": "7.3.0-beta.2",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@mochajs/json-file-reporter": "^1.2.0",
|
||||
"blink-diff": "1.0.13",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.0-beta.2",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -22,8 +22,8 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "7.3.0-pre.0",
|
||||
"@grafana/ui": "7.3.0-pre.0",
|
||||
"@grafana/data": "7.3.0-beta.2",
|
||||
"@grafana/ui": "7.3.0-beta.2",
|
||||
"systemjs": "0.20.19",
|
||||
"systemjs-plugin-css": "0.1.37"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
export * from './services';
|
||||
export * from './config';
|
||||
export * from './types';
|
||||
export * from './measurement';
|
||||
export { loadPluginCss, SystemJS, PluginCssOptions } from './utils/plugin';
|
||||
export { reportMetaAnalytics } from './utils/analytics';
|
||||
export { DataSourceWithBackend, HealthCheckResult, HealthStatus } from './utils/DataSourceWithBackend';
|
||||
|
||||
250
packages/grafana-runtime/src/measurement/collector.test.ts
Normal file
250
packages/grafana-runtime/src/measurement/collector.test.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { MeasurementCollector } from './collector';
|
||||
import { MeasurementAction } from './types';
|
||||
|
||||
describe('MeasurementCollector', () => {
|
||||
it('should collect values', () => {
|
||||
const collector = new MeasurementCollector();
|
||||
collector.addBatch({
|
||||
measurements: [
|
||||
{
|
||||
name: 'test',
|
||||
labels: { host: 'a' },
|
||||
time: 100,
|
||||
values: {
|
||||
f0: 0,
|
||||
f1: 1,
|
||||
f2: 'hello',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
labels: { host: 'b' },
|
||||
time: 101,
|
||||
values: {
|
||||
f0: 0,
|
||||
f1: 1,
|
||||
f2: 'hello',
|
||||
},
|
||||
config: {
|
||||
f2: {
|
||||
unit: 'mph',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'test',
|
||||
time: 102,
|
||||
labels: { host: 'a' }, // should append to first value
|
||||
values: {
|
||||
// note the missing values for f0/1
|
||||
f2: 'world',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const frames = collector.getData();
|
||||
expect(frames.length).toEqual(2);
|
||||
expect(frames[0]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": undefined,
|
||||
"name": "time",
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
100,
|
||||
102,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f0",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
0,
|
||||
null,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f1",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
1,
|
||||
null,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f2",
|
||||
"type": "string",
|
||||
"values": Array [
|
||||
"hello",
|
||||
"world",
|
||||
],
|
||||
},
|
||||
],
|
||||
"meta": Object {
|
||||
"custom": Object {
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
"name": "test",
|
||||
"refId": undefined,
|
||||
}
|
||||
`);
|
||||
expect(frames[1]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": undefined,
|
||||
"name": "time",
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
101,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "b",
|
||||
},
|
||||
"name": "f0",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
0,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "b",
|
||||
},
|
||||
"name": "f1",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
1,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {
|
||||
"unit": "mph",
|
||||
},
|
||||
"labels": Object {
|
||||
"host": "b",
|
||||
},
|
||||
"name": "f2",
|
||||
"type": "string",
|
||||
"values": Array [
|
||||
"hello",
|
||||
],
|
||||
},
|
||||
],
|
||||
"meta": Object {
|
||||
"custom": Object {
|
||||
"labels": Object {
|
||||
"host": "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
"name": "test",
|
||||
"refId": undefined,
|
||||
}
|
||||
`);
|
||||
|
||||
collector.addBatch({
|
||||
action: MeasurementAction.Replace,
|
||||
measurements: [
|
||||
{
|
||||
name: 'test',
|
||||
time: 105,
|
||||
labels: { host: 'a' },
|
||||
values: {
|
||||
f1: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const frames2 = collector.getData();
|
||||
expect(frames2.length).toEqual(2);
|
||||
expect(frames2[0].length).toEqual(1); // not three!
|
||||
expect(frames2[0]).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": undefined,
|
||||
"name": "time",
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
105,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f0",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
null,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f1",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
10,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
"name": "f2",
|
||||
"type": "string",
|
||||
"values": Array [
|
||||
null,
|
||||
],
|
||||
},
|
||||
],
|
||||
"meta": Object {
|
||||
"custom": Object {
|
||||
"labels": Object {
|
||||
"host": "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
"name": "test",
|
||||
"refId": undefined,
|
||||
}
|
||||
`);
|
||||
|
||||
collector.addBatch({
|
||||
action: MeasurementAction.Clear,
|
||||
measurements: [],
|
||||
});
|
||||
expect(collector.getData().length).toEqual(0);
|
||||
});
|
||||
});
|
||||
209
packages/grafana-runtime/src/measurement/collector.ts
Normal file
209
packages/grafana-runtime/src/measurement/collector.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
import {
|
||||
CircularDataFrame,
|
||||
Labels,
|
||||
formatLabels,
|
||||
FieldType,
|
||||
DataFrame,
|
||||
matchAllLabels,
|
||||
parseLabels,
|
||||
CircularVector,
|
||||
ArrayVector,
|
||||
} from '@grafana/data';
|
||||
import { Measurement, MeasurementBatch, LiveMeasurements, MeasurementsQuery, MeasurementAction } from './types';
|
||||
|
||||
interface MeasurementCacheConfig {
|
||||
append?: 'head' | 'tail';
|
||||
capacity?: number;
|
||||
}
|
||||
|
||||
/** This is a cache scoped to a the measurement name
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export class MeasurementCache {
|
||||
readonly frames: Record<string, CircularDataFrame> = {}; // key is the labels
|
||||
|
||||
constructor(public name: string, private config: MeasurementCacheConfig) {
|
||||
if (!this.config) {
|
||||
this.config = {
|
||||
append: 'tail',
|
||||
capacity: 600, // Default capacity 10min @ 1hz
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getFrames(match?: Labels): DataFrame[] {
|
||||
const frames = Object.values(this.frames);
|
||||
if (!match) {
|
||||
return frames;
|
||||
}
|
||||
return frames.filter(f => {
|
||||
return matchAllLabels(match, f.meta?.custom?.labels);
|
||||
});
|
||||
}
|
||||
|
||||
addMeasurement(m: Measurement, action: MeasurementAction): DataFrame {
|
||||
const key = m.labels ? formatLabels(m.labels) : '';
|
||||
let frame = this.frames[key];
|
||||
if (!frame) {
|
||||
frame = new CircularDataFrame(this.config);
|
||||
frame.name = this.name;
|
||||
frame.addField({
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
});
|
||||
for (const [key, value] of Object.entries(m.values)) {
|
||||
frame.addFieldFor(value, key).labels = m.labels;
|
||||
}
|
||||
frame.meta = {
|
||||
custom: {
|
||||
labels: m.labels,
|
||||
},
|
||||
};
|
||||
this.frames[key] = frame;
|
||||
}
|
||||
|
||||
// Clear existing values
|
||||
if (action === MeasurementAction.Replace) {
|
||||
for (const field of frame.fields) {
|
||||
(field.values as ArrayVector).buffer.length = 0; // same buffer, but reset to empty length
|
||||
}
|
||||
}
|
||||
|
||||
// Add the timestamp
|
||||
frame.values['time'].add(m.time || Date.now());
|
||||
|
||||
// Attach field config to the current fields
|
||||
if (m.config) {
|
||||
for (const [key, value] of Object.entries(m.config)) {
|
||||
const f = frame.fields.find(f => f.name === key);
|
||||
if (f) {
|
||||
f.config = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append all values (a row)
|
||||
for (const [key, value] of Object.entries(m.values)) {
|
||||
let v = frame.values[key];
|
||||
if (!v) {
|
||||
const f = frame.addFieldFor(value, key);
|
||||
f.labels = m.labels;
|
||||
v = f.values;
|
||||
}
|
||||
v.add(value);
|
||||
}
|
||||
|
||||
// Make sure all fields have the same length
|
||||
frame.validate();
|
||||
return frame;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export class MeasurementCollector implements LiveMeasurements {
|
||||
measurements = new Map<string, MeasurementCache>();
|
||||
config: MeasurementCacheConfig = {
|
||||
append: 'tail',
|
||||
capacity: 600, // Default capacity 10min @ 1hz
|
||||
};
|
||||
|
||||
//------------------------------------------------------
|
||||
// Public
|
||||
//------------------------------------------------------
|
||||
|
||||
getData(query?: MeasurementsQuery): DataFrame[] {
|
||||
const { name, labels, fields } = query || {};
|
||||
|
||||
let data: DataFrame[] = [];
|
||||
if (name) {
|
||||
// for now we only match exact names
|
||||
const m = this.measurements.get(name);
|
||||
if (m) {
|
||||
data = m.getFrames(labels);
|
||||
}
|
||||
} else {
|
||||
for (const f of this.measurements.values()) {
|
||||
data.push.apply(data, f.getFrames(labels));
|
||||
}
|
||||
}
|
||||
|
||||
if (fields && fields.length) {
|
||||
let filtered: DataFrame[] = [];
|
||||
for (const frame of data) {
|
||||
const match = frame.fields.filter(f => fields.includes(f.name));
|
||||
if (match.length > 0) {
|
||||
filtered.push({ ...frame, fields: match }); // Copy the frame with fewer fields
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
getDistinctNames(): string[] {
|
||||
return Object.keys(this.measurements);
|
||||
}
|
||||
|
||||
getDistinctLabels(name: string): Labels[] {
|
||||
const m = this.measurements.get(name);
|
||||
if (m) {
|
||||
return Object.keys(m.frames).map(k => parseLabels(k));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
setCapacity(size: number) {
|
||||
this.config.capacity = size;
|
||||
|
||||
// Now update all the circular buffers
|
||||
for (const wrap of this.measurements.values()) {
|
||||
for (const frame of Object.values(wrap.frames)) {
|
||||
for (const field of frame.fields) {
|
||||
(field.values as CircularVector).setCapacity(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCapacity() {
|
||||
return this.config.capacity!;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.measurements.clear();
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
// Collector
|
||||
//------------------------------------------------------
|
||||
|
||||
addBatch = (batch: MeasurementBatch) => {
|
||||
let action = batch.action ?? MeasurementAction.Append;
|
||||
if (action === MeasurementAction.Clear) {
|
||||
this.measurements.clear();
|
||||
action = MeasurementAction.Append;
|
||||
}
|
||||
|
||||
// Change the local buffer size
|
||||
if (batch.capacity && batch.capacity !== this.config.capacity) {
|
||||
this.setCapacity(batch.capacity);
|
||||
}
|
||||
|
||||
for (const measure of batch.measurements) {
|
||||
const name = measure.name || '';
|
||||
let m = this.measurements.get(name);
|
||||
if (!m) {
|
||||
m = new MeasurementCache(name, this.config);
|
||||
this.measurements.set(name, m);
|
||||
}
|
||||
if (measure.values) {
|
||||
m.addMeasurement(measure, action);
|
||||
} else {
|
||||
console.log('invalid measurement', measure);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
}
|
||||
3
packages/grafana-runtime/src/measurement/index.ts
Normal file
3
packages/grafana-runtime/src/measurement/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './types';
|
||||
export * from './collector';
|
||||
export * from './query';
|
||||
77
packages/grafana-runtime/src/measurement/query.ts
Normal file
77
packages/grafana-runtime/src/measurement/query.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
DataQueryResponse,
|
||||
isLiveChannelMessageEvent,
|
||||
isLiveChannelStatusEvent,
|
||||
isValidLiveChannelAddress,
|
||||
LiveChannelAddress,
|
||||
LoadingState,
|
||||
} from '@grafana/data';
|
||||
import { LiveMeasurements, MeasurementsQuery } from './types';
|
||||
import { getGrafanaLiveSrv } from '../services/live';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export function getLiveMeasurements(addr: LiveChannelAddress): LiveMeasurements | undefined {
|
||||
if (!isValidLiveChannelAddress(addr)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const live = getGrafanaLiveSrv();
|
||||
if (!live) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const channel = live.getChannel<LiveMeasurements>(addr);
|
||||
const getController = channel?.config?.getController;
|
||||
return getController ? getController() : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* When you know the stream will be managed measurements
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export function getLiveMeasurementsObserver(
|
||||
addr: LiveChannelAddress,
|
||||
requestId: string,
|
||||
query?: MeasurementsQuery
|
||||
): Observable<DataQueryResponse> {
|
||||
const rsp: DataQueryResponse = { data: [] };
|
||||
if (!addr || !addr.path) {
|
||||
return of(rsp); // Address not configured yet
|
||||
}
|
||||
|
||||
const live = getGrafanaLiveSrv();
|
||||
if (!live) {
|
||||
// This will only happen with the feature flag is not enabled
|
||||
rsp.error = { message: 'Grafana live is not initalized' };
|
||||
return of(rsp);
|
||||
}
|
||||
|
||||
rsp.key = requestId;
|
||||
return live
|
||||
.getChannel<LiveMeasurements>(addr)
|
||||
.getStream()
|
||||
.pipe(
|
||||
map(evt => {
|
||||
if (isLiveChannelMessageEvent(evt)) {
|
||||
rsp.data = evt.message.getData(query);
|
||||
if (!rsp.data.length) {
|
||||
// ?? skip when data is empty ???
|
||||
}
|
||||
delete rsp.error;
|
||||
rsp.state = LoadingState.Streaming;
|
||||
} else if (isLiveChannelStatusEvent(evt)) {
|
||||
if (evt.error != null) {
|
||||
rsp.error = rsp.error;
|
||||
rsp.state = LoadingState.Error;
|
||||
}
|
||||
}
|
||||
return { ...rsp }; // send event on all status messages
|
||||
})
|
||||
);
|
||||
}
|
||||
73
packages/grafana-runtime/src/measurement/types.ts
Normal file
73
packages/grafana-runtime/src/measurement/types.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { DataFrame, Labels, FieldConfig } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* the raw channel events are batches of Measurements
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface Measurement {
|
||||
name: string;
|
||||
time?: number; // Missing will use the browser time
|
||||
values: Record<string, any>;
|
||||
config?: Record<string, FieldConfig>;
|
||||
labels?: Labels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export enum MeasurementAction {
|
||||
/** The measurements will be added to the client buffer */
|
||||
Append = 'append',
|
||||
|
||||
/** The measurements will replace the client buffer */
|
||||
Replace = 'replace',
|
||||
|
||||
/** All measurements will be removed from the client buffer before processing */
|
||||
Clear = 'clear',
|
||||
}
|
||||
|
||||
/**
|
||||
* List of Measurements sent in a batch
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface MeasurementBatch {
|
||||
/**
|
||||
* The default action is to append values to the client buffer
|
||||
*/
|
||||
action?: MeasurementAction;
|
||||
|
||||
/**
|
||||
* List of measurements to process
|
||||
*/
|
||||
measurements: Measurement[];
|
||||
|
||||
/**
|
||||
* This will set the capacity on the client buffer for everything
|
||||
* in the measurement channel
|
||||
*/
|
||||
capacity?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface MeasurementsQuery {
|
||||
name?: string;
|
||||
labels?: Labels;
|
||||
fields?: string[]; // only include the fields with these names
|
||||
}
|
||||
|
||||
/**
|
||||
* Channels that receive Measurements can collect them into frames
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface LiveMeasurements {
|
||||
getData(query?: MeasurementsQuery): DataFrame[];
|
||||
getDistinctNames(): string[];
|
||||
getDistinctLabels(name: string): Labels[];
|
||||
setCapacity(size: number): void;
|
||||
getCapacity(): number;
|
||||
}
|
||||
@@ -6,6 +6,9 @@
|
||||
*/
|
||||
import { UrlQueryMap } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface LocationUpdate {
|
||||
/**
|
||||
* Target path where you automatically wants to navigate the user.
|
||||
|
||||
@@ -39,7 +39,7 @@ export const setGrafanaLiveSrv = (instance: GrafanaLiveSrv) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to retrieve the {@link GrafanaLiveSrv} that allows you to subscribe to
|
||||
* Used to retrieve the GrafanaLiveSrv that allows you to subscribe to
|
||||
* server side events and streams
|
||||
*
|
||||
* @alpha -- experimental
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.0-beta.2",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.0-beta.2",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -27,15 +27,15 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/core": "^10.0.27",
|
||||
"@grafana/data": "7.3.0-pre.0",
|
||||
"@grafana/e2e-selectors": "7.3.0-pre.0",
|
||||
"@grafana/data": "7.3.0-beta.2",
|
||||
"@grafana/e2e-selectors": "7.3.0-beta.2",
|
||||
"@grafana/slate-react": "0.22.9-grafana",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@iconscout/react-unicons": "1.1.4",
|
||||
"@torkelo/react-select": "3.0.8",
|
||||
"@types/hoist-non-react-statics": "3.3.1",
|
||||
"@types/react-beautiful-dnd": "12.1.2",
|
||||
"@types/react-color": "3.0.1",
|
||||
"@types/hoist-non-react-statics": "3.3.1",
|
||||
"@types/react-select": "3.0.8",
|
||||
"@types/react-table": "7.0.12",
|
||||
"@types/slate": "0.47.1",
|
||||
@@ -43,9 +43,8 @@
|
||||
"bizcharts": "^3.5.8",
|
||||
"classnames": "2.2.6",
|
||||
"d3": "5.15.0",
|
||||
"hoist-non-react-statics": "3.3.2",
|
||||
"immutable": "3.8.2",
|
||||
"emotion": "10.0.27",
|
||||
"hoist-non-react-statics": "3.3.2",
|
||||
"immutable": "3.8.2",
|
||||
"jquery": "3.5.1",
|
||||
"lodash": "4.17.19",
|
||||
|
||||
@@ -9,6 +9,7 @@ import { calculateFontSize } from '../../utils/measureText';
|
||||
|
||||
// Types
|
||||
import { BigValueColorMode, Props, BigValueJustifyMode, BigValueTextMode } from './BigValue';
|
||||
import { getTextColorForBackground } from '../../utils';
|
||||
|
||||
const LINE_HEIGHT = 1.2;
|
||||
const MAX_TITLE_SIZE = 30;
|
||||
@@ -51,7 +52,7 @@ export abstract class BigValueLayout {
|
||||
};
|
||||
|
||||
if (this.props.colorMode === BigValueColorMode.Background) {
|
||||
styles.color = 'white';
|
||||
styles.color = getTextColorForBackground(this.valueColor);
|
||||
}
|
||||
|
||||
return styles;
|
||||
@@ -69,7 +70,7 @@ export abstract class BigValueLayout {
|
||||
styles.color = this.valueColor;
|
||||
break;
|
||||
case BigValueColorMode.Background:
|
||||
styles.color = 'white';
|
||||
styles.color = getTextColorForBackground(this.valueColor);
|
||||
}
|
||||
|
||||
return styles;
|
||||
|
||||
@@ -30,7 +30,7 @@ exports[`BigValue Render with basic options should render 1`] = `
|
||||
<FormattedDisplayValue
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"color": "#202226",
|
||||
"fontSize": 230,
|
||||
"fontWeight": 500,
|
||||
"lineHeight": 1.2,
|
||||
|
||||
@@ -45,6 +45,12 @@ describe('LogDetails', () => {
|
||||
expect(wrapper.text().includes('key1label1key2label2')).toBe(true);
|
||||
});
|
||||
});
|
||||
describe('when log row has error', () => {
|
||||
it('should not render log level border', () => {
|
||||
const wrapper = setup({ hasError: true }, undefined);
|
||||
expect(wrapper.find({ 'aria-label': 'Log level' }).html()).not.toContain('logs-row__level');
|
||||
});
|
||||
});
|
||||
describe('when row entry has parsable fields', () => {
|
||||
it('should render heading ', () => {
|
||||
const wrapper = setup(undefined, { entry: 'test=successful' });
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface Props extends Themeable {
|
||||
showDuplicates: boolean;
|
||||
getRows: () => LogRowModel[];
|
||||
className?: string;
|
||||
hasError?: boolean;
|
||||
onMouseEnter?: () => void;
|
||||
onMouseLeave?: () => void;
|
||||
onClickFilterLabel?: (key: string, value: string) => void;
|
||||
@@ -70,6 +71,7 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
||||
const {
|
||||
row,
|
||||
theme,
|
||||
hasError,
|
||||
onClickFilterOutLabel,
|
||||
onClickFilterLabel,
|
||||
getRows,
|
||||
@@ -88,6 +90,8 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
||||
const labelsAvailable = Object.keys(labels).length > 0;
|
||||
const fields = getAllFields(row, getFieldLinks);
|
||||
const parsedFieldsAvailable = fields && fields.length > 0;
|
||||
// If logs with error, we are not showing the level color
|
||||
const levelClassName = cx(!hasError && [style.logsRowLevel, styles.logsRowLevelDetails]);
|
||||
|
||||
return (
|
||||
<tr
|
||||
@@ -96,7 +100,7 @@ class UnThemedLogDetails extends PureComponent<Props> {
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{showDuplicates && <td />}
|
||||
<td className={cx(style.logsRowLevel, styles.logsRowLevelDetails)} />
|
||||
<td className={levelClassName} aria-label="Log level" />
|
||||
<td colSpan={4}>
|
||||
<div className={style.logDetailsContainer}>
|
||||
<table className={style.logDetailsTable}>
|
||||
|
||||
@@ -8,8 +8,10 @@ import {
|
||||
DataQueryResponse,
|
||||
GrafanaTheme,
|
||||
dateTimeFormat,
|
||||
checkLogsError,
|
||||
} from '@grafana/data';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Tooltip } from '../Tooltip/Tooltip';
|
||||
import { cx, css } from 'emotion';
|
||||
|
||||
import {
|
||||
@@ -72,6 +74,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
label: hoverBackground;
|
||||
background-color: ${bgColor};
|
||||
`,
|
||||
errorLogRow: css`
|
||||
label: erroredLogRow;
|
||||
color: ${theme.colors.textWeak};
|
||||
`,
|
||||
};
|
||||
});
|
||||
/**
|
||||
@@ -160,22 +166,27 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
const { showDetails, showContext, hasHoverBackground } = this.state;
|
||||
const style = getLogRowStyles(theme, row.logLevel);
|
||||
const styles = getStyles(theme);
|
||||
const hoverBackground = cx(style.logsRow, { [styles.hoverBackground]: hasHoverBackground });
|
||||
const { errorMessage, hasError } = checkLogsError(row);
|
||||
const logRowBackground = cx(style.logsRow, {
|
||||
[styles.hoverBackground]: hasHoverBackground,
|
||||
[styles.errorLogRow]: hasError,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr
|
||||
className={hoverBackground}
|
||||
onMouseEnter={this.addHoverBackground}
|
||||
onMouseLeave={this.clearHoverBackground}
|
||||
onClick={this.toggleDetails}
|
||||
>
|
||||
<tr className={logRowBackground} onClick={this.toggleDetails}>
|
||||
{showDuplicates && (
|
||||
<td className={style.logsRowDuplicates}>
|
||||
{row.duplicates && row.duplicates > 0 ? `${row.duplicates + 1}x` : null}
|
||||
</td>
|
||||
)}
|
||||
<td className={style.logsRowLevel} />
|
||||
<td className={cx({ [style.logsRowLevel]: !hasError })}>
|
||||
{hasError && (
|
||||
<Tooltip content={`Error: ${errorMessage}`} placement="right" theme="error">
|
||||
<Icon className={style.logIconError} name="exclamation-triangle" size="sm" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</td>
|
||||
{!allowDetails && (
|
||||
<td title={showDetails ? 'Hide log details' : 'See log details'} className={style.logsRowToggleDetails}>
|
||||
<Icon className={styles.topVerticalAlign} name={showDetails ? 'angle-down' : 'angle-right'} />
|
||||
@@ -207,7 +218,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
</tr>
|
||||
{this.state.showDetails && (
|
||||
<LogDetails
|
||||
className={hoverBackground}
|
||||
className={logRowBackground}
|
||||
onMouseEnter={this.addHoverBackground}
|
||||
onMouseLeave={this.clearHoverBackground}
|
||||
showDuplicates={showDuplicates}
|
||||
@@ -218,6 +229,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
|
||||
onClickHideParsedField={onClickHideParsedField}
|
||||
getRows={getRows}
|
||||
row={row}
|
||||
hasError={hasError}
|
||||
showParsedFields={showParsedFields}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -9,6 +9,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
||||
let logColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.gray2 }, theme.type);
|
||||
const borderColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.gray2 }, theme.type);
|
||||
const bgColor = selectThemeVariant({ light: theme.palette.gray5, dark: theme.palette.dark4 }, theme.type);
|
||||
const hoverBgColor = selectThemeVariant({ light: theme.palette.gray7, dark: theme.palette.dark2 }, theme.type);
|
||||
const context = css`
|
||||
label: context;
|
||||
visibility: hidden;
|
||||
@@ -92,7 +93,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ${theme.colors.bodyBg};
|
||||
background: ${hoverBgColor};
|
||||
}
|
||||
`,
|
||||
logsRowDuplicates: css`
|
||||
@@ -116,6 +117,10 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo
|
||||
background-color: ${logColor};
|
||||
}
|
||||
`,
|
||||
logIconError: css`
|
||||
color: ${theme.palette.red};
|
||||
margin-left: -5px;
|
||||
`,
|
||||
logsRowToggleDetails: css`
|
||||
label: logs-row-toggle-details__level;
|
||||
position: relative;
|
||||
|
||||
@@ -23,9 +23,11 @@ export const FieldColorEditor: React.FC<FieldConfigEditorProps<FieldColor | unde
|
||||
const styles = useStyles(getStyles);
|
||||
|
||||
const options = fieldColorModeRegistry.list().map(mode => {
|
||||
let suffix = mode.isByValue ? ' (by value)' : '';
|
||||
|
||||
return {
|
||||
value: mode.id,
|
||||
label: mode.name,
|
||||
label: `${mode.name}${suffix}`,
|
||||
description: mode.description,
|
||||
isContinuous: mode.isContinuous,
|
||||
isByValue: mode.isByValue,
|
||||
|
||||
@@ -9,6 +9,7 @@ import memoizeOne from 'memoize-one';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { withTheme } from '../../themes';
|
||||
|
||||
// Default intervals used in the refresh picker component
|
||||
export const defaultIntervals = ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'];
|
||||
|
||||
const getStyles = memoizeOne((theme: GrafanaTheme) => {
|
||||
|
||||
@@ -131,3 +131,21 @@ export const CustomLabelField = () => {
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export const HtmlAttributes = () => {
|
||||
const [value, setValue] = useState<any>(options[0]);
|
||||
return (
|
||||
<SegmentFrame options={options}>
|
||||
<Segment
|
||||
data-testid="segment-test"
|
||||
id="segment-id"
|
||||
value={value}
|
||||
options={groupedOptions}
|
||||
onChange={({ value }) => {
|
||||
setValue(value);
|
||||
action('Segment value changed')(value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { HTMLProps } from 'react';
|
||||
import { cx } from 'emotion';
|
||||
import _ from 'lodash';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { SegmentSelect, useExpandableLabel, SegmentProps } from './';
|
||||
|
||||
export interface SegmentSyncProps<T> extends SegmentProps<T> {
|
||||
export interface SegmentSyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
|
||||
value?: T | SelectableValue<T>;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
options: Array<SelectableValue<T>>;
|
||||
@@ -18,6 +18,7 @@ export function Segment<T>({
|
||||
className,
|
||||
allowCustomValue,
|
||||
placeholder,
|
||||
...rest
|
||||
}: React.PropsWithChildren<SegmentSyncProps<T>>) {
|
||||
const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
|
||||
|
||||
@@ -38,6 +39,7 @@ export function Segment<T>({
|
||||
|
||||
return (
|
||||
<SegmentSelect
|
||||
{...rest}
|
||||
value={value && !_.isObject(value) ? { value } : value}
|
||||
options={options}
|
||||
width={width}
|
||||
|
||||
@@ -123,3 +123,21 @@ export const CustomLabel = () => {
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
export const HtmlAttributes = () => {
|
||||
const [value, setValue] = useState<any>(options[0]);
|
||||
return (
|
||||
<SegmentFrame loadOptions={() => loadOptions(options)}>
|
||||
<SegmentAsync
|
||||
data-testid="segment-async-test"
|
||||
id="segment-async"
|
||||
value={value}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
onChange={item => {
|
||||
setValue(item);
|
||||
action('Segment value changed')(item.value);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { HTMLProps } from 'react';
|
||||
import { cx } from 'emotion';
|
||||
import _ from 'lodash';
|
||||
import { SegmentSelect } from './SegmentSelect';
|
||||
@@ -7,7 +7,7 @@ import { useExpandableLabel, SegmentProps } from '.';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
|
||||
export interface SegmentAsyncProps<T> extends SegmentProps<T> {
|
||||
export interface SegmentAsyncProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
|
||||
value?: T | SelectableValue<T>;
|
||||
loadOptions: (query?: string) => Promise<Array<SelectableValue<T>>>;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
@@ -21,6 +21,7 @@ export function SegmentAsync<T>({
|
||||
className,
|
||||
allowCustomValue,
|
||||
placeholder,
|
||||
...rest
|
||||
}: React.PropsWithChildren<SegmentAsyncProps<T>>) {
|
||||
const [state, fetchOptions] = useAsyncFn(loadOptions, [loadOptions]);
|
||||
const [Label, width, expanded, setExpanded] = useExpandableLabel(false);
|
||||
@@ -43,6 +44,7 @@ export function SegmentAsync<T>({
|
||||
|
||||
return (
|
||||
<SegmentSelect
|
||||
{...rest}
|
||||
value={value && !_.isObject(value) ? { value } : value}
|
||||
options={state.value ?? []}
|
||||
width={width}
|
||||
|
||||
@@ -49,6 +49,23 @@ export const BasicInputWithPlaceholder = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const BasicInputWithHtmlAttributes = () => {
|
||||
const [value, setValue] = useState('some text');
|
||||
return (
|
||||
<SegmentFrame>
|
||||
<SegmentInput
|
||||
data-testid="segment-input-test"
|
||||
id="segment-input"
|
||||
value={value}
|
||||
onChange={text => {
|
||||
setValue(text as string);
|
||||
action('Segment value changed')(text);
|
||||
}}
|
||||
/>
|
||||
</SegmentFrame>
|
||||
);
|
||||
};
|
||||
|
||||
const InputComponent = ({ initialValue }: any) => {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import React, { HTMLProps, useRef, useState } from 'react';
|
||||
import { cx, css } from 'emotion';
|
||||
import useClickAway from 'react-use/lib/useClickAway';
|
||||
import { measureText } from '../../utils/measureText';
|
||||
import { useExpandableLabel, SegmentProps } from '.';
|
||||
|
||||
export interface SegmentInputProps<T> extends SegmentProps<T> {
|
||||
export interface SegmentInputProps<T> extends SegmentProps<T>, Omit<HTMLProps<HTMLInputElement>, 'value' | 'onChange'> {
|
||||
value: string | number;
|
||||
onChange: (text: string | number) => void;
|
||||
autofocus?: boolean;
|
||||
@@ -19,6 +19,7 @@ export function SegmentInput<T>({
|
||||
className,
|
||||
placeholder,
|
||||
autofocus = false,
|
||||
...rest
|
||||
}: React.PropsWithChildren<SegmentInputProps<T>>) {
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
const [value, setValue] = useState<number | string>(initialValue);
|
||||
@@ -50,6 +51,7 @@ export function SegmentInput<T>({
|
||||
|
||||
return (
|
||||
<input
|
||||
{...rest}
|
||||
ref={ref}
|
||||
autoFocus
|
||||
className={cx(`gf-form gf-form-input`, inputWidthStyle)}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useRef } from 'react';
|
||||
import React, { HTMLProps, useRef } from 'react';
|
||||
import { css, cx } from 'emotion';
|
||||
import useClickAway from 'react-use/lib/useClickAway';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select } from '../Forms/Legacy/Select/Select';
|
||||
|
||||
export interface Props<T> {
|
||||
export interface Props<T> extends Omit<HTMLProps<HTMLDivElement>, 'value' | 'onChange'> {
|
||||
value?: SelectableValue<T>;
|
||||
options: Array<SelectableValue<T>>;
|
||||
onChange: (item: SelectableValue<T>) => void;
|
||||
@@ -22,6 +22,7 @@ export function SegmentSelect<T>({
|
||||
width,
|
||||
noOptionsMessage = '',
|
||||
allowCustomValue = false,
|
||||
...rest
|
||||
}: React.PropsWithChildren<Props<T>>) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -39,7 +40,7 @@ export function SegmentSelect<T>({
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div {...rest} ref={ref}>
|
||||
<Select
|
||||
className={cx(
|
||||
css`
|
||||
|
||||
@@ -5,6 +5,7 @@ import { TableCellDisplayMode, TableCellProps } from './types';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { TableStyles } from './styles';
|
||||
import { FilterActions } from './FilterActions';
|
||||
import { getTextColorForBackground } from '../../utils';
|
||||
|
||||
export const DefaultCell: FC<TableCellProps> = props => {
|
||||
const { field, cell, tableStyles, row, cellProps } = props;
|
||||
@@ -65,7 +66,12 @@ function getCellStyle(tableStyles: TableStyles, field: Field, displayValue: Disp
|
||||
.spin(5)
|
||||
.toRgbString();
|
||||
|
||||
return tableStyles.buildCellContainerStyle('white', `linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`);
|
||||
const textColor = getTextColorForBackground(displayValue.color!);
|
||||
|
||||
return tableStyles.buildCellContainerStyle(
|
||||
textColor,
|
||||
`linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`
|
||||
);
|
||||
}
|
||||
|
||||
return tableStyles.cellContainer;
|
||||
|
||||
@@ -22,7 +22,7 @@ export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult';
|
||||
export { PieChart, PieChartType } from './PieChart/PieChart';
|
||||
export { UnitPicker } from './UnitPicker/UnitPicker';
|
||||
export { StatsPicker } from './StatsPicker/StatsPicker';
|
||||
export { RefreshPicker } from './RefreshPicker/RefreshPicker';
|
||||
export { RefreshPicker, defaultIntervals } from './RefreshPicker/RefreshPicker';
|
||||
export { TimeRangePicker } from './TimePicker/TimeRangePicker';
|
||||
export { TimeOfDayPicker } from './TimePicker/TimeOfDayPicker';
|
||||
export { TimeZonePicker } from './TimePicker/TimeZonePicker';
|
||||
|
||||
@@ -12,6 +12,8 @@ export type IconName =
|
||||
| 'filter'
|
||||
| 'angle-left'
|
||||
| 'angle-right'
|
||||
| 'angle-double-right'
|
||||
| 'angle-double-down'
|
||||
| 'pen'
|
||||
| 'envelope'
|
||||
| 'percentage'
|
||||
@@ -128,6 +130,8 @@ export const getAvailableIcons = (): IconName[] => [
|
||||
'filter',
|
||||
'angle-left',
|
||||
'angle-right',
|
||||
'angle-double-right',
|
||||
'angle-double-down',
|
||||
'pen',
|
||||
'envelope',
|
||||
'percentage',
|
||||
|
||||
@@ -4,6 +4,8 @@ import flattenDeep from 'lodash/flattenDeep';
|
||||
import chunk from 'lodash/chunk';
|
||||
import zip from 'lodash/zip';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import lightTheme from '../themes/light';
|
||||
import darkTheme from '../themes/dark';
|
||||
|
||||
export const PALETTE_ROWS = 4;
|
||||
export const PALETTE_COLUMNS = 14;
|
||||
@@ -93,4 +95,9 @@ function hslToHex(color: any) {
|
||||
return tinycolor(color).toHexString();
|
||||
}
|
||||
|
||||
export function getTextColorForBackground(color: string) {
|
||||
const b = tinycolor(color).getBrightness();
|
||||
return b > 150 ? lightTheme.colors.textStrong : darkTheme.colors.textStrong;
|
||||
}
|
||||
|
||||
export let sortedColors = sortColorsByHue(colors);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
let canvas: HTMLCanvasElement | null = null;
|
||||
const cache: Record<string, TextMetrics> = {};
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export function measureText(text: string, fontSize: number): TextMetrics {
|
||||
const fontStyle = `${fontSize}px 'Roboto'`;
|
||||
const cacheKey = text + fontStyle;
|
||||
@@ -26,6 +29,9 @@ export function measureText(text: string, fontSize: number): TextMetrics {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @beta
|
||||
*/
|
||||
export function calculateFontSize(text: string, width: number, height: number, lineHeight: number, maxSize?: number) {
|
||||
// calculate width in 14px
|
||||
const textSize = measureText(text, 14);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jaegertracing/jaeger-ui-components",
|
||||
"version": "7.3.0-pre.0",
|
||||
"version": "7.3.0-beta.2",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -14,8 +14,8 @@
|
||||
"typescript": "4.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "7.3.0-pre.0",
|
||||
"@grafana/ui": "7.3.0-pre.0",
|
||||
"@grafana/data": "7.3.0-beta.2",
|
||||
"@grafana/ui": "7.3.0-beta.2",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/deep-freeze": "^0.1.1",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import TimelineCollapser from './TimelineCollapser';
|
||||
import { TimelineCollapser } from './TimelineCollapser';
|
||||
|
||||
describe('<TimelineCollapser>', () => {
|
||||
it('renders without exploding', () => {
|
||||
|
||||
@@ -12,18 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import { IconButton } from '@grafana/ui';
|
||||
import { css } from 'emotion';
|
||||
import cx from 'classnames';
|
||||
|
||||
import { UITooltip, UIIcon } from '../../uiElementsContext';
|
||||
import React from 'react';
|
||||
import { createStyle } from '../../Theme';
|
||||
|
||||
const getStyles = createStyle(() => {
|
||||
return {
|
||||
TraceTimelineViewer: css`
|
||||
border-bottom: 1px solid #bbb;
|
||||
`,
|
||||
TimelineCollapser: css`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -31,21 +26,6 @@ const getStyles = createStyle(() => {
|
||||
justify-content: center;
|
||||
margin-right: 0.5rem;
|
||||
`,
|
||||
tooltipTitle: css`
|
||||
white-space: pre;
|
||||
`,
|
||||
btn: css`
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
cursor: pointer;
|
||||
margin-right: 0.3rem;
|
||||
padding: 0.1rem;
|
||||
&:hover {
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
`,
|
||||
btnExpanded: css`
|
||||
transform: rotate(90deg);
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -56,40 +36,27 @@ type CollapserProps = {
|
||||
onExpandAll: () => void;
|
||||
};
|
||||
|
||||
function getTitle(value: string) {
|
||||
export function TimelineCollapser(props: CollapserProps) {
|
||||
const { onExpandAll, onExpandOne, onCollapseAll, onCollapseOne } = props;
|
||||
const styles = getStyles();
|
||||
return <span className={styles.tooltipTitle}>{value}</span>;
|
||||
}
|
||||
|
||||
export default class TimelineCollapser extends React.PureComponent<CollapserProps> {
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: CollapserProps) {
|
||||
super(props);
|
||||
this.containerRef = React.createRef();
|
||||
}
|
||||
|
||||
// TODO: Something less hacky than createElement to help TypeScript / AntD
|
||||
getContainer = () => this.containerRef.current || document.createElement('div');
|
||||
|
||||
render() {
|
||||
const { onExpandAll, onExpandOne, onCollapseAll, onCollapseOne } = this.props;
|
||||
const styles = getStyles();
|
||||
return (
|
||||
<div className={styles.TimelineCollapser} ref={this.containerRef} data-test-id="TimelineCollapser">
|
||||
<UITooltip title={getTitle('Expand +1')} getPopupContainer={this.getContainer}>
|
||||
<UIIcon type="right" onClick={onExpandOne} className={cx(styles.btn, styles.btnExpanded)} />
|
||||
</UITooltip>
|
||||
<UITooltip title={getTitle('Collapse +1')} getPopupContainer={this.getContainer}>
|
||||
<UIIcon type="right" onClick={onCollapseOne} className={styles.btn} />
|
||||
</UITooltip>
|
||||
<UITooltip title={getTitle('Expand All')} getPopupContainer={this.getContainer}>
|
||||
<UIIcon type="double-right" onClick={onExpandAll} className={cx(styles.btn, styles.btnExpanded)} />
|
||||
</UITooltip>
|
||||
<UITooltip title={getTitle('Collapse All')} getPopupContainer={this.getContainer}>
|
||||
<UIIcon type="double-right" onClick={onCollapseAll} className={styles.btn} />
|
||||
</UITooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={styles.TimelineCollapser} data-test-id="TimelineCollapser">
|
||||
<IconButton tooltip="Expand +1" size="xl" tooltipPlacement="top" name="angle-down" onClick={onExpandOne} />
|
||||
<IconButton tooltip="Collapse +1" size="xl" tooltipPlacement="top" name="angle-right" onClick={onCollapseOne} />
|
||||
<IconButton
|
||||
tooltip="Expand All"
|
||||
size="xl"
|
||||
tooltipPlacement="top"
|
||||
name="angle-double-down"
|
||||
onClick={onExpandAll}
|
||||
/>
|
||||
<IconButton
|
||||
tooltip="Collapse All"
|
||||
size="xl"
|
||||
tooltipPlacement="top"
|
||||
name="angle-double-right"
|
||||
onClick={onCollapseAll}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import TimelineHeaderRow from './TimelineHeaderRow';
|
||||
import TimelineColumnResizer from './TimelineColumnResizer';
|
||||
import TimelineViewingLayer from './TimelineViewingLayer';
|
||||
import Ticks from '../Ticks';
|
||||
import TimelineCollapser from './TimelineCollapser';
|
||||
import { TimelineCollapser } from './TimelineCollapser';
|
||||
|
||||
describe('<TimelineHeaderRow>', () => {
|
||||
let wrapper;
|
||||
|
||||
@@ -16,7 +16,7 @@ import * as React from 'react';
|
||||
import { css } from 'emotion';
|
||||
import cx from 'classnames';
|
||||
|
||||
import TimelineCollapser from './TimelineCollapser';
|
||||
import { TimelineCollapser } from './TimelineCollapser';
|
||||
import TimelineColumnResizer from './TimelineColumnResizer';
|
||||
import TimelineViewingLayer from './TimelineViewingLayer';
|
||||
import Ticks from '../Ticks';
|
||||
|
||||
@@ -21,7 +21,7 @@ type URLValidationError struct {
|
||||
|
||||
// Error returns the error message.
|
||||
func (e URLValidationError) Error() string {
|
||||
return fmt.Sprintf("Validation of data source URL %q failed: %s", e.URL, e.Err.Error())
|
||||
return fmt.Sprintf("validation of data source URL %q failed: %s", e.URL, e.Err.Error())
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error.
|
||||
|
||||
@@ -585,7 +585,7 @@ func TestNewDataSourceProxy_InvalidURL(t *testing.T) {
|
||||
plugin := plugins.DataSourcePlugin{}
|
||||
_, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg)
|
||||
require.Error(t, err)
|
||||
assert.True(t, strings.HasPrefix(err.Error(), `Validation of data source URL "://host/root" failed`))
|
||||
assert.True(t, strings.HasPrefix(err.Error(), `validation of data source URL "://host/root" failed`))
|
||||
}
|
||||
|
||||
func TestNewDataSourceProxy_ProtocolLessURL(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
@@ -27,7 +28,7 @@ func (hs *HTTPServer) createShortURL(c *models.ReqContext, cmd dtos.CreateShortU
|
||||
return Error(500, "Failed to create short URL", err)
|
||||
}
|
||||
|
||||
url := path.Join(setting.AppUrl, "goto", shortURL.Uid)
|
||||
url := fmt.Sprintf("%s/goto/%s", strings.TrimSuffix(setting.AppUrl, "/"), shortURL.Uid)
|
||||
c.Logger.Debug("Created short URL", "url", url)
|
||||
|
||||
dto := dtos.ShortURL{
|
||||
|
||||
@@ -513,6 +513,26 @@ func SetBuildInformation(version, revision, branch string) {
|
||||
grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1)
|
||||
}
|
||||
|
||||
// SetEnvironmentInformation exposes environment values provided by the operators as an `_info` metric.
|
||||
// If there are no environment metrics labels configured, this metric will not be exposed.
|
||||
func SetEnvironmentInformation(labels map[string]string) error {
|
||||
if len(labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
grafanaEnvironmentInfo := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "environment_info",
|
||||
Help: "A metric with a constant '1' value labeled by environment information about the running instance.",
|
||||
Namespace: ExporterName,
|
||||
ConstLabels: labels,
|
||||
})
|
||||
|
||||
prometheus.MustRegister(grafanaEnvironmentInfo)
|
||||
|
||||
grafanaEnvironmentInfo.Set(1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetPluginBuildInformation(pluginID, pluginType, version string) {
|
||||
grafanaPluginBuildInfoDesc.WithLabelValues(pluginID, pluginType, version).Set(1)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
httpRequestsInFlight prometheus.Gauge
|
||||
httpRequestsInFlight prometheus.Gauge
|
||||
httpRequestDurationHistogram *prometheus.HistogramVec
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -23,33 +25,52 @@ func init() {
|
||||
},
|
||||
)
|
||||
|
||||
prometheus.MustRegister(httpRequestsInFlight)
|
||||
httpRequestDurationHistogram = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "grafana",
|
||||
Name: "http_request_duration_seconds",
|
||||
Help: "Histogram of latencies for HTTP requests.",
|
||||
Buckets: []float64{.1, .2, .4, 1, 3, 8, 20, 60, 120},
|
||||
},
|
||||
[]string{"handler"},
|
||||
)
|
||||
|
||||
prometheus.MustRegister(httpRequestsInFlight, httpRequestDurationHistogram)
|
||||
}
|
||||
|
||||
// RequestMetrics is a middleware handler that instruments the request
|
||||
func RequestMetrics(handler string) macaron.Handler {
|
||||
return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
|
||||
rw := res.(macaron.ResponseWriter)
|
||||
now := time.Now()
|
||||
httpRequestsInFlight.Inc()
|
||||
defer httpRequestsInFlight.Dec()
|
||||
c.Next()
|
||||
func RequestMetrics(cfg *setting.Cfg) func(handler string) macaron.Handler {
|
||||
return func(handler string) macaron.Handler {
|
||||
return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
|
||||
rw := res.(macaron.ResponseWriter)
|
||||
now := time.Now()
|
||||
httpRequestsInFlight.Inc()
|
||||
defer httpRequestsInFlight.Dec()
|
||||
c.Next()
|
||||
|
||||
status := rw.Status()
|
||||
status := rw.Status()
|
||||
|
||||
code := sanitizeCode(status)
|
||||
method := sanitizeMethod(req.Method)
|
||||
metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc()
|
||||
duration := time.Since(now).Nanoseconds() / int64(time.Millisecond)
|
||||
metrics.MHttpRequestSummary.WithLabelValues(handler, code, method).Observe(float64(duration))
|
||||
code := sanitizeCode(status)
|
||||
method := sanitizeMethod(req.Method)
|
||||
metrics.MHttpRequestTotal.WithLabelValues(handler, code, method).Inc()
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(req.RequestURI, "/api/datasources/proxy"):
|
||||
countProxyRequests(status)
|
||||
case strings.HasPrefix(req.RequestURI, "/api/"):
|
||||
countApiRequests(status)
|
||||
default:
|
||||
countPageRequests(status)
|
||||
duration := time.Since(now).Nanoseconds() / int64(time.Millisecond)
|
||||
|
||||
// enable histogram and disable summaries for http requests.
|
||||
if cfg.IsHTTPRequestHistogramEnabled() {
|
||||
httpRequestDurationHistogram.WithLabelValues(handler).Observe(float64(duration))
|
||||
} else {
|
||||
metrics.MHttpRequestSummary.WithLabelValues(handler, code, method).Observe(float64(duration))
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(req.RequestURI, "/api/datasources/proxy"):
|
||||
countProxyRequests(status)
|
||||
case strings.HasPrefix(req.RequestURI, "/api/"):
|
||||
countApiRequests(status)
|
||||
default:
|
||||
countPageRequests(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ type UpdatePluginDashboardError struct {
|
||||
}
|
||||
|
||||
func (d UpdatePluginDashboardError) Error() string {
|
||||
return "Dashboard belong to plugin"
|
||||
return "Dashboard belongs to plugin"
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -2,7 +2,7 @@ package models
|
||||
|
||||
import "github.com/centrifugal/centrifuge"
|
||||
|
||||
// ChannelPublisher writes data into a channel
|
||||
// ChannelPublisher writes data into a channel. Note that pemissions are not checked.
|
||||
type ChannelPublisher func(channel string, data []byte) error
|
||||
|
||||
// ChannelHandler defines the core channel behavior
|
||||
@@ -17,9 +17,10 @@ type ChannelHandler interface {
|
||||
OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error)
|
||||
}
|
||||
|
||||
// ChannelHandlerProvider -- this should be implemented by any core feature
|
||||
type ChannelHandlerProvider interface {
|
||||
// This is called fast and often -- it must be synchrnozed
|
||||
// ChannelHandlerFactory should be implemented by all core features.
|
||||
type ChannelHandlerFactory interface {
|
||||
// GetHandlerForPath gets a ChannelHandler for a path.
|
||||
// This is called fast and often -- it must be synchronized
|
||||
GetHandlerForPath(path string) (ChannelHandler, error)
|
||||
}
|
||||
|
||||
|
||||
50
pkg/models/measurement.go
Normal file
50
pkg/models/measurement.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package models
|
||||
|
||||
import "github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
|
||||
// NOTE:
|
||||
// this likely should go in the Plugin SDK since it will be useful from plugins
|
||||
|
||||
// Measurement is a single measurement value.
|
||||
type Measurement struct {
|
||||
// Name of the measurement.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Time is the measurement time. Units are usually ms, but depends on the channel
|
||||
Time int64 `json:"time,omitempty"`
|
||||
|
||||
// Values is the measurement's values. The value type is typically number or string.
|
||||
Values map[string]interface{} `json:"values,omitempty"`
|
||||
|
||||
// Config is an optional list of field configs.
|
||||
Config map[string]data.FieldConfig `json:"config,omitempty"`
|
||||
|
||||
// Labels are applied to all values.
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// MeasurementAction defines what should happen when you send a list of measurements.
|
||||
type MeasurementAction string
|
||||
|
||||
const (
|
||||
// MeasurementActionAppend means new values should be added to a client buffer. This is the default action
|
||||
MeasurementActionAppend MeasurementAction = "append"
|
||||
|
||||
// MeasurementActionReplace means new values should replace any existing values.
|
||||
MeasurementActionReplace MeasurementAction = "replace"
|
||||
|
||||
// MeasurementActionClear means all existing values should be remoed before adding.
|
||||
MeasurementActionClear MeasurementAction = "clear"
|
||||
)
|
||||
|
||||
// MeasurementBatch is a collection of measurements all sent at once.
|
||||
type MeasurementBatch struct {
|
||||
// Action is the action in question, the default is append.
|
||||
Action MeasurementAction `json:"action,omitempty"`
|
||||
|
||||
// Measurements is the array of measurements.
|
||||
Measurements []Measurement `json:"measurements,omitempty"`
|
||||
|
||||
// Capacity is the suggested size of the client buffer
|
||||
Capacity int64 `json:"capacity,omitempty"`
|
||||
}
|
||||
@@ -2,12 +2,15 @@ package plugins
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/grpcplugin"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
type AppPluginCss struct {
|
||||
@@ -21,6 +24,8 @@ type AppPlugin struct {
|
||||
|
||||
FoundChildPlugins []*PluginInclude `json:"-"`
|
||||
Pinned bool `json:"-"`
|
||||
|
||||
Executable string `json:"executable,omitempty"`
|
||||
}
|
||||
|
||||
// AppPluginRoute describes a plugin route that is defined in
|
||||
@@ -67,6 +72,15 @@ func (app *AppPlugin) Load(decoder *json.Decoder, base *PluginBase, backendPlugi
|
||||
return err
|
||||
}
|
||||
|
||||
if app.Backend {
|
||||
cmd := ComposePluginStartCommand(app.Executable)
|
||||
fullpath := filepath.Join(app.PluginDir, cmd)
|
||||
factory := grpcplugin.NewBackendPlugin(app.Id, fullpath, grpcplugin.PluginStartFuncs{})
|
||||
if err := backendPluginManager.Register(app.Id, factory); err != nil {
|
||||
return errutil.Wrapf(err, "failed to register backend plugin")
|
||||
}
|
||||
}
|
||||
|
||||
Apps[app.Id] = app
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -35,11 +35,25 @@ const (
|
||||
)
|
||||
|
||||
type PluginNotFoundError struct {
|
||||
PluginId string
|
||||
PluginID string
|
||||
}
|
||||
|
||||
func (e PluginNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Plugin with id %s not found", e.PluginId)
|
||||
return fmt.Sprintf("plugin with ID %q not found", e.PluginID)
|
||||
}
|
||||
|
||||
type duplicatePluginError struct {
|
||||
Plugin *PluginBase
|
||||
ExistingPlugin *PluginBase
|
||||
}
|
||||
|
||||
func (e duplicatePluginError) Error() string {
|
||||
return fmt.Sprintf("plugin with ID %q already loaded from %q", e.Plugin.Id, e.ExistingPlugin.PluginDir)
|
||||
}
|
||||
|
||||
func (e duplicatePluginError) Is(err error) bool {
|
||||
_, ok := err.(duplicatePluginError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// PluginLoader can load a plugin.
|
||||
@@ -77,8 +91,8 @@ type PluginBase struct {
|
||||
}
|
||||
|
||||
func (pb *PluginBase) registerPlugin(base *PluginBase) error {
|
||||
if _, exists := Plugins[pb.Id]; exists {
|
||||
return fmt.Errorf("Plugin with ID %q already exists", pb.Id)
|
||||
if p, exists := Plugins[pb.Id]; exists {
|
||||
return duplicatePluginError{Plugin: pb, ExistingPlugin: p}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(base.PluginDir, setting.StaticRootPath) {
|
||||
|
||||
@@ -271,6 +271,12 @@ func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error {
|
||||
|
||||
// Load the full plugin, and add it to manager
|
||||
if err := loader.Load(jsonParser, plugin, scanner.backendPluginManager); err != nil {
|
||||
if errors.Is(err, duplicatePluginError{}) {
|
||||
pm.log.Warn("Plugin is duplicate", "error", err)
|
||||
scanner.errors = append(scanner.errors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package plugins
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -163,6 +164,23 @@ func TestPluginManager_Init(t *testing.T) {
|
||||
require.Empty(t, pm.scanningErrors)
|
||||
assert.Equal(t, []string{"gel"}, fm.registeredPlugins)
|
||||
})
|
||||
|
||||
t.Run("With nested plugin duplicating parent", func(t *testing.T) {
|
||||
origPluginsPath := setting.PluginsPath
|
||||
t.Cleanup(func() {
|
||||
setting.PluginsPath = origPluginsPath
|
||||
})
|
||||
setting.PluginsPath = "testdata/duplicate-plugins"
|
||||
|
||||
pm := &PluginManager{
|
||||
Cfg: &setting.Cfg{},
|
||||
}
|
||||
err := pm.Init()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, pm.scanningErrors, 1)
|
||||
assert.True(t, errors.Is(pm.scanningErrors[0], duplicatePluginError{}))
|
||||
})
|
||||
}
|
||||
|
||||
func TestPluginManager_IsBackendOnlyPlugin(t *testing.T) {
|
||||
|
||||
14
pkg/plugins/testdata/duplicate-plugins/nested/nested/plugin.json
vendored
Normal file
14
pkg/plugins/testdata/duplicate-plugins/nested/nested/plugin.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "Child",
|
||||
"id": "test-app",
|
||||
"info": {
|
||||
"description": "Child plugin",
|
||||
"author": {
|
||||
"name": "Grafana Labs",
|
||||
"url": "http://grafana.com"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"updated": "2020-10-20"
|
||||
}
|
||||
}
|
||||
14
pkg/plugins/testdata/duplicate-plugins/nested/plugin.json
vendored
Normal file
14
pkg/plugins/testdata/duplicate-plugins/nested/plugin.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "Parent",
|
||||
"id": "test-app",
|
||||
"info": {
|
||||
"description": "Parent plugin",
|
||||
"author": {
|
||||
"name": "Grafana Labs",
|
||||
"url": "http://grafana.com"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"updated": "2020-10-20"
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
_ "github.com/grafana/grafana/pkg/extensions"
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
_ "github.com/grafana/grafana/pkg/infra/metrics"
|
||||
_ "github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
_ "github.com/grafana/grafana/pkg/infra/serverlock"
|
||||
@@ -117,6 +118,9 @@ func (s *Server) init(cfg *Config) error {
|
||||
|
||||
s.loadConfiguration()
|
||||
s.writePIDFile()
|
||||
if err := metrics.SetEnvironmentInformation(s.cfg.MetricsGrafanaEnvironmentInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
login.Init()
|
||||
social.NewOAuthService()
|
||||
@@ -269,7 +273,7 @@ func (s *Server) buildServiceGraph(services []*registry.Descriptor) error {
|
||||
objs := []interface{}{
|
||||
bus.GetBus(),
|
||||
s.cfg,
|
||||
routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing),
|
||||
routing.NewRouteRegister(middleware.RequestMetrics(s.cfg), middleware.RequestTracing),
|
||||
localcache.New(5*time.Minute, 10*time.Minute),
|
||||
s,
|
||||
}
|
||||
|
||||
@@ -1,27 +1,48 @@
|
||||
package live
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ChannelIdentifier is the channel id split by parts
|
||||
type ChannelIdentifier struct {
|
||||
Scope string // grafana, ds, or plugin
|
||||
Namespace string // feature, id, or name
|
||||
Path string // path within the channel handler
|
||||
// ChannelAddress is the channel ID split by parts.
|
||||
type ChannelAddress struct {
|
||||
// Scope is "grafana", "ds", or "plugin".
|
||||
Scope string `json:"scope,omitempty"`
|
||||
|
||||
// Namespace meaning depends on the scope.
|
||||
// * when grafana, namespace is a "feature"
|
||||
// * when ds, namespace is the datasource id
|
||||
// * when plugin, namespace is the plugin name
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
// Within each namespace, the handler can process the path as needed.
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// ParseChannelIdentifier parses the parts from a channel id:
|
||||
// ${scope} / ${namespace} / ${path}
|
||||
func ParseChannelIdentifier(id string) (ChannelIdentifier, error) {
|
||||
// ParseChannelAddress parses the parts from a channel ID:
|
||||
// ${scope} / ${namespace} / ${path}.
|
||||
func ParseChannelAddress(id string) ChannelAddress {
|
||||
addr := ChannelAddress{}
|
||||
parts := strings.SplitN(id, "/", 3)
|
||||
if len(parts) == 3 {
|
||||
return ChannelIdentifier{
|
||||
Scope: parts[0],
|
||||
Namespace: parts[1],
|
||||
Path: parts[2],
|
||||
}, nil
|
||||
length := len(parts)
|
||||
if length > 0 {
|
||||
addr.Scope = parts[0]
|
||||
}
|
||||
return ChannelIdentifier{}, fmt.Errorf("Invalid channel id: %s", id)
|
||||
if length > 1 {
|
||||
addr.Namespace = parts[1]
|
||||
}
|
||||
if length > 2 {
|
||||
addr.Path = parts[2]
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// IsValid checks if all parts of the address are valid.
|
||||
func (ca *ChannelAddress) IsValid() bool {
|
||||
return ca.Scope != "" && ca.Namespace != "" && ca.Path != ""
|
||||
}
|
||||
|
||||
// ToChannelID converts this to a single string.
|
||||
func (ca *ChannelAddress) ToChannelID() string {
|
||||
return ca.Scope + "/" + ca.Namespace + "/" + ca.Path
|
||||
}
|
||||
|
||||
@@ -4,27 +4,25 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseChannelIdentifier(t *testing.T) {
|
||||
ident, err := ParseChannelIdentifier("aaa/bbb/ccc/ddd")
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
func TestParseChannelAddress_Valid(t *testing.T) {
|
||||
addr := ParseChannelAddress("aaa/bbb/ccc/ddd")
|
||||
require.True(t, addr.IsValid())
|
||||
|
||||
ex := ChannelIdentifier{
|
||||
ex := ChannelAddress{
|
||||
Scope: "aaa",
|
||||
Namespace: "bbb",
|
||||
Path: "ccc/ddd",
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(ident, ex); diff != "" {
|
||||
if diff := cmp.Diff(addr, ex); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Check an invalid identifier
|
||||
_, err = ParseChannelIdentifier("aaa/bbb")
|
||||
if err == nil {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseChannelAddress_Invalid(t *testing.T) {
|
||||
addr := ParseChannelAddress("aaa/bbb")
|
||||
require.False(t, addr.IsValid())
|
||||
}
|
||||
|
||||
@@ -6,27 +6,28 @@ import (
|
||||
)
|
||||
|
||||
// BroadcastRunner will simply broadcast all events to `grafana/broadcast/*` channels
|
||||
// This makes no assumptions about the shape of the data and will broadcast it to anyone listening
|
||||
type BroadcastRunner struct{}
|
||||
// This assumes that data is a JSON object
|
||||
type BroadcastRunner struct {
|
||||
}
|
||||
|
||||
// GetHandlerForPath called on init
|
||||
func (g *BroadcastRunner) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
return g, nil // for now all channels share config
|
||||
func (b *BroadcastRunner) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
return b, nil // for now all channels share config
|
||||
}
|
||||
|
||||
// GetChannelOptions called fast and often
|
||||
func (g *BroadcastRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
func (b *BroadcastRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
return centrifuge.ChannelOptions{}
|
||||
}
|
||||
|
||||
// OnSubscribe for now allows anyone to subscribe to any dashboard
|
||||
func (g *BroadcastRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
func (b *BroadcastRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
// anyone can subscribe
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPublish called when an event is received from the websocket
|
||||
func (g *BroadcastRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
func (b *BroadcastRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
// expect the data to be the right shape?
|
||||
return e.Data, nil
|
||||
}
|
||||
|
||||
@@ -17,14 +17,7 @@ type dashboardEvent struct {
|
||||
|
||||
// DashboardHandler manages all the `grafana/dashboard/*` channels
|
||||
type DashboardHandler struct {
|
||||
publisher models.ChannelPublisher
|
||||
}
|
||||
|
||||
// CreateDashboardHandler Initialize a dashboard handler
|
||||
func CreateDashboardHandler(p models.ChannelPublisher) DashboardHandler {
|
||||
return DashboardHandler{
|
||||
publisher: p,
|
||||
}
|
||||
Publisher models.ChannelPublisher
|
||||
}
|
||||
|
||||
// GetHandlerForPath called on init
|
||||
@@ -58,7 +51,7 @@ func (g *DashboardHandler) publish(event dashboardEvent) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.publisher("grafana/dashboard/"+event.UID, msg)
|
||||
return g.Publisher("grafana/dashboard/"+event.UID, msg)
|
||||
}
|
||||
|
||||
// DashboardSaved will broadcast to all connected dashboards
|
||||
|
||||
41
pkg/services/live/features/measurements.go
Normal file
41
pkg/services/live/features/measurements.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"github.com/centrifugal/centrifuge"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = log.New("live.features") // scoped to all features?
|
||||
)
|
||||
|
||||
// MeasurementsRunner will simply broadcast all events to `grafana/broadcast/*` channels.
|
||||
// This makes no assumptions about the shape of the data and will broadcast it to anyone listening
|
||||
type MeasurementsRunner struct {
|
||||
}
|
||||
|
||||
// GetHandlerForPath gets the handler for a path.
|
||||
// It's called on init.
|
||||
func (m *MeasurementsRunner) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
return m, nil // for now all channels share config
|
||||
}
|
||||
|
||||
// GetChannelOptions gets channel options.
|
||||
// It gets called fast and often.
|
||||
func (m *MeasurementsRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
return centrifuge.ChannelOptions{}
|
||||
}
|
||||
|
||||
// OnSubscribe for now allows anyone to subscribe to any dashboard.
|
||||
func (m *MeasurementsRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
// anyone can subscribe
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPublish is called when an event is received from the websocket.
|
||||
func (m *MeasurementsRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
// currently generic... but should be stricter
|
||||
// logger.Debug("Measurements runner got event on channel", "channel", e.Channel)
|
||||
return e.Data, nil
|
||||
}
|
||||
@@ -7,48 +7,43 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/centrifugal/centrifuge"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// TestdataRunner manages all the `grafana/dashboard/*` channels
|
||||
type testdataRunner struct {
|
||||
// testDataRunner manages all the `grafana/dashboard/*` channels.
|
||||
type testDataRunner struct {
|
||||
publisher models.ChannelPublisher
|
||||
running bool
|
||||
speedMillis int
|
||||
dropPercent float64
|
||||
channel string
|
||||
name string
|
||||
}
|
||||
|
||||
// TestdataSupplier manages all the `grafana/testdata/*` channels
|
||||
type TestdataSupplier struct {
|
||||
publisher models.ChannelPublisher
|
||||
// TestDataSupplier manages all the `grafana/testdata/*` channels.
|
||||
type TestDataSupplier struct {
|
||||
Publisher models.ChannelPublisher
|
||||
}
|
||||
|
||||
// CreateTestdataSupplier Initialize a dashboard handler
|
||||
func CreateTestdataSupplier(p models.ChannelPublisher) TestdataSupplier {
|
||||
return TestdataSupplier{
|
||||
publisher: p,
|
||||
}
|
||||
}
|
||||
|
||||
// GetHandlerForPath called on init
|
||||
func (g *TestdataSupplier) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
// GetHandlerForPath gets the channel handler for a path.
|
||||
// Called on init.
|
||||
func (g *TestDataSupplier) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
channel := "grafana/testdata/" + path
|
||||
|
||||
if path == "random-2s-stream" {
|
||||
return &testdataRunner{
|
||||
publisher: g.publisher,
|
||||
return &testDataRunner{
|
||||
publisher: g.Publisher,
|
||||
running: false,
|
||||
speedMillis: 2000,
|
||||
dropPercent: 0,
|
||||
channel: channel,
|
||||
name: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if path == "random-flakey-stream" {
|
||||
return &testdataRunner{
|
||||
publisher: g.publisher,
|
||||
return &testDataRunner{
|
||||
publisher: g.Publisher,
|
||||
running: false,
|
||||
speedMillis: 400,
|
||||
dropPercent: .6,
|
||||
@@ -59,13 +54,14 @@ func (g *TestdataSupplier) GetHandlerForPath(path string) (models.ChannelHandler
|
||||
return nil, fmt.Errorf("unknown channel")
|
||||
}
|
||||
|
||||
// GetChannelOptions called fast and often
|
||||
func (g *testdataRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
// GetChannelOptions gets channel options.
|
||||
// Called fast and often.
|
||||
func (g *testDataRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
return centrifuge.ChannelOptions{}
|
||||
}
|
||||
|
||||
// OnSubscribe for now allows anyone to subscribe to any dashboard
|
||||
func (g *testdataRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
// OnSubscribe for now allows anyone to subscribe to any dashboard.
|
||||
func (g *testDataRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
if !g.running {
|
||||
g.running = true
|
||||
|
||||
@@ -77,26 +73,26 @@ func (g *testdataRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.Subscrib
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPublish called when an event is received from the websocket
|
||||
func (g *testdataRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
// OnPublish is called when an event is received from the websocket.
|
||||
func (g *testDataRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
return nil, fmt.Errorf("can not publish to testdata")
|
||||
}
|
||||
|
||||
type randomWalkMessage struct {
|
||||
Time int64
|
||||
Value float64
|
||||
Min float64
|
||||
Max float64
|
||||
}
|
||||
|
||||
// RunRandomCSV just for an example
|
||||
func (g *testdataRunner) runRandomCSV() {
|
||||
// runRandomCSV is just for an example.
|
||||
func (g *testDataRunner) runRandomCSV() {
|
||||
spread := 50.0
|
||||
|
||||
walker := rand.Float64() * 100
|
||||
ticker := time.NewTicker(time.Duration(g.speedMillis) * time.Millisecond)
|
||||
|
||||
line := randomWalkMessage{}
|
||||
measurement := models.Measurement{
|
||||
Name: g.name,
|
||||
Time: 0,
|
||||
Values: make(map[string]interface{}, 5),
|
||||
}
|
||||
msg := models.MeasurementBatch{
|
||||
Measurements: []models.Measurement{measurement}, // always a single measurement
|
||||
}
|
||||
|
||||
for t := range ticker.C {
|
||||
if rand.Float64() <= g.dropPercent {
|
||||
@@ -105,12 +101,12 @@ func (g *testdataRunner) runRandomCSV() {
|
||||
delta := rand.Float64() - 0.5
|
||||
walker += delta
|
||||
|
||||
line.Time = t.UnixNano() / int64(time.Millisecond)
|
||||
line.Value = walker
|
||||
line.Min = walker - ((rand.Float64() * spread) + 0.01)
|
||||
line.Max = walker + ((rand.Float64() * spread) + 0.01)
|
||||
measurement.Time = t.UnixNano() / int64(time.Millisecond)
|
||||
measurement.Values["value"] = walker
|
||||
measurement.Values["min"] = walker - ((rand.Float64() * spread) + 0.01)
|
||||
measurement.Values["max"] = walker + ((rand.Float64() * spread) + 0.01)
|
||||
|
||||
bytes, err := json.Marshal(&line)
|
||||
bytes, err := json.Marshal(&msg)
|
||||
if err != nil {
|
||||
logger.Warn("unable to marshal line", "error", err)
|
||||
continue
|
||||
@@ -118,7 +114,7 @@ func (g *testdataRunner) runRandomCSV() {
|
||||
|
||||
err = g.publisher(g.channel, bytes)
|
||||
if err != nil {
|
||||
logger.Warn("write", "channel", g.channel, "line", line)
|
||||
logger.Warn("write", "channel", g.channel, "measurement", measurement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ var (
|
||||
|
||||
// CoreGrafanaScope list of core features
|
||||
type CoreGrafanaScope struct {
|
||||
Features map[string]models.ChannelHandlerProvider
|
||||
Features map[string]models.ChannelHandlerFactory
|
||||
|
||||
// The generic service to advertise dashboard changes
|
||||
Dashboards models.DashboardActivityChannel
|
||||
@@ -47,7 +47,7 @@ func InitializeBroker() (*GrafanaLive, error) {
|
||||
channels: make(map[string]models.ChannelHandler),
|
||||
channelsMu: sync.RWMutex{},
|
||||
GrafanaScope: CoreGrafanaScope{
|
||||
Features: make(map[string]models.ChannelHandlerProvider),
|
||||
Features: make(map[string]models.ChannelHandlerFactory),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -83,13 +83,17 @@ func InitializeBroker() (*GrafanaLive, error) {
|
||||
glive.node = node
|
||||
|
||||
// Initialize the main features
|
||||
dash := features.CreateDashboardHandler(glive.Publish)
|
||||
tds := features.CreateTestdataSupplier(glive.Publish)
|
||||
dash := &features.DashboardHandler{
|
||||
Publisher: glive.Publish,
|
||||
}
|
||||
|
||||
glive.GrafanaScope.Dashboards = &dash
|
||||
glive.GrafanaScope.Features["dashboard"] = &dash
|
||||
glive.GrafanaScope.Features["testdata"] = &tds
|
||||
glive.GrafanaScope.Dashboards = dash
|
||||
glive.GrafanaScope.Features["dashboard"] = dash
|
||||
glive.GrafanaScope.Features["testdata"] = &features.TestDataSupplier{
|
||||
Publisher: glive.Publish,
|
||||
}
|
||||
glive.GrafanaScope.Features["broadcast"] = &features.BroadcastRunner{}
|
||||
glive.GrafanaScope.Features["measurements"] = &features.MeasurementsRunner{}
|
||||
|
||||
// Set ConnectHandler called when client successfully connected to Node. Your code
|
||||
// inside handler must be synchronized since it will be called concurrently from
|
||||
@@ -232,11 +236,11 @@ func (g *GrafanaLive) GetChannelHandler(channel string) (models.ChannelHandler,
|
||||
}
|
||||
|
||||
// Parse the identifier ${scope}/${namespace}/${path}
|
||||
id, err := ParseChannelIdentifier(channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
addr := ParseChannelAddress(channel)
|
||||
if !addr.IsValid() {
|
||||
return nil, fmt.Errorf("invalid channel: %q", channel)
|
||||
}
|
||||
logger.Info("initChannel", "channel", channel, "id", id)
|
||||
logger.Info("initChannel", "channel", channel, "address", addr)
|
||||
|
||||
g.channelsMu.Lock()
|
||||
defer g.channelsMu.Unlock()
|
||||
@@ -245,39 +249,48 @@ func (g *GrafanaLive) GetChannelHandler(channel string) (models.ChannelHandler,
|
||||
return c, nil
|
||||
}
|
||||
|
||||
c, err = g.initChannel(id)
|
||||
getter, err := g.GetChannelHandlerFactory(addr.Scope, addr.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First access will initialize
|
||||
c, err = getter.GetHandlerForPath(addr.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g.channels[channel] = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (g *GrafanaLive) initChannel(id ChannelIdentifier) (models.ChannelHandler, error) {
|
||||
if id.Scope == "grafana" {
|
||||
p, ok := g.GrafanaScope.Features[id.Namespace]
|
||||
// GetChannelHandlerFactory gets a ChannelHandlerFactory for a namespace.
|
||||
// It gives threadsafe access to the channel.
|
||||
func (g *GrafanaLive) GetChannelHandlerFactory(scope string, name string) (models.ChannelHandlerFactory, error) {
|
||||
if scope == "grafana" {
|
||||
p, ok := g.GrafanaScope.Features[name]
|
||||
if ok {
|
||||
return p.GetHandlerForPath(id.Path)
|
||||
return p, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Unknown feature: %s", id.Namespace)
|
||||
return nil, fmt.Errorf("unknown feature: %q", name)
|
||||
}
|
||||
|
||||
if id.Scope == "ds" {
|
||||
return nil, fmt.Errorf("todo... look up datasource: %s", id.Namespace)
|
||||
if scope == "ds" {
|
||||
return nil, fmt.Errorf("todo... look up datasource: %q", name)
|
||||
}
|
||||
|
||||
if id.Scope == "plugin" {
|
||||
p, ok := plugins.Plugins[id.Namespace]
|
||||
if scope == "plugin" {
|
||||
p, ok := plugins.Plugins[name]
|
||||
if ok {
|
||||
h := &PluginHandler{
|
||||
Plugin: p,
|
||||
}
|
||||
return h.GetHandlerForPath(id.Path)
|
||||
return h, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown plugin: %s", id.Namespace)
|
||||
return nil, fmt.Errorf("unknown plugin: %q", name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid scope: %s", id.Scope)
|
||||
return nil, fmt.Errorf("invalid scope: %q", scope)
|
||||
}
|
||||
|
||||
// Publish sends the data to the channel without checking permissions etc
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user