Merge remote-tracking branch 'origin/main' into guicaulada/unified-secrets
This commit is contained in:
+2
-2
@@ -92,7 +92,7 @@ exports[`no enzyme tests`] = {
|
||||
"packages/jaeger-ui-components/src/TracePageHeader/TracePageHeader.test.js:3669774385": [
|
||||
[15, 26, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js:1298620967": [
|
||||
"packages/jaeger-ui-components/src/TracePageHeader/TracePageSearchBar.test.js:2095199604": [
|
||||
[15, 19, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"packages/jaeger-ui-components/src/TraceTimelineViewer/ListView/index.test.js:793239444": [
|
||||
@@ -305,7 +305,7 @@ exports[`no enzyme tests`] = {
|
||||
"public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:2974837543": [
|
||||
[1, 19, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:4272466995": [
|
||||
"public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:132770839": [
|
||||
[1, 19, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx:1089831034": [
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[run]
|
||||
init_cmds = [
|
||||
["make", "gen-go"],
|
||||
["go", "run", "build.go", "-dev", "build-cli"],
|
||||
["go", "run", "build.go", "-dev", "build-server"],
|
||||
["GO_BUILD_DEV=1", "make", "build-cli"],
|
||||
["GO_BUILD_DEV=1", "make", "build-server"],
|
||||
["./bin/grafana-server", "-packaging=dev", "cfg:app_mode=development"]
|
||||
]
|
||||
watch_all = true
|
||||
@@ -13,10 +13,10 @@ watch_dirs = [
|
||||
"$WORKDIR/conf",
|
||||
]
|
||||
watch_exts = [".go", ".ini", ".toml", ".template.html"]
|
||||
ignore_files = ["wire_gen.go"]
|
||||
ignore_files = ["wire_gen.go"]
|
||||
build_delay = 1500
|
||||
cmds = [
|
||||
["make", "gen-go"],
|
||||
["go", "run", "build.go", "-dev", "build-server"],
|
||||
["GO_BUILD_DEV=1", "make", "build-server"],
|
||||
["./bin/grafana-server", "-packaging=dev", "cfg:app_mode=development"]
|
||||
]
|
||||
|
||||
+58
-68
@@ -11,7 +11,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -114,7 +114,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -130,7 +130,7 @@ steps:
|
||||
name: initialize
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--variants linux-amd64,linux-amd64-musl,darwin-amd64,windows-amd64,armv6 --no-pull-enterprise
|
||||
--variants linux-amd64,linux-amd64-musl,darwin-amd64,windows-amd64,armv6
|
||||
depends_on:
|
||||
- initialize
|
||||
environment: {}
|
||||
@@ -138,7 +138,6 @@ steps:
|
||||
name: build-backend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -147,7 +146,6 @@ steps:
|
||||
name: build-frontend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -182,7 +180,7 @@ steps:
|
||||
name: ensure-cuetsified
|
||||
- commands:
|
||||
- . scripts/build/gpg-test-vars.sh && ./bin/grabpl package --jobs 8 --edition oss
|
||||
--build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise --variants linux-amd64,linux-amd64-musl,darwin-amd64,windows-amd64,armv6
|
||||
--build-id ${DRONE_BUILD_NUMBER} --variants linux-amd64,linux-amd64-musl,darwin-amd64,windows-amd64,armv6
|
||||
depends_on:
|
||||
- build-plugins
|
||||
- build-backend
|
||||
@@ -358,7 +356,7 @@ services:
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -422,7 +420,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -460,7 +458,6 @@ steps:
|
||||
name: lint-docs
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -506,7 +503,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -544,7 +541,6 @@ steps:
|
||||
name: lint-docs
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -589,7 +585,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -692,7 +688,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -744,7 +740,6 @@ steps:
|
||||
from_secret: drone_token
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment: {}
|
||||
@@ -752,7 +747,6 @@ steps:
|
||||
name: build-backend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -761,7 +755,6 @@ steps:
|
||||
name: build-frontend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -797,8 +790,7 @@ steps:
|
||||
image: grafana/build-container:1.5.3
|
||||
name: ensure-cuetsified
|
||||
- commands:
|
||||
- ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise
|
||||
--sign
|
||||
- ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --sign
|
||||
depends_on:
|
||||
- build-plugins
|
||||
- build-backend
|
||||
@@ -1102,7 +1094,7 @@ services:
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -1172,7 +1164,7 @@ steps:
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/windows/grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/windows/grabpl.exe
|
||||
-OutFile grabpl.exe
|
||||
image: grafana/ci-wix:0.1.1
|
||||
name: initialize
|
||||
@@ -1239,6 +1231,8 @@ trigger:
|
||||
- exclude
|
||||
include:
|
||||
- .drone.yml
|
||||
repo:
|
||||
- grafana/grafana
|
||||
type: docker
|
||||
---
|
||||
depends_on:
|
||||
@@ -1257,7 +1251,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -1343,7 +1337,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -1360,7 +1354,7 @@ steps:
|
||||
name: initialize
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN}
|
||||
--no-pull-enterprise ${DRONE_TAG}
|
||||
${DRONE_TAG}
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -1370,7 +1364,7 @@ steps:
|
||||
name: build-backend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --edition
|
||||
oss --no-pull-enterprise ${DRONE_TAG}
|
||||
oss ${DRONE_TAG}
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -1379,7 +1373,7 @@ steps:
|
||||
name: build-frontend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN}
|
||||
--edition oss --no-pull-enterprise ${DRONE_TAG}
|
||||
--edition oss ${DRONE_TAG}
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -1415,8 +1409,8 @@ steps:
|
||||
image: grafana/build-container:1.5.3
|
||||
name: ensure-cuetsified
|
||||
- commands:
|
||||
- ./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} --sign
|
||||
${DRONE_TAG}
|
||||
depends_on:
|
||||
- build-plugins
|
||||
- build-backend
|
||||
@@ -1651,7 +1645,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -1772,7 +1766,7 @@ services:
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -1856,7 +1850,7 @@ steps:
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/windows/grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/windows/grabpl.exe
|
||||
-OutFile grabpl.exe
|
||||
image: grafana/ci-wix:0.1.1
|
||||
name: initialize
|
||||
@@ -1915,7 +1909,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -1954,7 +1948,7 @@ steps:
|
||||
name: initialize
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN}
|
||||
--no-pull-enterprise ${DRONE_TAG}
|
||||
${DRONE_TAG}
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -1964,7 +1958,7 @@ steps:
|
||||
name: build-backend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --edition
|
||||
enterprise --no-pull-enterprise ${DRONE_TAG}
|
||||
enterprise ${DRONE_TAG}
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -1973,7 +1967,7 @@ steps:
|
||||
name: build-frontend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN}
|
||||
--edition enterprise --no-pull-enterprise ${DRONE_TAG}
|
||||
--edition enterprise ${DRONE_TAG}
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -2010,7 +2004,7 @@ steps:
|
||||
name: ensure-cuetsified
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise2 --github-token $${GITHUB_TOKEN}
|
||||
--no-pull-enterprise ${DRONE_TAG}
|
||||
${DRONE_TAG}
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -2020,7 +2014,7 @@ steps:
|
||||
name: build-backend-enterprise2
|
||||
- commands:
|
||||
- ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN}
|
||||
--no-pull-enterprise --sign ${DRONE_TAG}
|
||||
--sign ${DRONE_TAG}
|
||||
depends_on:
|
||||
- build-plugins
|
||||
- build-backend
|
||||
@@ -2178,7 +2172,7 @@ steps:
|
||||
name: upload-packages
|
||||
- commands:
|
||||
- ./bin/grabpl package --jobs 8 --edition enterprise2 --github-token $${GITHUB_TOKEN}
|
||||
--no-pull-enterprise --sign ${DRONE_TAG}
|
||||
--sign ${DRONE_TAG}
|
||||
depends_on:
|
||||
- build-plugins
|
||||
- build-backend
|
||||
@@ -2258,7 +2252,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -2431,7 +2425,7 @@ services:
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -2559,7 +2553,7 @@ steps:
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/windows/grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/windows/grabpl.exe
|
||||
-OutFile grabpl.exe
|
||||
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
@@ -2634,7 +2628,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -2712,7 +2706,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -2773,7 +2767,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -2852,7 +2846,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -2914,7 +2908,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -2950,7 +2944,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -2997,7 +2991,7 @@ steps:
|
||||
name: initialize
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -3034,6 +3028,8 @@ volumes:
|
||||
---
|
||||
depends_on:
|
||||
- publish-artifacts-public
|
||||
- publish-docker-oss-public
|
||||
- publish-docker-enterprise-public
|
||||
kind: pipeline
|
||||
name: publish-packages
|
||||
node:
|
||||
@@ -3045,7 +3041,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -3108,7 +3104,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -3124,7 +3120,6 @@ steps:
|
||||
name: initialize
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment: {}
|
||||
@@ -3132,7 +3127,6 @@ steps:
|
||||
name: build-backend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -3141,7 +3135,6 @@ steps:
|
||||
name: build-frontend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -3177,8 +3170,7 @@ steps:
|
||||
image: grafana/build-container:1.5.3
|
||||
name: ensure-cuetsified
|
||||
- commands:
|
||||
- ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --no-pull-enterprise
|
||||
--sign
|
||||
- ./bin/grabpl package --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --sign
|
||||
depends_on:
|
||||
- build-plugins
|
||||
- build-backend
|
||||
@@ -3373,7 +3365,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -3489,7 +3481,7 @@ services:
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -3568,7 +3560,7 @@ steps:
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/windows/grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/windows/grabpl.exe
|
||||
-OutFile grabpl.exe
|
||||
image: grafana/ci-wix:0.1.1
|
||||
name: initialize
|
||||
@@ -3618,7 +3610,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -3653,7 +3645,6 @@ steps:
|
||||
name: initialize
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment: {}
|
||||
@@ -3661,7 +3652,6 @@ steps:
|
||||
name: build-backend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -3670,7 +3660,7 @@ steps:
|
||||
name: build-frontend
|
||||
- commands:
|
||||
- ./bin/grabpl build-frontend-packages --jobs 8 --edition enterprise --build-id
|
||||
${DRONE_BUILD_NUMBER} --no-pull-enterprise
|
||||
${DRONE_BUILD_NUMBER}
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
@@ -3707,7 +3697,7 @@ steps:
|
||||
name: ensure-cuetsified
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise2 --build-id ${DRONE_BUILD_NUMBER}
|
||||
--variants linux-amd64 --no-pull-enterprise
|
||||
--variants linux-amd64
|
||||
depends_on:
|
||||
- initialize
|
||||
environment: {}
|
||||
@@ -3715,7 +3705,7 @@ steps:
|
||||
name: build-backend-enterprise2
|
||||
- commands:
|
||||
- ./bin/grabpl package --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise --sign
|
||||
--sign
|
||||
depends_on:
|
||||
- build-plugins
|
||||
- build-backend
|
||||
@@ -3882,7 +3872,7 @@ steps:
|
||||
name: upload-packages
|
||||
- commands:
|
||||
- ./bin/grabpl package --jobs 8 --edition enterprise2 --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise --variants linux-amd64 --sign
|
||||
--variants linux-amd64 --sign
|
||||
depends_on:
|
||||
- build-plugins
|
||||
- build-backend
|
||||
@@ -3957,7 +3947,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -4122,7 +4112,7 @@ services:
|
||||
steps:
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/grabpl
|
||||
- curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/grabpl
|
||||
- chmod +x bin/grabpl
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: grabpl
|
||||
@@ -4242,7 +4232,7 @@ steps:
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- $$ProgressPreference = "SilentlyContinue"
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.27/windows/grabpl.exe
|
||||
- Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.30/windows/grabpl.exe
|
||||
-OutFile grabpl.exe
|
||||
- git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"
|
||||
- cd grafana-enterprise
|
||||
@@ -4441,6 +4431,6 @@ kind: secret
|
||||
name: gcp_upload_artifacts_key
|
||||
---
|
||||
kind: signature
|
||||
hmac: b5191e597c2ea7a1152ab18ca2c4aece939024ceb8fd5c76581c087a64ae6ad3
|
||||
hmac: 430843c058dc4f40a3f881e13fc61ab12f5b6dc823f99067e56ab49fc1bdd459
|
||||
|
||||
...
|
||||
|
||||
@@ -63,6 +63,7 @@ go.sum @grafana/backend-platform
|
||||
/pkg/services/ngalert @grafana/alerting-squad-backend
|
||||
/pkg/services/sqlstore/migrations/ualert @grafana/alerting-squad-backend
|
||||
/pkg/services/alerting @grafana/alerting-squad-backend
|
||||
/pkg/tests/api/alerting @grafana/alerting-squad-backend
|
||||
/public/app/features/alerting @grafana/alerting-squad-frontend
|
||||
|
||||
# Library Services
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
},
|
||||
{
|
||||
"type": "changedfiles",
|
||||
"matches": ["/pkg/services/ngalert/**/*", "/pkg/services/sqlstore/migrations/ualert/**/*", "/pkg/services/alerting/**/*", "/public/app/features/alerting/**/*"],
|
||||
"matches": ["/pkg/services/ngalert/**/*", "/pkg/services/sqlstore/migrations/ualert/**/*", "/pkg/services/alerting/**/*", "/public/app/features/alerting/**/*", "/pkg/tests/api/alerting/**/*"],
|
||||
"action": "updateLabel",
|
||||
"addLabel": "area/alerting"
|
||||
},
|
||||
|
||||
@@ -60,12 +60,6 @@ jobs:
|
||||
echo "::set-output name=branch_name::v${{steps.regex-match.outputs.group1}}"
|
||||
echo "::set-output name=branch_exist::$(git ls-remote --heads https://github.com/grafana/grafana.git v${{ steps.regex-match.outputs.group1 }}.x | wc -l)"
|
||||
|
||||
- name: Check input version is aligned with branch(not main)
|
||||
if: ${{ github.event.inputs.version != '' && steps.intermedia.outputs.branch_exist != '0' && !contains(steps.intermedia.outputs.short_ref, steps.intermedia.outputs.branch_name) }}
|
||||
run: |
|
||||
echo " You need to run the workflow on branch v${{steps.regex-match.outputs.group1}}.x
|
||||
exit 1
|
||||
|
||||
- name: Check input version is aligned with branch(main)
|
||||
if: ${{ github.event.inputs.version != '' && steps.intermedia.outputs.branch_exist == '0' && !contains(steps.intermedia.outputs.short_ref, 'main') }}
|
||||
run: |
|
||||
|
||||
@@ -8,10 +8,8 @@ name: "CodeQL"
|
||||
on:
|
||||
push:
|
||||
branches: [main, v1.8.x, v2.0.x, v2.1.x, v2.6.x, v3.0.x, v3.1.x, v4.0.x, v4.1.x, v4.2.x, v4.3.x, v4.4.x, v4.5.x, v4.6.x, v4.7.x, v5.0.x, v5.1.x, v5.2.x, v5.3.x, v5.4.x, v6.0.x, v6.1.x, v6.2.x, v6.3.x, v6.4.x, v6.5.x, v6.6.x, v6.7.x, v7.0.x, v7.1.x, v7.2.x]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '**/*.cue'
|
||||
- '**/*.json'
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
@@ -51,21 +49,5 @@ jobs:
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: "CodeQL for PR / go"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '**/*.go'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: "go"
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
@@ -0,0 +1,31 @@
|
||||
name: "CodeQL for PR / javascript"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '**/*.js'
|
||||
- '**/*.ts'
|
||||
- '**/*.tsx'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: "javascript"
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
@@ -0,0 +1,29 @@
|
||||
name: "CodeQL for PR / python"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '**/*.py'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: "python"
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "eslint",
|
||||
"version": "8.10.0-sdk",
|
||||
"version": "8.11.0-sdk",
|
||||
"main": "./lib/api.js",
|
||||
"type": "commonjs"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
<!-- 8.4.5 START -->
|
||||
|
||||
# 8.4.5 (2022-03-31)
|
||||
|
||||
### Features and enhancements
|
||||
|
||||
- **Instrumentation:** Make backend plugin metrics endpoints available with optional authentication. [#46467](https://github.com/grafana/grafana/pull/46467), [@marefr](https://github.com/marefr)
|
||||
- **Table panel:** Show datalinks for cell display modes JSON View and Gauge derivates. [#46020](https://github.com/grafana/grafana/pull/46020), [@mdvictor](https://github.com/mdvictor)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- **Azure Monitor:** Small bug fixes for Resource Picker. [#46665](https://github.com/grafana/grafana/pull/46665), [@sarahzinger](https://github.com/sarahzinger)
|
||||
- **Logger:** Use specified format for file logger. [#46970](https://github.com/grafana/grafana/pull/46970), [@sakjur](https://github.com/sakjur)
|
||||
- **Logs:** Handle missing fields in dataframes better. [#46963](https://github.com/grafana/grafana/pull/46963), [@gabor](https://github.com/gabor)
|
||||
- **ManageDashboards:** Fix error when deleting all dashboards from folder view. [#46877](https://github.com/grafana/grafana/pull/46877), [@joshhunt](https://github.com/joshhunt)
|
||||
|
||||
<!-- 8.4.5 END -->
|
||||
<!-- 8.4.4 START -->
|
||||
|
||||
# 8.4.4 (2022-03-16)
|
||||
|
||||
@@ -14,6 +14,8 @@ GO_FILES ?= ./pkg/...
|
||||
SH_FILES ?= $(shell find ./scripts -name *.sh)
|
||||
API_DEFINITION_FILES = $(shell find ./pkg/api/docs/definitions -name '*.go' -print)
|
||||
SWAGGER_TAG ?= latest
|
||||
GO_BUILD_FLAGS += $(if $(GO_BUILD_DEV),-dev)
|
||||
GO_BUILD_FLAGS += $(if $(GO_BUILD_TAGS),-build-tags=$(GO_BUILD_TAGS))
|
||||
|
||||
all: deps build
|
||||
|
||||
@@ -87,15 +89,15 @@ gen-go: $(WIRE)
|
||||
|
||||
build-go: $(MERGED_SPEC_TARGET) gen-go ## Build all Go binaries.
|
||||
@echo "build go files"
|
||||
$(GO) run build.go build
|
||||
$(GO) run build.go $(GO_BUILD_FLAGS) build
|
||||
|
||||
build-server: ## Build Grafana server.
|
||||
@echo "build server"
|
||||
$(GO) run build.go build-server
|
||||
$(GO) run build.go $(GO_BUILD_FLAGS) build-server
|
||||
|
||||
build-cli: ## Build Grafana CLI application.
|
||||
@echo "build grafana-cli"
|
||||
$(GO) run build.go build-cli
|
||||
$(GO) run build.go $(GO_BUILD_FLAGS) build-cli
|
||||
|
||||
build-js: ## Build frontend assets.
|
||||
@echo "build frontend"
|
||||
|
||||
+5
-5
@@ -69,8 +69,8 @@ Configure alerting behavior in the absence of data using information in the foll
|
||||
| Alerting | Set alert rule state to `Alerting`. |
|
||||
| Ok | Set alert rule state to `Normal`. |
|
||||
|
||||
| Error or timeout option | Description |
|
||||
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Alerting | Set alert rule state to `Alerting` |
|
||||
| OK | Set alert rule state to `Normal` |
|
||||
| Error | Create a new alert `DatasourceError` with the name and UID of the alert rule, and UID of the datasource that returned no data as labels. |
|
||||
| Error or timeout option | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Alerting | Set alert rule state to `Alerting`. From Grafana 8.5, the alert rule waits for the entire duration for which the condition is true before firing. |
|
||||
| OK | Set alert rule state to `Normal` |
|
||||
| Error | Create a new alert `DatasourceError` with the name and UID of the alert rule, and UID of the datasource that returned no data as labels. |
|
||||
|
||||
@@ -323,7 +323,16 @@ If you're not currently logged in to the CloudWatch console, the link will forwa
|
||||
|
||||
## Alerting
|
||||
|
||||
Since CloudWatch Logs queries can return numeric data, for example through the use of the `stats` command, alerts are supported.
|
||||
Alerting require queries that return numeric data, which CloudWatch Logs support. For example through the use of the `stats` command, alerts are supported. For example, this is a valid query for alerting on messages that include the text "Exception":
|
||||
|
||||
```
|
||||
filter @message like /Exception/
|
||||
| stats count(*) as exceptionCount by bin(1h)
|
||||
| sort exceptionCount desc
|
||||
```
|
||||
|
||||
**NOTE**: When trying to alert on a query, if an error like `input data must be a wide series but got ...` is received, make sure that your query returns valid numeric data that can be printed in a Time series panel.
|
||||
|
||||
For more information on Grafana alerts, refer to [Alerting]({{< relref "../../alerting/_index.md" >}}) documentation.
|
||||
|
||||
## Configure CloudWatch with grafana.ini
|
||||
|
||||
@@ -53,6 +53,7 @@ Improve an existing plugin with one of our guides:
|
||||
- [Build a streaming data source plugin]({{< relref "build-a-streaming-data-source-plugin.md" >}})
|
||||
- [Error handling]({{< relref "error-handling.md" >}})
|
||||
- [Working with data frames]({{< relref "working-with-data-frames.md" >}})
|
||||
- [Development with local Grafana]({{< relref "development-with-local-grafana.md" >}})
|
||||
|
||||
### Concepts
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
+++
|
||||
title = "Development with local Grafana"
|
||||
+++
|
||||
|
||||
# Development with local Grafana
|
||||
|
||||
This guide allows you to setup a development environment where you run Grafana and your plugin locally. With this, you will be able to see your changes as you add them.
|
||||
|
||||
## Run Grafana in your host
|
||||
|
||||
If you have git, Go and the required version of NodeJS in your system, you can clone and run Grafana locally:
|
||||
|
||||
1. Download and set up Grafana. You can find instructions on how to do it in the [developer-guide](https://github.com/grafana/grafana/blob/HEAD/contribute/developer-guide.md).
|
||||
|
||||
2. Grafana will look for plugins, by default, on its `data/plugins` directory. You can create a symbolic link to your plugin repository to detect new changes:
|
||||
|
||||
```bash
|
||||
ln -s <plugin-path>/dist data/plugins/<plugin-name>
|
||||
```
|
||||
|
||||
3. (Optional) If the step above doesn't work for you (e.g. you are running on Windows), you can also modify the default path in the Grafana configuration (that can be found at `conf/custom.ini`) and point to the directory with your plugin:
|
||||
|
||||
```ini
|
||||
[paths]
|
||||
plugins = <path-to-your-plugin-parent-directory>
|
||||
```
|
||||
|
||||
## Run Grafana with docker-compose
|
||||
|
||||
Another possibility is to run Grafana with docker-compose so it runs in a container. For doing so, create the docker-compose file in your plugin directory:
|
||||
|
||||
**NOTE**: Some plugins already include a docker-compose file so you can skip this step.
|
||||
|
||||
```yaml
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
grafana:
|
||||
# Change latest with your target version, if needed
|
||||
image: grafana/grafana:latest
|
||||
ports:
|
||||
- 3000:3000/tcp
|
||||
volumes:
|
||||
# Use your plugin folder (e.g. redshift-datasource)
|
||||
- ./dist:/var/lib/grafana/plugins/<plugin-folder>
|
||||
- ./provisioning:/etc/grafana/provisioning
|
||||
environment:
|
||||
- TERM=linux
|
||||
- GF_LOG_LEVEL=debug
|
||||
- GF_DATAPROXY_LOGGING=true
|
||||
- GF_DEFAULT_APP_MODE=development
|
||||
```
|
||||
|
||||
## Run your plugin
|
||||
|
||||
Finally start your plugin in development mode. Go to your plugin root directory and follow these steps:
|
||||
|
||||
1. Build your plugin backend and start the frontend in watch mode:
|
||||
|
||||
```bash
|
||||
mage -v
|
||||
yarn watch
|
||||
```
|
||||
|
||||
2. Start Grafana backend and frontend:
|
||||
|
||||
2.1 For a local copy of Grafana, go to the directory with Grafana source code and run:
|
||||
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
2.2 Or with docker-compose, in your plugin directory, run:
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
After this, you should be able to see your plugin listed in Grafana and test your changes. Note that any change in the fronted will require you to refresh your browser while changes in the backend may require to rebuild your plugin binaries and reload the plugin (`mage && mage reloadPlugin` for local development or `docker-compose up` again if you are using docker-compose).
|
||||
@@ -50,6 +50,9 @@ The reference information that follows complements conceptual information about
|
||||
| `fixed:folders:writer` | All permissions from `fixed:dashboards:writer` and <br>`folders:read`<br>`folders:write`<br>`folders:create`<br>`folders:delete`<br>`folders.permissions:read`<br>`folders.permissions:write` | Read, create, update, and delete all folders and dashboards. |
|
||||
| `fixed:folders.permissions:reader` | `folders.permissions:read` | Read all folder permissions. |
|
||||
| `fixed:folders.permissions:writer` | All permissions from `fixed:folders.permissions:reader` and <br>`folders.permissions:write` | Read and update all folder permissions. |
|
||||
| `fixed:annotations:reader` | `annotations:read` | Read all annotations and annotation tags. |
|
||||
| `fixed:annotations.dashboard:writer` | `annotations:write` <br>`annotations.create`<br> `annotations:delete` for scope `annotations:type:dashboard` | Create, update and delete dashboard annotations and annotation tags. |
|
||||
| `fixed:annotations:writer` | `annotations:write` <br>`annotations.create`<br> `annotations:delete` for scope `annotations:type:*` | Create, update and delete all annotations and annotation tags. |
|
||||
|
||||
## Default built-in role assignments
|
||||
|
||||
@@ -57,5 +60,5 @@ The reference information that follows complements conceptual information about
|
||||
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Grafana Admin | `fixed:roles:reader`<br>`fixed:roles:writer`<br>`fixed:users:reader`<br>`fixed:users:writer`<br>`fixed:org.users:reader`<br>`fixed:org.users:writer`<br>`fixed:ldap:reader`<br>`fixed:ldap:writer`<br>`fixed:stats:reader`<br>`fixed:settings:reader`<br>`fixed:settings:writer`<br>`fixed:provisioning:writer`<br>`fixed:organization:reader`<br>`fixed:organization:maintainer`<br>`fixed:licensing:reader`<br>`fixed:licensing:writer` | Default [Grafana server administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#grafana-server-administrators" >}}) assignments. |
|
||||
| Admin | `fixed:reports:reader`<br>`fixed:reports:writer`<br>`fixed:datasources:reader`<br>`fixed:datasources:writer`<br>`fixed:organization:writer`<br>`fixed:datasources.permissions:reader`<br>`fixed:datasources.permissions:writer`<br>`fixed:teams:writer`<br>`fixed:dashboards:reader`<br>`fixed:dashboards:writer`<br>`fixed:dashboards.permissions:reader`<br>`fixed:dashboards.permissions:writer`<br>`fixed:folders:reader`<br>`fixes:folders:writer`<br>`fixed:folders.permissions:reader`<br>`fixed:folders.permissions:writer` | Default [Grafana organization administrator]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
|
||||
| Editor | `fixed:datasources:explorer`<br>`fixed:dashboards:creator`<br>`fixed:folders:creator`<br>`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled | Default [Editor]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
|
||||
| Viewer | `fixed:datasources:id:reader`<br>`fixed:organization:reader` | Default [Viewer]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
|
||||
| Editor | `fixed:datasources:explorer`<br>`fixed:dashboards:creator`<br>`fixed:folders:creator`<br>`fixed:annotations:writer`<br>`fixed:teams:creator` if the `editors_can_admin` configuration flag is enabled | Default [Editor]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
|
||||
| Viewer | `fixed:datasources:id:reader`<br>`fixed:organization:reader`<br>`fixed:annotations:reader`<br>`fixed:annotations.dashboard:writer` | Default [Viewer]({{< relref "../../administration/manage-users-and-permissions/about-users-and-permissions.md#organization-users-and-permissions" >}}) assignments. |
|
||||
|
||||
@@ -113,23 +113,28 @@ The following list contains fine-grained access control actions.
|
||||
| `folders:delete` | `folders:*`<br>`folders:id:*` | Delete one or more folders. |
|
||||
| `folers.permissions:read` | `folders:*`<br>`folders:id:*` | Read permissions for one or more folders. |
|
||||
| `folders.permissions:write` | `folders:*`<br>`folders:id:*` | Update permissions for one or more folders. |
|
||||
| `annotations.read` | `annotations:*`<br>`annotations:type:*` | Read annotations and annotation tags. |
|
||||
| `annotations.create` | `annotations:*`<br>`annotations:type:*` | Create annotations. |
|
||||
| `annotations.write` | `annotations:*`<br>`annotations:type:*` | Update annotations. |
|
||||
| `annotations.delete` | `annotations:*`<br>`annotations:type:*` | Delete annotations. |
|
||||
|
||||
## Scope definitions
|
||||
|
||||
The following list contains fine-grained access control scopes.
|
||||
|
||||
| Scopes | Descriptions |
|
||||
| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `permissions:delegate` | The scope is only applicable for roles associated with the Access Control itself and indicates that you can delegate your permissions only, or a subset of it, by creating a new role or making an assignment. |
|
||||
| `roles:*` <br> `roles:uid:*` | Restrict an action to a set of roles. For example, `roles:*` matches any role and `roles:uid:randomuid` matches only the role whose UID is `randomuid`. |
|
||||
| `reports:*` <br> `reports:id:*` | Restrict an action to a set of reports. For example, `reports:*` matches any report and `reports:id:1` matches the report whose ID is `1`. |
|
||||
| `services:accesscontrol` | Restrict an action to target only the fine-grained access control service. You can use this in conjunction with the `status:accesscontrol` actions. |
|
||||
| `global.users:*` <br> `global.users:id:*` | Restrict an action to a set of global users. For example, `global.users:*` matches any user and `global.users:id:1` matches the user whose ID is `1`. |
|
||||
| `teams:*` <br> `teams:id:*` | Restrict an action to a set of teams from an organization. For example, `teams:*` matches any team and `teams:id:1` matches the team whose ID is `1`. |
|
||||
| `users:*` <br> `users:id:*` | Restrict an action to a set of users from an organization. For example, `users:*` matches any user and `users:id:1` matches the user whose ID is `1`. |
|
||||
| `orgs:*` <br> `orgs:id:*` | Restrict an action to a set of organizations. For example, `orgs:*` matches any organization and `orgs:id:1` matches the organization whose ID is `1`. |
|
||||
| `settings:*` | Restrict an action to a subset of settings. For example, `settings:*` matches all settings, `settings:auth.saml:*` matches all SAML settings, and `settings:auth.saml:enabled` matches the enable property on the SAML settings. |
|
||||
| `provisioners:*` | Restrict an action to a set of provisioners. For example, `provisioners:*` matches any provisioner, and `provisioners:accesscontrol` matches the fine-grained access control [provisioner]({{< relref "./provisioning.md" >}}). |
|
||||
| `datasources:*`<br>`datasources:id:*`<br>`datasources:uid:*`<br>`datasources:name:*` | Restrict an action to a set of data sources. For example, `datasources:*` matches any data source, and `datasources:name:postgres` matches the data source named `postgres`. |
|
||||
| `folders:*`<br>`folders:id:*` | Restrict an action to a set of folders. For example, `folders:*` matches any folder, and `folders:id:1` matches the folder whose ID is `1`. |
|
||||
| `dashboards:*`<br>`dashboards:id:*` | Restrict an action to a set of dashboards. For example, `dashboards:*` matches any dashboard, and `dashboards:id:1` matches the dashboard whose ID is `1`. |
|
||||
| Scopes | Descriptions |
|
||||
| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `permissions:delegate` | The scope is only applicable for roles associated with the Access Control itself and indicates that you can delegate your permissions only, or a subset of it, by creating a new role or making an assignment. |
|
||||
| `roles:*` <br> `roles:uid:*` | Restrict an action to a set of roles. For example, `roles:*` matches any role and `roles:uid:randomuid` matches only the role whose UID is `randomuid`. |
|
||||
| `reports:*` <br> `reports:id:*` | Restrict an action to a set of reports. For example, `reports:*` matches any report and `reports:id:1` matches the report whose ID is `1`. |
|
||||
| `services:accesscontrol` | Restrict an action to target only the fine-grained access control service. You can use this in conjunction with the `status:accesscontrol` actions. |
|
||||
| `global.users:*` <br> `global.users:id:*` | Restrict an action to a set of global users. For example, `global.users:*` matches any user and `global.users:id:1` matches the user whose ID is `1`. |
|
||||
| `teams:*` <br> `teams:id:*` | Restrict an action to a set of teams from an organization. For example, `teams:*` matches any team and `teams:id:1` matches the team whose ID is `1`. |
|
||||
| `users:*` <br> `users:id:*` | Restrict an action to a set of users from an organization. For example, `users:*` matches any user and `users:id:1` matches the user whose ID is `1`. |
|
||||
| `orgs:*` <br> `orgs:id:*` | Restrict an action to a set of organizations. For example, `orgs:*` matches any organization and `orgs:id:1` matches the organization whose ID is `1`. |
|
||||
| `settings:*` | Restrict an action to a subset of settings. For example, `settings:*` matches all settings, `settings:auth.saml:*` matches all SAML settings, and `settings:auth.saml:enabled` matches the enable property on the SAML settings. |
|
||||
| `provisioners:*` | Restrict an action to a set of provisioners. For example, `provisioners:*` matches any provisioner, and `provisioners:accesscontrol` matches the fine-grained access control [provisioner]({{< relref "./provisioning.md" >}}). |
|
||||
| `datasources:*`<br>`datasources:id:*`<br>`datasources:uid:*`<br>`datasources:name:*` | Restrict an action to a set of data sources. For example, `datasources:*` matches any data source, and `datasources:name:postgres` matches the data source named `postgres`. |
|
||||
| `folders:*`<br>`folders:id:*` | Restrict an action to a set of folders. For example, `folders:*` matches any folder, and `folders:id:1` matches the folder whose ID is `1`. |
|
||||
| `dashboards:*`<br>`dashboards:id:*` | Restrict an action to a set of dashboards. For example, `dashboards:*` matches any dashboard, and `dashboards:id:1` matches the dashboard whose ID is `1`. |
|
||||
| `annotations:*`<br>`annotations:type:*` | Restrict an action to a set of annotations. For example, `annotations:*` matches any annotation, `annotations:type:dashboard` matches annotations associated with dashboards and `annotations:type:organization` matches organization annotations. |
|
||||
|
||||
@@ -5,14 +5,25 @@ keywords = ["grafana", "http", "documentation", "api", "annotation", "annotation
|
||||
aliases = ["/docs/grafana/latest/http_api/annotations/"]
|
||||
+++
|
||||
|
||||
# Annotations resources / actions
|
||||
# Annotations API
|
||||
|
||||
This is the API documentation for the new Grafana Annotations feature released in Grafana 4.6. Annotations are saved in the Grafana database (sqlite, mysql or postgres). Annotations can be global annotations that can be shown on any dashboard by configuring an annotation data source - they are filtered by tags. Or they can be tied to a panel on a dashboard and are then only shown on that panel.
|
||||
This is the API documentation for the new Grafana Annotations feature released in Grafana 4.6. Annotations are saved in the Grafana database (sqlite, mysql or postgres). Annotations can be organization annotations that can be shown on any dashboard by configuring an annotation data source - they are filtered by tags. Or they can be tied to a panel on a dashboard and are then only shown on that panel.
|
||||
|
||||
> If you are running Grafana Enterprise and have [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) enabled, access to endpoints will be controlled by Fine-grained access control permissions.
|
||||
> Refer to specific endpoints to understand what permissions are required.
|
||||
|
||||
## Find Annotations
|
||||
|
||||
`GET /api/annotations?from=1506676478816&to=1507281278816&tags=tag1&tags=tag2&limit=100`
|
||||
|
||||
#### Required permissions
|
||||
|
||||
See note in the [introduction]({{< ref "#annotations-api" >}}) for an explanation.
|
||||
|
||||
| Action | Scope |
|
||||
| ---------------- | -------------- |
|
||||
| annotations:read | annotations:\* |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
@@ -32,7 +43,7 @@ Query Parameters:
|
||||
- `panelId`: number. Optional. Find annotations that are scoped to a specific panel
|
||||
- `userId`: number. Optional. Find annotations created by a specific user
|
||||
- `type`: string. Optional. `alert`|`annotation` Return alerts or user created annotations
|
||||
- `tags`: string. Optional. Use this to filter global annotations. Global annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. To do an "AND" filtering with multiple tags, specify the tags parameter multiple times e.g. `tags=tag1&tags=tag2`.
|
||||
- `tags`: string. Optional. Use this to filter organization annotations. Organization annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. To do an "AND" filtering with multiple tags, specify the tags parameter multiple times e.g. `tags=tag1&tags=tag2`.
|
||||
|
||||
**Example Response**:
|
||||
|
||||
@@ -85,13 +96,21 @@ Content-Type: application/json
|
||||
## Create Annotation
|
||||
|
||||
Creates an annotation in the Grafana database. The `dashboardId` and `panelId` fields are optional.
|
||||
If they are not specified then a global annotation is created and can be queried in any dashboard that adds
|
||||
If they are not specified then an organization annotation is created and can be queried in any dashboard that adds
|
||||
the Grafana annotations data source. When creating a region annotation include the timeEnd property.
|
||||
|
||||
The format for `time` and `timeEnd` should be epoch numbers in millisecond resolution.
|
||||
|
||||
`POST /api/annotations`
|
||||
|
||||
#### Required permissions
|
||||
|
||||
See note in the [introduction]({{< ref "#annotations-api" >}}) for an explanation.
|
||||
|
||||
| Action | Scope |
|
||||
| ------------------ | ----------------------- |
|
||||
| annotations:create | annotations:type:<type> |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
@@ -132,6 +151,14 @@ format (string with multiple tags being separated by a space).
|
||||
|
||||
`POST /api/annotations/graphite`
|
||||
|
||||
#### Required permissions
|
||||
|
||||
See note in the [introduction]({{< ref "#annotations-api" >}}) for an explanation.
|
||||
|
||||
| Action | Scope |
|
||||
| ------------------ | ----------------------------- |
|
||||
| annotations:create | annotations:type:organization |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
@@ -165,6 +192,14 @@ Content-Type: application/json
|
||||
|
||||
Updates all properties of an annotation that matches the specified id. To only update certain property, consider using the [Patch Annotation](#patch-annotation) operation.
|
||||
|
||||
#### Required permissions
|
||||
|
||||
See note in the [introduction]({{< ref "#annotations-api" >}}) for an explanation.
|
||||
|
||||
| Action | Scope |
|
||||
| ----------------- | ----------------------- |
|
||||
| annotations:write | annotations:type:<type> |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
@@ -202,6 +237,14 @@ Updates one or more properties of an annotation that matches the specified id.
|
||||
|
||||
This operation currently supports updating of the `text`, `tags`, `time` and `timeEnd` properties.
|
||||
|
||||
#### Required permissions
|
||||
|
||||
See note in the [introduction]({{< ref "#annotations-api" >}}) for an explanation.
|
||||
|
||||
| Action | Scope |
|
||||
| ----------------- | ----------------------- |
|
||||
| annotations:write | annotations:type:<type> |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
@@ -233,6 +276,14 @@ Content-Type: application/json
|
||||
|
||||
Deletes the annotation that matches the specified id.
|
||||
|
||||
#### Required permissions
|
||||
|
||||
See note in the [introduction]({{< ref "#annotations-api" >}}) for an explanation.
|
||||
|
||||
| Action | Scope |
|
||||
| ------------------ | ----------------------- |
|
||||
| annotations:delete | annotations:type:<type> |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
@@ -259,6 +310,14 @@ Content-Type: application/json
|
||||
|
||||
Find all the event tags created in the annotations.
|
||||
|
||||
#### Required permissions
|
||||
|
||||
See note in the [introduction]({{< ref "#annotations-api" >}}) for an explanation.
|
||||
|
||||
| Action | Scope |
|
||||
| ---------------- | ----- |
|
||||
| annotations:read | N/A |
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
|
||||
@@ -6,6 +6,6 @@ aliases = ["/docs/sources/panels/library-panels/about-library-panels/"]
|
||||
|
||||
# About Grafana panel libraries
|
||||
|
||||
A library panel is a reusable panel that you can use in any dashboard. When you make a change to a library panel, that change propegates to all instances of where the panel is used. Library panels streamline reuse of panels across multiple dashboards.
|
||||
A library panel is a reusable panel that you can use in any dashboard. When you make a change to a library panel, that change propagates to all instances of where the panel is used. Library panels streamline reuse of panels across multiple dashboards.
|
||||
|
||||
You can save a library panel in a folder alongside saved dashboards.
|
||||
|
||||
@@ -8,6 +8,7 @@ weight = 10000
|
||||
Here you can find detailed release notes that list everything that is included in every release as well as notices
|
||||
about deprecations, breaking changes as well as changes that relate to plugin development.
|
||||
|
||||
- [Release notes for 8.4.5]({{< relref "release-notes-8-4-5" >}})
|
||||
- [Release notes for 8.4.4]({{< relref "release-notes-8-4-4" >}})
|
||||
- [Release notes for 8.4.3]({{< relref "release-notes-8-4-3" >}})
|
||||
- [Release notes for 8.4.2]({{< relref "release-notes-8-4-2" >}})
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
+++
|
||||
title = "Release notes for Grafana 8.4.5"
|
||||
hide_menu = true
|
||||
+++
|
||||
|
||||
<!-- Auto generated by update changelog github action -->
|
||||
|
||||
# Release notes for Grafana 8.4.5
|
||||
|
||||
### Features and enhancements
|
||||
|
||||
- **Instrumentation:** Make backend plugin metrics endpoints available with optional authentication. [#46467](https://github.com/grafana/grafana/pull/46467), [@marefr](https://github.com/marefr)
|
||||
- **Table panel:** Show datalinks for cell display modes JSON View and Gauge derivates. [#46020](https://github.com/grafana/grafana/pull/46020), [@mdvictor](https://github.com/mdvictor)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- **Azure Monitor:** Small bug fixes for Resource Picker. [#46665](https://github.com/grafana/grafana/pull/46665), [@sarahzinger](https://github.com/sarahzinger)
|
||||
- **Logger:** Use specified format for file logger. [#46970](https://github.com/grafana/grafana/pull/46970), [@sakjur](https://github.com/sakjur)
|
||||
- **Logs:** Handle missing fields in dataframes better. [#46963](https://github.com/grafana/grafana/pull/46963), [@gabor](https://github.com/gabor)
|
||||
- **ManageDashboards:** Fix error when deleting all dashboards from folder view. [#46877](https://github.com/grafana/grafana/pull/46877), [@joshhunt](https://github.com/joshhunt)
|
||||
@@ -105,3 +105,7 @@ Enables value inspection from table cell. The raw value is presented in a modal
|
||||
## Column filter
|
||||
|
||||
You can temporarily change how column data is displayed. For example, you can order values from highest to lowest or hide specific values. For more information, refer to [Filter table columns]({{< relref "./filter-table-columns.md" >}}).
|
||||
|
||||
## Pagination
|
||||
|
||||
Use this option to enable or disable pagination. It is a front-end option that does not affect queries. When enabled, the page size automatically adjusts to the height of the table.
|
||||
|
||||
@@ -102,7 +102,7 @@ e2e.scenario({
|
||||
e2e.components.PanelEditor.DataPane.content().should('be.visible');
|
||||
|
||||
// Field & Overrides tabs (need to switch to React based vis, i.e. Table)
|
||||
e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Show header').should('be.visible');
|
||||
e2e.components.PanelEditor.OptionsPane.fieldLabel('Header and footer Show header').should('be.visible');
|
||||
e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Column width').should('be.visible');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,8 +17,6 @@ require (
|
||||
cloud.google.com/go/storage v1.18.2
|
||||
cuelang.org/go v0.4.0
|
||||
github.com/Azure/azure-sdk-for-go v59.3.0+incompatible
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.10.0
|
||||
github.com/Azure/go-autorest/autorest v0.11.22
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
@@ -54,6 +52,7 @@ require (
|
||||
github.com/gosimple/slug v1.9.0
|
||||
github.com/grafana/cuetsy v0.0.0-20211119211437-8c25464cc9bf
|
||||
github.com/grafana/grafana-aws-sdk v0.10.1
|
||||
github.com/grafana/grafana-azure-sdk-go v1.0.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.129.0
|
||||
github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
@@ -183,7 +182,7 @@ require (
|
||||
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/flatbuffers v2.0.0+incompatible // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.1
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
|
||||
@@ -247,19 +246,20 @@ require (
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1
|
||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/kms v1.1.0
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.17
|
||||
github.com/golang-migrate/migrate/v4 v4.7.0
|
||||
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f
|
||||
gocloud.dev v0.24.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.10.0 // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.17 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
|
||||
@@ -270,6 +270,7 @@ require (
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
|
||||
github.com/getkin/kin-openapi v0.91.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
|
||||
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/klauspost/compress v1.13.6 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
|
||||
@@ -1335,6 +1335,8 @@ github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036 h1:GplhUk6Xes5J
|
||||
github.com/grafana/go-mssqldb v0.0.0-20210326084033-d0ce3c521036/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/grafana/grafana-aws-sdk v0.10.1 h1:Ksguhjx6EuGLN/5Oc7oZoxuDReJ5RxIH99yqSMpLGUs=
|
||||
github.com/grafana/grafana-aws-sdk v0.10.1/go.mod h1:vFIOHEnY1u5nY0/tge1IHQjPuG6DRKr2ISf/HikUdjE=
|
||||
github.com/grafana/grafana-azure-sdk-go v1.0.0 h1:RIVQyVb89/y/BnOVsVDcxiMtmWF8NmAX8ql0OJvzwNc=
|
||||
github.com/grafana/grafana-azure-sdk-go v1.0.0/go.mod h1:xbzMaG74BN4rOP1NYEsCMNWkPbK7GfSU09PGYfQYm+g=
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 h1:2ud7NNM7LrGPO4x0NFR8qLq68CqI4SmB7I2yRN2w9oE=
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.94.0/go.mod h1:3VXz4nCv6wH5SfgB3mlW39s+c+LetqSCjFj7xxPC5+M=
|
||||
@@ -2462,8 +2464,6 @@ github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBn
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20220110192739-f9ce83ec349f h1:2upw/ZfjkCKpc4k6DXg7lMfCSLkfw/8epV5/y2ZUQ8U=
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20220110192739-f9ce83ec349f/go.mod h1:PnwzbSst7KD3vpBzzlntZU5gjVa455Uqa5QPiKSYJzQ=
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae h1:oyiy3uBj1F4O3AaFh7hUGBrJjAssJhKyAbwxtkslxqo=
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae/go.mod h1:PnwzbSst7KD3vpBzzlntZU5gjVa455Uqa5QPiKSYJzQ=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "8.4.4",
|
||||
"testing": "8.4.4"
|
||||
"stable": "8.4.5",
|
||||
"testing": "8.4.5"
|
||||
}
|
||||
|
||||
+7
-2
@@ -54,7 +54,8 @@
|
||||
"i18n:extract": "lingui extract",
|
||||
"i18n:compile": "lingui compile",
|
||||
"postinstall": "husky install",
|
||||
"betterer": "betterer"
|
||||
"betterer": "betterer",
|
||||
"betterer:stats": "ts-node --transpile-only --project ./scripts/cli/tsconfig.json ./scripts/cli/reportBettererStats.ts"
|
||||
},
|
||||
"grafana": {
|
||||
"whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v8-4/",
|
||||
@@ -86,6 +87,7 @@
|
||||
"@babel/preset-env": "7.16.11",
|
||||
"@babel/preset-react": "7.16.7",
|
||||
"@babel/preset-typescript": "7.16.7",
|
||||
"@betterer/betterer": "5.1.7",
|
||||
"@betterer/cli": "5.1.7",
|
||||
"@betterer/regexp": "5.1.7",
|
||||
"@emotion/eslint-plugin": "11.7.0",
|
||||
@@ -128,9 +130,10 @@
|
||||
"@types/hoist-non-react-statics": "3.3.1",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/jquery": "3.5.14",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsurl": "^1.2.28",
|
||||
"@types/lingui__macro": "^3",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/lodash": "4.14.181",
|
||||
"@types/logfmt": "^1.2.1",
|
||||
"@types/mousetrap": "1.6.9",
|
||||
"@types/node": "16.11.26",
|
||||
@@ -246,6 +249,7 @@
|
||||
"@grafana/e2e-selectors": "workspace:*",
|
||||
"@grafana/experimental": "0.0.2-canary.22",
|
||||
"@grafana/google-sdk": "0.0.3",
|
||||
"@grafana/lezer-logql": "^0.0.11",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/slate-react": "0.22.10-grafana",
|
||||
@@ -310,6 +314,7 @@
|
||||
"immer": "9.0.12",
|
||||
"immutable": "4.0.0",
|
||||
"jquery": "3.6.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-source-map": "0.6.1",
|
||||
"jsurl": "^0.1.5",
|
||||
"lezer-promql": "0.22.0",
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"@types/history": "4.7.11",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/jquery": "3.5.14",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/lodash": "4.14.181",
|
||||
"@types/marked": "4.0.3",
|
||||
"@types/node": "16.11.26",
|
||||
"@types/papaparse": "5.3.2",
|
||||
|
||||
@@ -202,7 +202,8 @@ export function dataFrameToJSON(frame: DataFrame): DataFrameJSON {
|
||||
meta: frame.meta,
|
||||
name: frame.name,
|
||||
fields: frame.fields.map((f) => {
|
||||
const { values, ...sfield } = f;
|
||||
const { values, state, display, ...sfield } = f;
|
||||
delete (sfield as any).entities;
|
||||
data.values.push(values.toArray());
|
||||
return sfield;
|
||||
}),
|
||||
|
||||
@@ -84,7 +84,7 @@ export const getTimeZoneGroups = memoize(
|
||||
return groups;
|
||||
}
|
||||
|
||||
const group = zone.substr(0, delimiter);
|
||||
const group = zone.slice(0, delimiter);
|
||||
groups[group] = groups[group] ?? [];
|
||||
groups[group].push(zone);
|
||||
|
||||
|
||||
@@ -414,6 +414,46 @@ describe('Date display options', () => {
|
||||
expect(processor('2020-08-01T08:48:43.783337Z').text).toEqual('2020-08-01 08:48:43');
|
||||
});
|
||||
|
||||
it('should handle ISO string dates when in other timezones than UTC', () => {
|
||||
const processor = getDisplayProcessor({
|
||||
timeZone: 'CET',
|
||||
field: {
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
},
|
||||
theme: createTheme(),
|
||||
});
|
||||
|
||||
expect(processor('2020-08-01T08:48:43.783337Z').text).toEqual('2020-08-01 10:48:43'); //DST
|
||||
expect(processor('2020-12-01T08:48:43.783337Z').text).toEqual('2020-12-01 09:48:43'); //STD
|
||||
});
|
||||
|
||||
it('should handle ISO string dates with timezone offset', () => {
|
||||
const processor = getDisplayProcessor({
|
||||
timeZone: 'utc',
|
||||
field: {
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
},
|
||||
theme: createTheme(),
|
||||
});
|
||||
|
||||
expect(processor('2020-12-01T08:48:43.783337+02:00').text).toEqual('2020-12-01 06:48:43');
|
||||
});
|
||||
|
||||
it('should handle ISO string dates without timezone qualifier by assuming UTC', () => {
|
||||
const processor = getDisplayProcessor({
|
||||
timeZone: 'CET',
|
||||
field: {
|
||||
type: FieldType.time,
|
||||
config: {},
|
||||
},
|
||||
theme: createTheme(),
|
||||
});
|
||||
|
||||
expect(processor('2020-12-01T08:48:43.783337').text).toEqual('2020-12-01 09:48:43');
|
||||
});
|
||||
|
||||
describe('number formatting for string values', () => {
|
||||
it('should preserve string unchanged if unit is strings', () => {
|
||||
const processor = getDisplayProcessor({
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Field, FieldType } from '../types/dataFrame';
|
||||
import { DisplayProcessor, DisplayValue } from '../types/displayValue';
|
||||
import { getValueFormat, isBooleanUnit } from '../valueFormats/valueFormats';
|
||||
import { getValueMappingResult } from '../utils/valueMappings';
|
||||
import { dateTime, dateTimeParse } from '../datetime';
|
||||
import { toUtc, dateTimeParse } from '../datetime';
|
||||
import { KeyValue, TimeZone } from '../types';
|
||||
import { getScaleCalculator } from './scale';
|
||||
import { GrafanaTheme2 } from '../themes/types';
|
||||
@@ -77,7 +77,7 @@ export function getDisplayProcessor(options?: DisplayProcessorOptions): DisplayP
|
||||
const isStringUnit = unit === 'string';
|
||||
|
||||
if (hasDateUnit && typeof value === 'string') {
|
||||
value = dateTime(value).valueOf();
|
||||
value = toUtc(value).valueOf();
|
||||
}
|
||||
|
||||
let numeric = isStringUnit ? NaN : anyToNumber(value);
|
||||
|
||||
@@ -57,12 +57,32 @@ describe('stringToMs', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('[un]escapeStringForRegex', () => {
|
||||
it.each([
|
||||
'[]',
|
||||
'\\',
|
||||
'[(abc])',
|
||||
'onetwothree',
|
||||
'<namedgroup}(this is not a regex>',
|
||||
'string\\with\\backslash',
|
||||
'everyspecialchar([{])}.,/?&*-^&<>#',
|
||||
])('should be symmetric', (input) => {
|
||||
const output = unEscapeStringFromRegex(escapeStringForRegex(input));
|
||||
expect(output).toEqual(input);
|
||||
});
|
||||
});
|
||||
|
||||
describe('escapeStringForRegex', () => {
|
||||
describe('when using a string with special chars', () => {
|
||||
it('then all special chars should be escaped', () => {
|
||||
const result = escapeStringForRegex('([{}])|*+-.?<>#&^$');
|
||||
expect(result).toBe('\\(\\[\\{\\}\\]\\)\\|\\*\\+\\-\\.\\?\\<\\>\\#\\&\\^\\$');
|
||||
});
|
||||
it.each([
|
||||
'[[[',
|
||||
'[]\\',
|
||||
'[(abc])',
|
||||
'onetwothree',
|
||||
'<namedgroup}(this is not a regex>',
|
||||
'string\\with\\backslash',
|
||||
'everyspecialchar([{])}.,/?&*-^&<>#',
|
||||
])('should always produce output that compiles', (value) => {
|
||||
expect(() => new RegExp(escapeStringForRegex(value))).not.toThrowError();
|
||||
});
|
||||
|
||||
describe('when using a string without special chars', () => {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { camelCase } from 'lodash';
|
||||
const specialChars = ['(', '[', '{', '}', ']', ')', '|', '*', '+', '-', '.', '?', '<', '>', '#', '&', '^', '$'];
|
||||
|
||||
const specialChars = ['(', '[', '{', '}', ']', ')', '\\', '|', '*', '+', '-', '.', '?', '<', '>', '#', '&', '^', '$'];
|
||||
const specialMatcher = '([\\' + specialChars.join('\\') + '])';
|
||||
const specialCharEscape = new RegExp(specialMatcher, 'g');
|
||||
const specialCharUnescape = new RegExp('(\\\\)' + specialMatcher, 'g');
|
||||
|
||||
export const escapeStringForRegex = (value: string) => {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return specialChars.reduce((escaped, currentChar) => escaped.replace(currentChar, '\\' + currentChar), value);
|
||||
return value.replace(specialCharEscape, '\\$1');
|
||||
};
|
||||
|
||||
export const unEscapeStringFromRegex = (value: string) => {
|
||||
@@ -14,7 +18,7 @@ export const unEscapeStringFromRegex = (value: string) => {
|
||||
return value;
|
||||
}
|
||||
|
||||
return specialChars.reduce((escaped, currentChar) => escaped.replace('\\' + currentChar, currentChar), value);
|
||||
return value.replace(specialCharUnescape, '$2');
|
||||
};
|
||||
|
||||
export function stringStartsAsRegEx(str: string): boolean {
|
||||
@@ -45,7 +49,7 @@ export function stringToMs(str: string): number {
|
||||
}
|
||||
|
||||
const nr = parseInt(str, 10);
|
||||
const unit = str.substr(String(nr).length);
|
||||
const unit = str.slice(String(nr).length);
|
||||
const s = 1000;
|
||||
const m = s * 60;
|
||||
const h = m * 60;
|
||||
|
||||
@@ -29,7 +29,7 @@ function clamp(value: number, min = 0, max = 1) {
|
||||
* @beta
|
||||
*/
|
||||
export function hexToRgb(color: string) {
|
||||
color = color.substr(1);
|
||||
color = color.slice(1);
|
||||
|
||||
const re = new RegExp(`.{1,${color.length >= 6 ? 2 : 1}}`, 'g');
|
||||
let colors = color.match(re);
|
||||
@@ -141,7 +141,7 @@ export function decomposeColor(color: string | DecomposeColor): DecomposeColor {
|
||||
values = values.split(' ');
|
||||
colorSpace = values.shift();
|
||||
if (values.length === 4 && values[3].charAt(0) === '/') {
|
||||
values[3] = values[3].substr(1);
|
||||
values[3] = values[3].slice(1);
|
||||
}
|
||||
if (['srgb', 'display-p3', 'a98-rgb', 'prophoto-rgb', 'rec-2020'].indexOf(colorSpace) === -1) {
|
||||
throw new Error(
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface FeatureToggles {
|
||||
tempoServiceGraph?: boolean;
|
||||
lokiBackendMode?: boolean;
|
||||
accesscontrol?: boolean;
|
||||
['accesscontrol-builtins']?: boolean;
|
||||
prometheus_azure_auth?: boolean;
|
||||
influxdbBackendMigration?: boolean;
|
||||
newNavigation?: boolean;
|
||||
@@ -46,7 +47,9 @@ export interface FeatureToggles {
|
||||
dashboardComments?: boolean;
|
||||
annotationComments?: boolean;
|
||||
migrationLocking?: boolean;
|
||||
saveDashboardDrawer?: boolean;
|
||||
storage?: boolean;
|
||||
alertProvisioning?: boolean;
|
||||
storageLocalUpload?: boolean;
|
||||
azureMonitorResourcePickerForMetrics?: boolean;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ export class CSVReader {
|
||||
// #{columkey}#a,b,c
|
||||
const idx = first.indexOf('#', 2);
|
||||
if (idx > 0) {
|
||||
const k = first.substr(1, idx - 1);
|
||||
const k = first.slice(1, idx);
|
||||
const isName = 'name' === k;
|
||||
|
||||
// Simple object used to check if headers match
|
||||
@@ -103,7 +103,7 @@ export class CSVReader {
|
||||
this.data.push(this.current);
|
||||
}
|
||||
|
||||
const v = first.substr(idx + 1);
|
||||
const v = first.slice(idx + 1);
|
||||
if (isName) {
|
||||
this.current.addFieldFor(undefined, v);
|
||||
for (let j = 1; j < line.length; j++) {
|
||||
|
||||
@@ -66,7 +66,7 @@ export function toFixed(value: number, decimals?: DecimalCount): string {
|
||||
const decimalPos = formatted.indexOf('.');
|
||||
const precision = decimalPos === -1 ? 0 : formatted.length - decimalPos - 1;
|
||||
if (precision < decimals) {
|
||||
return (precision ? formatted : formatted + '.') + String(factor).substr(1, decimals - precision);
|
||||
return (precision ? formatted : formatted + '.') + String(factor).slice(1, decimals - precision + 1);
|
||||
}
|
||||
|
||||
return formatted;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"@rollup/plugin-commonjs": "21.0.2",
|
||||
"@rollup/plugin-node-resolve": "13.1.3",
|
||||
"@types/chrome-remote-interface": "0.31.4",
|
||||
"@types/lodash": "4.14.180",
|
||||
"@types/lodash": "4.14.181",
|
||||
"@types/node": "16.11.26",
|
||||
"@types/uuid": "8.3.4",
|
||||
"rollup": "2.70.1",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"@types/angular": "1.8.4",
|
||||
"@types/history": "4.7.11",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/lodash": "4.14.181",
|
||||
"@types/react": "17.0.42",
|
||||
"@types/react-dom": "17.0.14",
|
||||
"@types/systemjs": "^0.20.6",
|
||||
|
||||
@@ -105,7 +105,7 @@ export class DataSourcePicker extends PureComponent<DataSourcePickerProps, DataS
|
||||
|
||||
if (ds) {
|
||||
return {
|
||||
label: ds.name.substr(0, 37),
|
||||
label: ds.name.slice(0, 37),
|
||||
value: ds.uid,
|
||||
imgUrl: ds.meta.info.logos.small,
|
||||
hideText: hideTextValue,
|
||||
|
||||
@@ -9,5 +9,10 @@
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"extends": "@grafana/tsconfig",
|
||||
"include": ["src/**/*.ts*", "../../public/app/types/jquery/*.ts", "../../public/app/types/*.d.ts"]
|
||||
"include": [
|
||||
"src/**/*.ts*",
|
||||
"../../public/app/types/jquery/*.ts",
|
||||
"../../public/app/types/*.d.ts",
|
||||
"../grafana-ui/src/types/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ var fs = require('fs');
|
||||
var githubClient_1 = tslib_1.__importDefault(require('./githubClient'));
|
||||
var resolveContentType = function (extension) {
|
||||
if (extension.startsWith('.')) {
|
||||
extension = extension.substr(1);
|
||||
extension = extension.slice(1);
|
||||
}
|
||||
switch (extension) {
|
||||
case 'zip':
|
||||
|
||||
@@ -8,7 +8,7 @@ import { AxiosResponse } from 'axios';
|
||||
|
||||
const resolveContentType = (extension: string): string => {
|
||||
if (extension.startsWith('.')) {
|
||||
extension = extension.substr(1);
|
||||
extension = extension.slice(1);
|
||||
}
|
||||
switch (extension) {
|
||||
case 'zip':
|
||||
|
||||
@@ -197,7 +197,7 @@ const getBaseWebpackConfig: WebpackConfigurationGetter = async (options) => {
|
||||
(context, request, callback) => {
|
||||
const prefix = 'grafana/';
|
||||
if (request.indexOf(prefix) === 0) {
|
||||
return callback(null, request.substr(prefix.length));
|
||||
return callback(null, request.slice(prefix.length));
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
"@types/is-hotkey": "0.1.7",
|
||||
"@types/jest": "27.4.1",
|
||||
"@types/jquery": "3.5.14",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/lodash": "4.14.181",
|
||||
"@types/mock-raf": "1.0.3",
|
||||
"@types/node": "16.11.26",
|
||||
"@types/prismjs": "1.26.0",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { Tooltip, PopoverContent } from '../Tooltip';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
import { useTheme } from '../../themes';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { PopoverContent, Tooltip } from '../Tooltip';
|
||||
import { LabelProps } from './Label';
|
||||
|
||||
export interface Props extends Omit<LabelProps, 'css' | 'description' | 'category'> {
|
||||
@@ -41,7 +42,7 @@ export const InlineLabel: FunctionComponent<Props> = ({
|
||||
{children}
|
||||
{tooltip && (
|
||||
<Tooltip placement="top" content={tooltip} theme="info">
|
||||
<Icon name="info-circle" size="sm" className={styles.icon} />
|
||||
<Icon tabIndex={0} name="info-circle" size="sm" className={styles.icon} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Component>
|
||||
|
||||
@@ -216,7 +216,7 @@ export class GraphNG extends React.Component<GraphNGProps, GraphNGState> {
|
||||
|
||||
const propsChanged = !sameProps(prevProps, this.props, propsToDiff);
|
||||
|
||||
if (frames !== prevProps.frames || propsChanged) {
|
||||
if (frames !== prevProps.frames || propsChanged || timeZone !== prevProps.timeZone) {
|
||||
let newState = this.prepState(this.props, false);
|
||||
|
||||
if (newState) {
|
||||
|
||||
@@ -112,6 +112,22 @@ describe('nullInsertThreshold Transformer', () => {
|
||||
expect(result.fields[0].values.toArray()).toStrictEqual([1, 2, 3, 4, 10, 11]);
|
||||
expect(result.fields[1].values.toArray()).toStrictEqual([4, null, 6, null, 8, null]);
|
||||
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c', null]);
|
||||
|
||||
// should work for frames with 1 datapoint
|
||||
const df2 = new MutableDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1] },
|
||||
{ name: 'One', type: FieldType.number, values: [1] },
|
||||
{ name: 'Two', type: FieldType.string, values: ['a'] },
|
||||
],
|
||||
});
|
||||
|
||||
const result2 = applyNullInsertThreshold(df2, null, 13);
|
||||
|
||||
expect(result2.fields[0].values.toArray()).toStrictEqual([1, 2]);
|
||||
expect(result2.fields[1].values.toArray()).toStrictEqual([1, null]);
|
||||
expect(result2.fields[2].values.toArray()).toStrictEqual(['a', null]);
|
||||
});
|
||||
|
||||
// TODO: make this work
|
||||
@@ -132,12 +148,12 @@ describe('nullInsertThreshold Transformer', () => {
|
||||
expect(result.fields[2].values.toArray()).toStrictEqual(['a', null, 'b', null, 'c']);
|
||||
});
|
||||
|
||||
test('should noop on fewer than two values', () => {
|
||||
test('should noop on 0 datapoints', () => {
|
||||
const df = new MutableDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [1] },
|
||||
{ name: 'Value', type: FieldType.number, values: [1] },
|
||||
{ name: 'Time', type: FieldType.time, config: { interval: 1 }, values: [] },
|
||||
{ name: 'Value', type: FieldType.number, values: [] },
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export function applyNullInsertThreshold(
|
||||
refFieldPseudoMax: number | null = null,
|
||||
insertMode: InsertMode = INSERT_MODES.threshold
|
||||
): DataFrame {
|
||||
if (frame.length < 2) {
|
||||
if (frame.length === 0) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Pagination } from './Pagination';
|
||||
|
||||
describe('Pagination component', () => {
|
||||
it('should render only 10 buttons when number of pages is higher than 8', () => {
|
||||
render(<Pagination currentPage={1} numberOfPages={90} onNavigate={() => {}} />);
|
||||
expect(screen.getAllByRole('button')).toHaveLength(10);
|
||||
});
|
||||
it('should only show 3 buttons when showSmallVersion is true', () => {
|
||||
render(<Pagination currentPage={1} numberOfPages={90} onNavigate={() => {}} showSmallVersion />);
|
||||
expect(screen.getAllByRole('button')).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
@@ -1,77 +1,94 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { stylesFactory } from '../../themes';
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { Button, ButtonVariant } from '../Button';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
|
||||
const PAGE_LENGTH_TO_CONDENSE = 8;
|
||||
|
||||
export interface Props {
|
||||
/** The current page index being shown. */
|
||||
/** The current page index being shown. */
|
||||
currentPage: number;
|
||||
/** Number of total pages. */
|
||||
/** Number of total pages. */
|
||||
numberOfPages: number;
|
||||
/** Callback function for fetching the selected page */
|
||||
/** Callback function for fetching the selected page. */
|
||||
onNavigate: (toPage: number) => void;
|
||||
/** When set to true and the pagination result is only one page it will not render the pagination at all */
|
||||
/** When set to true and the pagination result is only one page it will not render the pagination at all. */
|
||||
hideWhenSinglePage?: boolean;
|
||||
/** Small version only shows the current page and the navigation buttons. */
|
||||
showSmallVersion?: boolean;
|
||||
}
|
||||
|
||||
export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavigate, hideWhenSinglePage }) => {
|
||||
const styles = getStyles();
|
||||
const pages = [...new Array(numberOfPages).keys()];
|
||||
export const Pagination: React.FC<Props> = ({
|
||||
currentPage,
|
||||
numberOfPages,
|
||||
onNavigate,
|
||||
hideWhenSinglePage,
|
||||
showSmallVersion,
|
||||
}) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const pageLengthToCondense = showSmallVersion ? 1 : 8;
|
||||
|
||||
const condensePages = numberOfPages > PAGE_LENGTH_TO_CONDENSE;
|
||||
const getListItem = (page: number, variant: 'primary' | 'secondary') => (
|
||||
<li key={page} className={styles.item}>
|
||||
<Button size="sm" variant={variant} onClick={() => onNavigate(page)}>
|
||||
{page}
|
||||
</Button>
|
||||
</li>
|
||||
);
|
||||
const pageButtons = useMemo(() => {
|
||||
const pages = [...new Array(numberOfPages).keys()];
|
||||
|
||||
const pageButtons = pages.reduce<JSX.Element[]>((pagesToRender, pageIndex) => {
|
||||
const page = pageIndex + 1;
|
||||
const variant: ButtonVariant = page === currentPage ? 'primary' : 'secondary';
|
||||
const condensePages = numberOfPages > pageLengthToCondense;
|
||||
const getListItem = (page: number, variant: 'primary' | 'secondary') => (
|
||||
<li key={page} className={styles.item}>
|
||||
<Button size="sm" variant={variant} onClick={() => onNavigate(page)}>
|
||||
{page}
|
||||
</Button>
|
||||
</li>
|
||||
);
|
||||
|
||||
// The indexes at which to start and stop condensing pages
|
||||
const lowerBoundIndex = PAGE_LENGTH_TO_CONDENSE;
|
||||
const upperBoundIndex = numberOfPages - PAGE_LENGTH_TO_CONDENSE + 1;
|
||||
// When the indexes overlap one another this number is negative
|
||||
const differenceOfBounds = upperBoundIndex - lowerBoundIndex;
|
||||
return pages.reduce<JSX.Element[]>((pagesToRender, pageIndex) => {
|
||||
const page = pageIndex + 1;
|
||||
const variant: ButtonVariant = page === currentPage ? 'primary' : 'secondary';
|
||||
|
||||
const isFirstOrLastPage = page === 1 || page === numberOfPages;
|
||||
// This handles when the lowerBoundIndex < currentPage < upperBoundIndex
|
||||
const currentPageIsBetweenBounds =
|
||||
differenceOfBounds > -1 && currentPage >= lowerBoundIndex && currentPage <= upperBoundIndex;
|
||||
// The indexes at which to start and stop condensing pages
|
||||
const lowerBoundIndex = pageLengthToCondense;
|
||||
const upperBoundIndex = numberOfPages - pageLengthToCondense + 1;
|
||||
// When the indexes overlap one another this number is negative
|
||||
const differenceOfBounds = upperBoundIndex - lowerBoundIndex;
|
||||
|
||||
if (condensePages) {
|
||||
if (
|
||||
isFirstOrLastPage ||
|
||||
(currentPage < lowerBoundIndex && page < lowerBoundIndex) ||
|
||||
(differenceOfBounds >= 0 && currentPage > upperBoundIndex && page > upperBoundIndex) ||
|
||||
(differenceOfBounds < 0 && currentPage >= lowerBoundIndex && page > upperBoundIndex) ||
|
||||
(currentPageIsBetweenBounds && page >= currentPage - 2 && page <= currentPage + 2)
|
||||
) {
|
||||
// Renders a button for the page
|
||||
const isFirstOrLastPage = page === 1 || page === numberOfPages;
|
||||
// This handles when the lowerBoundIndex < currentPage < upperBoundIndex
|
||||
const currentPageIsBetweenBounds =
|
||||
differenceOfBounds > -1 && currentPage >= lowerBoundIndex && currentPage <= upperBoundIndex;
|
||||
|
||||
// Show ellipsis after that many pages
|
||||
const ellipsisOffset = showSmallVersion ? 1 : 3;
|
||||
|
||||
// The offset to show more pages when currentPageIsBetweenBounds
|
||||
const pageOffset = showSmallVersion ? 0 : 2;
|
||||
|
||||
if (condensePages) {
|
||||
if (
|
||||
isFirstOrLastPage ||
|
||||
(currentPage < lowerBoundIndex && page < lowerBoundIndex) ||
|
||||
(differenceOfBounds >= 0 && currentPage > upperBoundIndex && page > upperBoundIndex) ||
|
||||
(differenceOfBounds < 0 && currentPage >= lowerBoundIndex && page > upperBoundIndex) ||
|
||||
(currentPageIsBetweenBounds && page >= currentPage - pageOffset && page <= currentPage + pageOffset)
|
||||
) {
|
||||
// Renders a button for the page
|
||||
pagesToRender.push(getListItem(page, variant));
|
||||
} else if (
|
||||
(page === lowerBoundIndex && currentPage < lowerBoundIndex) ||
|
||||
(page === upperBoundIndex && currentPage > upperBoundIndex) ||
|
||||
(currentPageIsBetweenBounds &&
|
||||
(page === currentPage - ellipsisOffset || page === currentPage + ellipsisOffset))
|
||||
) {
|
||||
// Renders and ellipsis to represent condensed pages
|
||||
pagesToRender.push(
|
||||
<li key={page} className={styles.item}>
|
||||
<Icon className={styles.ellipsis} name="ellipsis-v" />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
pagesToRender.push(getListItem(page, variant));
|
||||
} else if (
|
||||
(page === lowerBoundIndex && currentPage < lowerBoundIndex) ||
|
||||
(page === upperBoundIndex && currentPage > upperBoundIndex) ||
|
||||
(currentPageIsBetweenBounds && (page === currentPage - 3 || page === currentPage + 3))
|
||||
) {
|
||||
// Renders and ellipsis to represent condensed pages
|
||||
pagesToRender.push(
|
||||
<li key={page} className={styles.item}>
|
||||
<Icon className={styles.ellipsis} name="ellipsis-v" />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
pagesToRender.push(getListItem(page, variant));
|
||||
}
|
||||
return pagesToRender;
|
||||
}, []);
|
||||
return pagesToRender;
|
||||
}, []);
|
||||
}, [currentPage, numberOfPages, onNavigate, pageLengthToCondense, showSmallVersion, styles.ellipsis, styles.item]);
|
||||
|
||||
if (hideWhenSinglePage && numberOfPages <= 1) {
|
||||
return null;
|
||||
@@ -108,7 +125,7 @@ export const Pagination: React.FC<Props> = ({ currentPage, numberOfPages, onNavi
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory(() => {
|
||||
const getStyles = () => {
|
||||
return {
|
||||
container: css`
|
||||
float: right;
|
||||
@@ -122,4 +139,4 @@ const getStyles = stylesFactory(() => {
|
||||
transform: rotate(90deg);
|
||||
`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -30,6 +30,8 @@ export interface PanelContext {
|
||||
onToggleSeriesVisibility?: (label: string, mode: SeriesVisibilityChangeMode) => void;
|
||||
|
||||
canAddAnnotations?: () => boolean;
|
||||
canEditAnnotations?: (dashboardId: number) => boolean;
|
||||
canDeleteAnnotations?: (dashboardId: number) => boolean;
|
||||
onAnnotationCreate?: (annotation: AnnotationEventUIModel) => void;
|
||||
onAnnotationUpdate?: (annotation: AnnotationEventUIModel) => void;
|
||||
onAnnotationDelete?: (id: string) => void;
|
||||
|
||||
@@ -10,18 +10,19 @@ export interface FooterRowProps {
|
||||
totalColumnsWidth: number;
|
||||
footerGroups: HeaderGroup[];
|
||||
footerValues: FooterItem[];
|
||||
isPaginationVisible: boolean;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const FooterRow = (props: FooterRowProps) => {
|
||||
const { totalColumnsWidth, footerGroups, height } = props;
|
||||
const { totalColumnsWidth, footerGroups, height, isPaginationVisible } = props;
|
||||
const e2eSelectorsTable = selectors.components.Panels.Visualization.Table;
|
||||
const tableStyles = useStyles2(getTableStyles);
|
||||
|
||||
return (
|
||||
<table
|
||||
style={{
|
||||
position: 'absolute',
|
||||
position: isPaginationVisible ? 'relative' : 'absolute',
|
||||
width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%',
|
||||
bottom: '0px',
|
||||
}}
|
||||
|
||||
@@ -29,7 +29,7 @@ export function JSONViewCell(props: TableCellProps): JSX.Element {
|
||||
return (
|
||||
<div {...cellProps} className={inspectEnabled ? tableStyles.cellContainerNoOverflow : tableStyles.cellContainer}>
|
||||
<div className={cx(tableStyles.cellText, txt)}>
|
||||
{!link && <div className={tableStyles.cellText}>{value}</div>}
|
||||
{!link && <div className={tableStyles.cellText}>{displayValue}</div>}
|
||||
{link && (
|
||||
<a
|
||||
href={link.href}
|
||||
|
||||
@@ -186,3 +186,8 @@ export const Footer: Story = (args) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Pagination: Story = (args) => <Basic {...args} />;
|
||||
Pagination.args = {
|
||||
pageSize: 10,
|
||||
};
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import React, { FC, memo, useCallback, useMemo } from 'react';
|
||||
import React, { FC, memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { DataFrame, getFieldDisplayName } from '@grafana/data';
|
||||
import {
|
||||
Cell,
|
||||
Column,
|
||||
TableState,
|
||||
useAbsoluteLayout,
|
||||
useFilters,
|
||||
UseFiltersState,
|
||||
usePagination,
|
||||
useResizeColumns,
|
||||
UseResizeColumnsState,
|
||||
useSortBy,
|
||||
UseSortByState,
|
||||
useTable,
|
||||
} from 'react-table';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
@@ -27,6 +26,7 @@ import { TableCell } from './TableCell';
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { FooterRow } from './FooterRow';
|
||||
import { HeaderRow } from './HeaderRow';
|
||||
import { Pagination } from '../Pagination/Pagination';
|
||||
|
||||
const COLUMN_MIN_WIDTH = 150;
|
||||
|
||||
@@ -45,13 +45,12 @@ export interface Props {
|
||||
onSortByChange?: TableSortByActionCallback;
|
||||
onCellFilterAdded?: TableFilterActionCallback;
|
||||
footerValues?: FooterItem[];
|
||||
enablePagination?: boolean;
|
||||
}
|
||||
|
||||
interface ReactTableInternalState extends UseResizeColumnsState<{}>, UseSortByState<{}>, UseFiltersState<{}> {}
|
||||
|
||||
function useTableStateReducer({ onColumnResize, onSortByChange, data }: Props) {
|
||||
return useCallback(
|
||||
(newState: ReactTableInternalState, action: any) => {
|
||||
(newState: TableState, action: any) => {
|
||||
switch (action.type) {
|
||||
case 'columnDoneResizing':
|
||||
if (onColumnResize) {
|
||||
@@ -95,8 +94,8 @@ function useTableStateReducer({ onColumnResize, onSortByChange, data }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
function getInitialState(initialSortBy: Props['initialSortBy'], columns: Column[]): Partial<ReactTableInternalState> {
|
||||
const state: Partial<ReactTableInternalState> = {};
|
||||
function getInitialState(initialSortBy: Props['initialSortBy'], columns: Column[]): Partial<TableState> {
|
||||
const state: Partial<TableState> = {};
|
||||
|
||||
if (initialSortBy) {
|
||||
state.sortBy = [];
|
||||
@@ -126,6 +125,7 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
initialSortBy,
|
||||
footerValues,
|
||||
showTypeIcons,
|
||||
enablePagination,
|
||||
} = props;
|
||||
|
||||
const tableStyles = useStyles2(getTableStyles);
|
||||
@@ -188,17 +188,40 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
[initialSortBy, memoizedColumns, memoizedData, resizable, stateReducer]
|
||||
);
|
||||
|
||||
const { getTableProps, headerGroups, rows, prepareRow, totalColumnsWidth, footerGroups } = useTable(
|
||||
options,
|
||||
useFilters,
|
||||
useSortBy,
|
||||
useAbsoluteLayout,
|
||||
useResizeColumns
|
||||
);
|
||||
const {
|
||||
getTableProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
totalColumnsWidth,
|
||||
footerGroups,
|
||||
page,
|
||||
state,
|
||||
gotoPage,
|
||||
setPageSize,
|
||||
pageOptions,
|
||||
} = useTable(options, useFilters, useSortBy, usePagination, useAbsoluteLayout, useResizeColumns);
|
||||
|
||||
let listHeight = height - (headerHeight + footerHeight);
|
||||
if (enablePagination) {
|
||||
listHeight -= tableStyles.cellHeight;
|
||||
}
|
||||
const pageSize = Math.round(listHeight / tableStyles.cellHeight) - 1;
|
||||
|
||||
useEffect(() => {
|
||||
// Don't update the page size if it is less than 1
|
||||
if (pageSize <= 0) {
|
||||
return;
|
||||
}
|
||||
setPageSize(pageSize);
|
||||
}, [pageSize, setPageSize]);
|
||||
|
||||
const RenderRow = React.useCallback(
|
||||
({ index: rowIndex, style }) => {
|
||||
const row = rows[rowIndex];
|
||||
let row = rows[rowIndex];
|
||||
if (enablePagination) {
|
||||
row = page[rowIndex];
|
||||
}
|
||||
prepareRow(row);
|
||||
return (
|
||||
<div {...row.getRowProps({ style })} className={tableStyles.row}>
|
||||
@@ -215,20 +238,52 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[onCellFilterAdded, prepareRow, rows, tableStyles]
|
||||
[onCellFilterAdded, page, enablePagination, prepareRow, rows, tableStyles]
|
||||
);
|
||||
|
||||
const listHeight = height - (headerHeight + footerHeight);
|
||||
const onNavigate = useCallback(
|
||||
(toPage: number) => {
|
||||
gotoPage(toPage - 1);
|
||||
},
|
||||
[gotoPage]
|
||||
);
|
||||
|
||||
const itemCount = enablePagination ? page.length : data.length;
|
||||
let paginationEl = null;
|
||||
if (enablePagination) {
|
||||
const itemsRangeStart = state.pageIndex * state.pageSize + 1;
|
||||
let itemsRangeEnd = itemsRangeStart + state.pageSize - 1;
|
||||
const isSmall = width < 500;
|
||||
if (itemsRangeEnd > data.length) {
|
||||
itemsRangeEnd = data.length;
|
||||
}
|
||||
paginationEl = (
|
||||
<div className={tableStyles.paginationWrapper}>
|
||||
<div>
|
||||
<Pagination
|
||||
currentPage={state.pageIndex + 1}
|
||||
numberOfPages={pageOptions.length}
|
||||
showSmallVersion={isSmall}
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
</div>
|
||||
{isSmall ? null : (
|
||||
<div className={tableStyles.paginationSummary}>
|
||||
{itemsRangeStart} - {itemsRangeEnd} of {data.length} rows
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div {...getTableProps()} className={tableStyles.table} aria-label={ariaLabel} role="table">
|
||||
<CustomScrollbar hideVerticalTrack={true}>
|
||||
<div style={{ width: totalColumnsWidth ? `${totalColumnsWidth}px` : '100%' }}>
|
||||
<div className={tableStyles.tableContentWrapper(totalColumnsWidth)}>
|
||||
{!noHeader && <HeaderRow data={data} headerGroups={headerGroups} showTypeIcons={showTypeIcons} />}
|
||||
{rows.length > 0 ? (
|
||||
{itemCount > 0 ? (
|
||||
<FixedSizeList
|
||||
height={listHeight}
|
||||
itemCount={rows.length}
|
||||
itemCount={itemCount}
|
||||
itemSize={tableStyles.rowHeight}
|
||||
width={'100%'}
|
||||
style={{ overflow: 'hidden auto' }}
|
||||
@@ -243,11 +298,13 @@ export const Table: FC<Props> = memo((props: Props) => {
|
||||
{footerValues && (
|
||||
<FooterRow
|
||||
height={footerHeight}
|
||||
isPaginationVisible={Boolean(enablePagination)}
|
||||
footerValues={footerValues}
|
||||
footerGroups={footerGroups}
|
||||
totalColumnsWidth={totalColumnsWidth}
|
||||
/>
|
||||
)}
|
||||
{paginationEl}
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
</div>
|
||||
|
||||
@@ -170,6 +170,32 @@ export const getTableStyles = (theme: GrafanaTheme2) => {
|
||||
label: headerFilter;
|
||||
cursor: pointer;
|
||||
`,
|
||||
paginationWrapper: css`
|
||||
display: flex;
|
||||
background: ${headerBg};
|
||||
height: ${cellHeight}px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-top: 1px solid ${theme.colors.border.weak};
|
||||
li {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
div:not(:only-child):first-child {
|
||||
flex-grow: 0.6;
|
||||
}
|
||||
`,
|
||||
paginationSummary: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
margin-left: auto;
|
||||
`,
|
||||
|
||||
tableContentWrapper: (totalColumnsWidth: number) => css`
|
||||
width: ${totalColumnsWidth ?? '100%'};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`,
|
||||
row: css`
|
||||
label: row;
|
||||
border-bottom: 1px solid ${borderColor};
|
||||
|
||||
@@ -21,7 +21,7 @@ const getStyles = (theme: GrafanaTheme, height: number, visible: boolean) => {
|
||||
box-shadow: 0 0 20px ${theme.colors.dropdownShadow};
|
||||
visibility: ${visible === true ? 'visible' : 'hidden'};
|
||||
width: 250px;
|
||||
height: ${height + parseInt(theme.spacing.xxs, 10)}px;
|
||||
min-height: ${height + parseInt(theme.spacing.xxs, 10)}px;
|
||||
position: relative;
|
||||
word-break: break-word;
|
||||
`,
|
||||
|
||||
@@ -214,7 +214,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
let series: SeriesTableRowProps[] = [];
|
||||
const frame = otherProps.data;
|
||||
const fields = frame.fields;
|
||||
const sortIdx: Array<[number, number]> = [];
|
||||
const sortIdx: any[] = [];
|
||||
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const field = frame.fields[i];
|
||||
@@ -232,7 +232,7 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
const v = otherProps.data.fields[i].values.get(focusedPointIdxs[i]!);
|
||||
const display = field.display!(v);
|
||||
|
||||
sortIdx.push([series.length, v]);
|
||||
sortIdx.push(v);
|
||||
series.push({
|
||||
color: display.color || FALLBACK_COLOR,
|
||||
label: getFieldDisplayName(field, frame),
|
||||
@@ -242,7 +242,16 @@ export const TooltipPlugin: React.FC<TooltipPluginProps> = ({
|
||||
}
|
||||
|
||||
if (sortOrder !== SortOrder.None) {
|
||||
series.sort((a, b) => arrayUtils.sortValues(sortOrder)(a.value, b.value));
|
||||
// create sort reference series array, as Array.sort() mutates the original array
|
||||
const sortRef = [...series];
|
||||
const sortFn = arrayUtils.sortValues(sortOrder);
|
||||
|
||||
series.sort((a, b) => {
|
||||
// get compared values indices to retrieve raw values from sortIdx
|
||||
const aIdx = sortRef.indexOf(a);
|
||||
const bIdx = sortRef.indexOf(b);
|
||||
return sortFn(sortIdx[aIdx], sortIdx[bIdx]);
|
||||
});
|
||||
}
|
||||
|
||||
tooltip = <SeriesTable series={series} timestamp={xVal} />;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import type {
|
||||
UseColumnOrderInstanceProps,
|
||||
UseColumnOrderState,
|
||||
UseExpandedHooks,
|
||||
UseExpandedInstanceProps,
|
||||
UseExpandedOptions,
|
||||
UseExpandedRowProps,
|
||||
UseExpandedState,
|
||||
UseFiltersColumnOptions,
|
||||
UseFiltersColumnProps,
|
||||
UseFiltersInstanceProps,
|
||||
UseFiltersOptions,
|
||||
UseFiltersState,
|
||||
UseGlobalFiltersColumnOptions,
|
||||
UseGlobalFiltersInstanceProps,
|
||||
UseGlobalFiltersOptions,
|
||||
UseGlobalFiltersState,
|
||||
UseGroupByCellProps,
|
||||
UseGroupByColumnOptions,
|
||||
UseGroupByColumnProps,
|
||||
UseGroupByHooks,
|
||||
UseGroupByInstanceProps,
|
||||
UseGroupByOptions,
|
||||
UseGroupByRowProps,
|
||||
UseGroupByState,
|
||||
UsePaginationInstanceProps,
|
||||
UsePaginationOptions,
|
||||
UsePaginationState,
|
||||
UseResizeColumnsColumnOptions,
|
||||
UseResizeColumnsColumnProps,
|
||||
UseResizeColumnsOptions,
|
||||
UseResizeColumnsState,
|
||||
UseRowSelectHooks,
|
||||
UseRowSelectInstanceProps,
|
||||
UseRowSelectOptions,
|
||||
UseRowSelectRowProps,
|
||||
UseRowSelectState,
|
||||
UseRowStateCellProps,
|
||||
UseRowStateInstanceProps,
|
||||
UseRowStateOptions,
|
||||
UseRowStateRowProps,
|
||||
UseRowStateState,
|
||||
UseSortByColumnOptions,
|
||||
UseSortByColumnProps,
|
||||
UseSortByHooks,
|
||||
UseSortByInstanceProps,
|
||||
UseSortByOptions,
|
||||
UseSortByState,
|
||||
} from 'react-table';
|
||||
|
||||
declare module 'react-table' {
|
||||
export interface TableOptions<D extends Record<string, unknown>>
|
||||
extends UseExpandedOptions<D>,
|
||||
UseFiltersOptions<D>,
|
||||
UseGlobalFiltersOptions<D>,
|
||||
UseGroupByOptions<D>,
|
||||
UsePaginationOptions<D>,
|
||||
UseResizeColumnsOptions<D>,
|
||||
UseRowSelectOptions<D>,
|
||||
UseRowStateOptions<D>,
|
||||
UseSortByOptions<D>,
|
||||
// note that having Record here allows you to add anything to the options, this matches the spirit of the
|
||||
// underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
|
||||
// feature set, this is a safe default.
|
||||
Record<string, any> {}
|
||||
|
||||
export interface Hooks<D extends Record<string, unknown> = Record<string, unknown>>
|
||||
extends UseExpandedHooks<D>,
|
||||
UseGroupByHooks<D>,
|
||||
UseRowSelectHooks<D>,
|
||||
UseSortByHooks<D> {}
|
||||
|
||||
export interface TableInstance<D extends Record<string, unknown> = Record<string, unknown>>
|
||||
extends UseColumnOrderInstanceProps<D>,
|
||||
UseExpandedInstanceProps<D>,
|
||||
UseFiltersInstanceProps<D>,
|
||||
UseGlobalFiltersInstanceProps<D>,
|
||||
UseGroupByInstanceProps<D>,
|
||||
UsePaginationInstanceProps<D>,
|
||||
UseRowSelectInstanceProps<D>,
|
||||
UseRowStateInstanceProps<D>,
|
||||
UseSortByInstanceProps<D> {}
|
||||
|
||||
export interface TableState<D extends Record<string, unknown> = Record<string, unknown>>
|
||||
extends UseColumnOrderState<D>,
|
||||
UseExpandedState<D>,
|
||||
UseFiltersState<D>,
|
||||
UseGlobalFiltersState<D>,
|
||||
UseGroupByState<D>,
|
||||
UsePaginationState<D>,
|
||||
UseResizeColumnsState<D>,
|
||||
UseRowSelectState<D>,
|
||||
UseRowStateState<D>,
|
||||
UseSortByState<D> {}
|
||||
|
||||
export interface ColumnInterface<D extends Record<string, unknown> = Record<string, unknown>>
|
||||
extends UseGlobalFiltersColumnOptions<D>,
|
||||
UseGroupByColumnOptions<D>,
|
||||
UseResizeColumnsColumnOptions<D>,
|
||||
UseSortByColumnOptions<D> {}
|
||||
|
||||
export interface ColumnInstance<D extends Record<string, unknown> = Record<string, unknown>>
|
||||
extends UseFiltersColumnProps<D>,
|
||||
UseGroupByColumnProps<D>,
|
||||
UseResizeColumnsColumnProps<D>,
|
||||
UseSortByColumnProps<D> {}
|
||||
|
||||
export interface Cell<D extends Record<string, unknown> = Record<string, unknown>, V = any>
|
||||
extends UseGroupByCellProps<D>,
|
||||
UseRowStateCellProps<D> {}
|
||||
}
|
||||
@@ -37,5 +37,5 @@ export function getNextCharacter(global?: any) {
|
||||
const range = selection.getRangeAt(0);
|
||||
const text = selection.anchorNode.textContent;
|
||||
const offset = range.startOffset;
|
||||
return text!.substr(offset, 1);
|
||||
return text!.slice(offset, offset + 1);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"@types/deep-freeze": "^0.1.1",
|
||||
"@types/grafana__slate-react": "npm:@types/slate-react@0.22.5",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/lodash": "4.14.149",
|
||||
"@types/lodash": "4.14.181",
|
||||
"@types/prop-types": "^15",
|
||||
"@types/react": "17.0.42",
|
||||
"@types/react-icons": "2.2.7",
|
||||
|
||||
@@ -21,7 +21,6 @@ import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import SpanGraph from './SpanGraph';
|
||||
import TracePageSearchBar from './TracePageSearchBar';
|
||||
import { autoColor, TUpdateViewRangeTimeFunction, ViewRange, ViewRangeTimeUpdate } from '..';
|
||||
import LabeledList from '../common/LabeledList';
|
||||
import TraceName from '../common/TraceName';
|
||||
@@ -138,22 +137,15 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
|
||||
type TracePageHeaderEmbedProps = {
|
||||
canCollapse: boolean;
|
||||
clearSearch: () => void;
|
||||
focusUiFindMatches: () => void;
|
||||
hideMap: boolean;
|
||||
hideSummary: boolean;
|
||||
nextResult: () => void;
|
||||
onSlimViewClicked: () => void;
|
||||
onTraceGraphViewClicked: () => void;
|
||||
prevResult: () => void;
|
||||
resultCount: number;
|
||||
slimView: boolean;
|
||||
trace: Trace;
|
||||
updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void;
|
||||
updateViewRangeTime: TUpdateViewRangeTimeFunction;
|
||||
viewRange: ViewRange;
|
||||
searchValue: string;
|
||||
onSearchValueChange: (value: string) => void;
|
||||
timeZone: TimeZone;
|
||||
};
|
||||
|
||||
@@ -200,21 +192,14 @@ export const HEADER_ITEMS = [
|
||||
export default function TracePageHeader(props: TracePageHeaderEmbedProps) {
|
||||
const {
|
||||
canCollapse,
|
||||
clearSearch,
|
||||
focusUiFindMatches,
|
||||
hideMap,
|
||||
hideSummary,
|
||||
nextResult,
|
||||
onSlimViewClicked,
|
||||
prevResult,
|
||||
resultCount,
|
||||
slimView,
|
||||
trace,
|
||||
updateNextViewRangeTime,
|
||||
updateViewRangeTime,
|
||||
viewRange,
|
||||
searchValue,
|
||||
onSearchValueChange,
|
||||
timeZone,
|
||||
} = props;
|
||||
|
||||
@@ -267,17 +252,6 @@ export default function TracePageHeader(props: TracePageHeaderEmbedProps) {
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
<TracePageSearchBar
|
||||
clearSearch={clearSearch}
|
||||
focusUiFindMatches={focusUiFindMatches}
|
||||
nextResult={nextResult}
|
||||
prevResult={prevResult}
|
||||
resultCount={resultCount}
|
||||
// TODO: we can change this when we have scroll to span functionality
|
||||
navigable={false}
|
||||
searchValue={searchValue}
|
||||
onSearchValueChange={onSearchValueChange}
|
||||
/>
|
||||
</div>
|
||||
{summaryItems && <LabeledList className={styles.TracePageHeaderOverviewItems} items={summaryItems} />}
|
||||
{!hideMap && !slimView && (
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { createTheme } from '@grafana/data';
|
||||
|
||||
import * as markers from './TracePageSearchBar.markers';
|
||||
import TracePageSearchBar, { getStyles } from './TracePageSearchBar';
|
||||
@@ -24,7 +25,7 @@ const defaultProps = {
|
||||
navigable: true,
|
||||
nextResult: () => {},
|
||||
prevResult: () => {},
|
||||
resultCount: 0,
|
||||
suffix: '',
|
||||
searchValue: 'something',
|
||||
};
|
||||
|
||||
@@ -45,26 +46,28 @@ describe('<TracePageSearchBar>', () => {
|
||||
name: 'search',
|
||||
})
|
||||
);
|
||||
expect(suffix.hasClass(getStyles().TracePageSearchBarCount)).toBe(true);
|
||||
expect(suffix.text()).toBe(String(defaultProps.resultCount));
|
||||
const theme = createTheme();
|
||||
expect(suffix.hasClass(getStyles(theme).TracePageSearchBarSuffix)).toBe(true);
|
||||
expect(suffix.text()).toBe(String(defaultProps.suffix));
|
||||
});
|
||||
|
||||
it('renders buttons', () => {
|
||||
const buttons = wrapper.find('Button');
|
||||
expect(buttons.length).toBe(4);
|
||||
expect(buttons.length).toBe(2);
|
||||
buttons.forEach((button) => {
|
||||
expect(button.prop('disabled')).toBe(false);
|
||||
});
|
||||
expect(wrapper.find('Button[icon="arrow-up"]').prop('onClick')).toBe(defaultProps.prevResult);
|
||||
expect(wrapper.find('Button[icon="arrow-down"]').prop('onClick')).toBe(defaultProps.nextResult);
|
||||
expect(wrapper.find('Button[icon="times"]').prop('onClick')).toBe(defaultProps.clearSearch);
|
||||
});
|
||||
|
||||
it('hides navigation buttons when not navigable', () => {
|
||||
it('only shows navigable buttons when navigable is true', () => {
|
||||
wrapper.setProps({ navigable: false });
|
||||
const button = wrapper.find('Button');
|
||||
expect(button.length).toBe(1);
|
||||
expect(button.prop('icon')).toBe('times');
|
||||
var buttons = wrapper.find('Button');
|
||||
expect(buttons.length).toBe(0);
|
||||
wrapper.setProps({ navigable: true });
|
||||
buttons = wrapper.find('Button');
|
||||
expect(buttons.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +82,7 @@ describe('<TracePageSearchBar>', () => {
|
||||
|
||||
it('renders buttons', () => {
|
||||
const buttons = wrapper.find('Button');
|
||||
expect(buttons.length).toBe(4);
|
||||
expect(buttons.length).toBe(2);
|
||||
buttons.forEach((button) => {
|
||||
expect(button.prop('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import cx from 'classnames';
|
||||
import IoAndroidLocate from 'react-icons/lib/io/android-locate';
|
||||
import { css } from '@emotion/css';
|
||||
import { Button, useStyles2 } from '@grafana/ui';
|
||||
|
||||
@@ -24,11 +23,24 @@ import UiFindInput from '../common/UiFindInput';
|
||||
import { ubFlexAuto, ubJustifyEnd } from '../uberUtilityStyles';
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import { memo } from 'react';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
export const getStyles = () => {
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
TracePageSearchBar: css`
|
||||
label: TracePageSearchBar;
|
||||
float: right;
|
||||
position: sticky;
|
||||
top: 8px;
|
||||
right: 0;
|
||||
z-index: ${theme.zIndex.navbarFixed};
|
||||
background: ${theme.colors.background.primary};
|
||||
margin-top: 8px;
|
||||
margin-bottom: -48px;
|
||||
padding: 8px;
|
||||
margin-right: 2px;
|
||||
border-radius: 4px;
|
||||
box-shadow: ${theme.shadows.z2};
|
||||
`,
|
||||
TracePageSearchBarBar: css`
|
||||
label: TracePageSearchBarBar;
|
||||
@@ -38,14 +50,14 @@ export const getStyles = () => {
|
||||
max-width: 100%;
|
||||
}
|
||||
`,
|
||||
TracePageSearchBarCount: css`
|
||||
label: TracePageSearchBarCount;
|
||||
TracePageSearchBarSuffix: css`
|
||||
label: TracePageSearchBarSuffix;
|
||||
opacity: 0.6;
|
||||
`,
|
||||
TracePageSearchBarBtn: css`
|
||||
label: TracePageSearchBarBtn;
|
||||
border-left: none;
|
||||
transition: 0.2s;
|
||||
margin-left: 8px;
|
||||
`,
|
||||
TracePageSearchBarBtnDisabled: css`
|
||||
label: TracePageSearchBarBtnDisabled;
|
||||
@@ -61,72 +73,62 @@ export const getStyles = () => {
|
||||
type TracePageSearchBarProps = {
|
||||
prevResult: () => void;
|
||||
nextResult: () => void;
|
||||
clearSearch: () => void;
|
||||
focusUiFindMatches: () => void;
|
||||
resultCount: number;
|
||||
navigable: boolean;
|
||||
searchValue: string;
|
||||
onSearchValueChange: (value: string) => void;
|
||||
searchBarSuffix: string;
|
||||
};
|
||||
|
||||
export default memo(function TracePageSearchBar(props: TracePageSearchBarProps) {
|
||||
const {
|
||||
clearSearch,
|
||||
focusUiFindMatches,
|
||||
navigable,
|
||||
nextResult,
|
||||
prevResult,
|
||||
resultCount,
|
||||
onSearchValueChange,
|
||||
searchValue,
|
||||
} = props;
|
||||
const { navigable, nextResult, prevResult, onSearchValueChange, searchValue, searchBarSuffix } = props;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const count = searchValue ? <span className={styles.TracePageSearchBarCount}>{resultCount}</span> : null;
|
||||
const suffix = searchValue ? (
|
||||
<span className={styles.TracePageSearchBarSuffix} data-testid="trace-page-search-bar-suffix">
|
||||
{searchBarSuffix}
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
const btnClass = cx(styles.TracePageSearchBarBtn, { [styles.TracePageSearchBarBtnDisabled]: !searchValue });
|
||||
const uiFindInputInputProps = {
|
||||
'data-test': markers.IN_TRACE_SEARCH,
|
||||
className: cx(styles.TracePageSearchBarBar, ubFlexAuto),
|
||||
name: 'search',
|
||||
suffix: count,
|
||||
suffix,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.TracePageSearchBar}>
|
||||
<span className={ubJustifyEnd} style={{ display: 'flex' }}>
|
||||
<UiFindInput onChange={onSearchValueChange} value={searchValue} inputProps={uiFindInputInputProps} />
|
||||
<UiFindInput
|
||||
onChange={onSearchValueChange}
|
||||
value={searchValue}
|
||||
inputProps={uiFindInputInputProps}
|
||||
allowClear={true}
|
||||
/>
|
||||
<>
|
||||
{navigable && (
|
||||
<>
|
||||
<Button
|
||||
className={cx(btnClass, styles.TracePageSearchBarLocateBtn)}
|
||||
disabled={!searchValue}
|
||||
type="button"
|
||||
onClick={focusUiFindMatches}
|
||||
>
|
||||
<IoAndroidLocate />
|
||||
</Button>
|
||||
<Button className={btnClass} disabled={!searchValue} type="button" icon="arrow-up" onClick={prevResult} />
|
||||
<Button
|
||||
className={btnClass}
|
||||
variant="secondary"
|
||||
disabled={!searchValue}
|
||||
type="button"
|
||||
icon="arrow-down"
|
||||
data-testid="trace-page-search-bar-next-result-button"
|
||||
onClick={nextResult}
|
||||
/>
|
||||
<Button
|
||||
className={btnClass}
|
||||
variant="secondary"
|
||||
disabled={!searchValue}
|
||||
type="button"
|
||||
icon="arrow-up"
|
||||
data-testid="trace-page-search-bar-prev-result-button"
|
||||
onClick={prevResult}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
fill={'text'}
|
||||
// className={btnClass}
|
||||
disabled={!searchValue}
|
||||
type="button"
|
||||
icon="times"
|
||||
onClick={clearSearch}
|
||||
title={'Clear search'}
|
||||
/>
|
||||
</>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -166,17 +166,17 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => {
|
||||
`,
|
||||
rowMatchingFilter: css`
|
||||
label: rowMatchingFilter;
|
||||
background-color: ${autoColor(theme, '#fffce4')};
|
||||
background-color: ${autoColor(theme, '#fffbde')};
|
||||
&:hover .${nameWrapperClassName} {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${autoColor(theme, '#fff5e1')},
|
||||
${autoColor(theme, '#fff5e1')} 75%,
|
||||
${autoColor(theme, '#ffe6c9')}
|
||||
${autoColor(theme, '#fffbde')},
|
||||
${autoColor(theme, '#fffbde')} 75%,
|
||||
${autoColor(theme, '#f7f1c6')}
|
||||
);
|
||||
}
|
||||
&:hover .${viewClassName} {
|
||||
background-color: ${autoColor(theme, '#fff3d7')};
|
||||
background-color: ${autoColor(theme, '#f7f1c6')};
|
||||
outline: 1px solid ${autoColor(theme, '#ddd')};
|
||||
}
|
||||
`,
|
||||
|
||||
+7
-2
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import * as React from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { Icon, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import AccordianKeyValues from './AccordianKeyValues';
|
||||
@@ -98,6 +98,9 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
content: attr(data-label);
|
||||
}
|
||||
`,
|
||||
serviceName: css`
|
||||
margin-right: 8px;
|
||||
`,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -134,7 +137,9 @@ export function References(props: ReferenceItemProps) {
|
||||
<span className={styles.itemContent}>
|
||||
{reference.span ? (
|
||||
<span>
|
||||
<span className="span-svc-name">{reference.span.process.serviceName}</span>
|
||||
<span className={cx('span-svc-name', styles.serviceName)}>
|
||||
{reference.span.process.serviceName}
|
||||
</span>
|
||||
<small className="endpoint-name">{reference.span.operationName}</small>
|
||||
</span>
|
||||
) : (
|
||||
|
||||
@@ -101,6 +101,7 @@ type TVirtualizedTraceViewOwnProps = {
|
||||
createSpanLink?: SpanLinkFunc;
|
||||
scrollElement?: Element;
|
||||
focusedSpanId?: string;
|
||||
focusedSpanIdForSearch: string;
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
};
|
||||
@@ -223,6 +224,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
trace: nextTrace,
|
||||
uiFind,
|
||||
focusedSpanId,
|
||||
focusedSpanIdForSearch,
|
||||
} = this.props;
|
||||
|
||||
if (trace !== nextTrace) {
|
||||
@@ -241,6 +243,10 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
if (focusedSpanId !== prevProps.focusedSpanId) {
|
||||
this.scrollToSpan(focusedSpanId);
|
||||
}
|
||||
|
||||
if (focusedSpanIdForSearch !== prevProps.focusedSpanIdForSearch) {
|
||||
this.scrollToSpan(focusedSpanIdForSearch);
|
||||
}
|
||||
}
|
||||
|
||||
getRowStates(): RowState[] {
|
||||
@@ -378,6 +384,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
theme,
|
||||
createSpanLink,
|
||||
focusedSpanId,
|
||||
focusedSpanIdForSearch,
|
||||
} = this.props;
|
||||
// to avert flow error
|
||||
if (!trace) {
|
||||
@@ -387,7 +394,7 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
const isCollapsed = childrenHiddenIDs.has(spanID);
|
||||
const isDetailExpanded = detailStates.has(spanID);
|
||||
const isMatchingFilter = findMatchesIDs ? findMatchesIDs.has(spanID) : false;
|
||||
const isFocused = spanID === focusedSpanId;
|
||||
const isFocused = spanID === focusedSpanId || spanID === focusedSpanIdForSearch;
|
||||
const showErrorIcon = isErrorSpan(span) || (isCollapsed && spanContainsErredSpan(trace.spans, spanIndex));
|
||||
|
||||
// Check for direct child "server" span if the span is a "client" span.
|
||||
|
||||
@@ -105,6 +105,7 @@ type TProps = TExtractUiFindFromStateReturn & {
|
||||
createSpanLink?: SpanLinkFunc;
|
||||
scrollElement?: Element;
|
||||
focusedSpanId?: string;
|
||||
focusedSpanIdForSearch: string;
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
};
|
||||
@@ -163,6 +164,7 @@ export class UnthemedTraceTimelineViewer extends React.PureComponent<TProps, Sta
|
||||
traceTimeline,
|
||||
theme,
|
||||
topOfExploreViewRef,
|
||||
focusedSpanIdForSearch,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { trace } = rest;
|
||||
@@ -194,6 +196,7 @@ export class UnthemedTraceTimelineViewer extends React.PureComponent<TProps, Sta
|
||||
setSpanNameColumnWidth={setSpanNameColumnWidth}
|
||||
currentViewRangeTime={viewRange.time.current}
|
||||
topOfExploreViewRef={topOfExploreViewRef}
|
||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
||||
/>
|
||||
</div>
|
||||
</ExternalLinkContext.Provider>
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function filterSpans(textFilter: string, spans: TraceSpan[] | TNi
|
||||
.filter(Boolean)
|
||||
.forEach((w) => {
|
||||
if (w[0] === '-') {
|
||||
excludeKeys.push(w.substr(1).toLowerCase());
|
||||
excludeKeys.push(w.slice(1).toLowerCase());
|
||||
} else {
|
||||
includeFilters.push(w.toLowerCase());
|
||||
}
|
||||
|
||||
@@ -6,5 +6,11 @@
|
||||
},
|
||||
"exclude": ["dist", "node_modules"],
|
||||
"extends": "@grafana/tsconfig",
|
||||
"include": ["src/**/*.ts*", "typings", "../../public/app/types/jquery/*.ts", "../../public/app/types/*.d.ts"]
|
||||
"include": [
|
||||
"src/**/*.ts*",
|
||||
"typings",
|
||||
"../../public/app/types/jquery/*.ts",
|
||||
"../../public/app/types/*.d.ts",
|
||||
"../grafana-ui/src/types/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -265,10 +265,9 @@ func (hs *HTTPServer) declareFixedRoles() error {
|
||||
DisplayName: "Annotation reader",
|
||||
Description: "Read annotations and tags",
|
||||
Group: "Annotations",
|
||||
Version: 1,
|
||||
Version: 2,
|
||||
Permissions: []ac.Permission{
|
||||
{Action: ac.ActionAnnotationsRead, Scope: ac.ScopeAnnotationsAll},
|
||||
{Action: ac.ActionAnnotationsTagsRead, Scope: ac.ScopeAnnotationsTagsAll},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(models.ROLE_VIEWER)},
|
||||
|
||||
@@ -495,7 +495,7 @@ func TestAPI_Annotations_AccessControl(t *testing.T) {
|
||||
{
|
||||
name: "AccessControl getting tags for annotations with correct permissions is allowed",
|
||||
args: args{
|
||||
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsTagsRead, Scope: accesscontrol.ScopeAnnotationsTagsAll}},
|
||||
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead}},
|
||||
url: "/api/annotations/tags",
|
||||
method: http.MethodGet,
|
||||
},
|
||||
@@ -504,7 +504,7 @@ func TestAPI_Annotations_AccessControl(t *testing.T) {
|
||||
{
|
||||
name: "AccessControl getting tags for annotations without correct permissions is forbidden",
|
||||
args: args{
|
||||
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsTagsRead}},
|
||||
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite}},
|
||||
url: "/api/annotations/tags",
|
||||
method: http.MethodGet,
|
||||
},
|
||||
|
||||
+1
-1
@@ -448,7 +448,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
annotationsRoute.Put("/:annotationId", authorize(reqSignedIn, ac.EvalPermission(ac.ActionAnnotationsWrite, ac.ScopeAnnotationsID)), routing.Wrap(hs.UpdateAnnotation))
|
||||
annotationsRoute.Patch("/:annotationId", authorize(reqSignedIn, ac.EvalPermission(ac.ActionAnnotationsWrite, ac.ScopeAnnotationsID)), routing.Wrap(hs.PatchAnnotation))
|
||||
annotationsRoute.Post("/graphite", authorize(reqEditorRole, ac.EvalPermission(ac.ActionAnnotationsCreate, ac.ScopeAnnotationsTypeOrganization)), routing.Wrap(hs.PostGraphiteAnnotation))
|
||||
annotationsRoute.Get("/tags", authorize(reqSignedIn, ac.EvalPermission(ac.ActionAnnotationsTagsRead, ac.ScopeAnnotationsTagsAll)), routing.Wrap(hs.GetAnnotationTags))
|
||||
annotationsRoute.Get("/tags", authorize(reqSignedIn, ac.EvalPermission(ac.ActionAnnotationsRead)), routing.Wrap(hs.GetAnnotationTags))
|
||||
})
|
||||
|
||||
apiRoute.Post("/frontend-metrics", routing.Wrap(hs.PostFrontendMetrics))
|
||||
|
||||
+43
-18
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/guardian"
|
||||
@@ -115,25 +116,33 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
||||
creator = hs.getUserLogin(c.Req.Context(), dash.CreatedBy)
|
||||
}
|
||||
|
||||
annotationPermissions := &dtos.AnnotationPermission{}
|
||||
|
||||
if !hs.AccessControl.IsDisabled() {
|
||||
hs.getAnnotationPermissionsByScope(c, &annotationPermissions.Dashboard, accesscontrol.ScopeAnnotationsTypeDashboard)
|
||||
hs.getAnnotationPermissionsByScope(c, &annotationPermissions.Organization, accesscontrol.ScopeAnnotationsTypeOrganization)
|
||||
}
|
||||
|
||||
meta := dtos.DashboardMeta{
|
||||
IsStarred: isStarred,
|
||||
Slug: dash.Slug,
|
||||
Type: models.DashTypeDB,
|
||||
CanStar: c.IsSignedIn,
|
||||
CanSave: canSave,
|
||||
CanEdit: canEdit,
|
||||
CanAdmin: canAdmin,
|
||||
CanDelete: canDelete,
|
||||
Created: dash.Created,
|
||||
Updated: dash.Updated,
|
||||
UpdatedBy: updater,
|
||||
CreatedBy: creator,
|
||||
Version: dash.Version,
|
||||
HasAcl: dash.HasAcl,
|
||||
IsFolder: dash.IsFolder,
|
||||
FolderId: dash.FolderId,
|
||||
Url: dash.GetUrl(),
|
||||
FolderTitle: "General",
|
||||
IsStarred: isStarred,
|
||||
Slug: dash.Slug,
|
||||
Type: models.DashTypeDB,
|
||||
CanStar: c.IsSignedIn,
|
||||
CanSave: canSave,
|
||||
CanEdit: canEdit,
|
||||
CanAdmin: canAdmin,
|
||||
CanDelete: canDelete,
|
||||
Created: dash.Created,
|
||||
Updated: dash.Updated,
|
||||
UpdatedBy: updater,
|
||||
CreatedBy: creator,
|
||||
Version: dash.Version,
|
||||
HasAcl: dash.HasAcl,
|
||||
IsFolder: dash.IsFolder,
|
||||
FolderId: dash.FolderId,
|
||||
Url: dash.GetUrl(),
|
||||
FolderTitle: "General",
|
||||
AnnotationsPermissions: annotationPermissions,
|
||||
}
|
||||
|
||||
// lookup folder title
|
||||
@@ -190,6 +199,22 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
|
||||
return response.JSON(200, dto)
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) getAnnotationPermissionsByScope(c *models.ReqContext, actions *dtos.AnnotationActions, scope string) {
|
||||
var err error
|
||||
|
||||
evaluate := accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsDelete, scope)
|
||||
actions.CanDelete, err = hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluate)
|
||||
if err != nil {
|
||||
hs.log.Warn("Failed to evaluate permission", "err", err, "action", accesscontrol.ActionAnnotationsDelete, "scope", scope)
|
||||
}
|
||||
|
||||
evaluate = accesscontrol.EvalPermission(accesscontrol.ActionAnnotationsWrite, scope)
|
||||
actions.CanEdit, err = hs.AccessControl.Evaluate(c.Req.Context(), c.SignedInUser, evaluate)
|
||||
if err != nil {
|
||||
hs.log.Warn("Failed to evaluate permission", "err", err, "action", accesscontrol.ActionAnnotationsWrite, "scope", scope)
|
||||
}
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) getUserLogin(ctx context.Context, userID int64) string {
|
||||
query := models.GetUserByIdQuery{Id: userID}
|
||||
err := hs.SQLStore.GetUserById(ctx, &query)
|
||||
|
||||
@@ -118,10 +118,11 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
mockSQLStore.ExpectedDashboard = fakeDash
|
||||
|
||||
hs := &HTTPServer{
|
||||
Cfg: setting.NewCfg(),
|
||||
pluginStore: &fakePluginStore{},
|
||||
SQLStore: mockSQLStore,
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
Cfg: setting.NewCfg(),
|
||||
pluginStore: &fakePluginStore{},
|
||||
SQLStore: mockSQLStore,
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
Features: featuremgmt.WithFeatures(),
|
||||
}
|
||||
hs.SQLStore = mockSQLStore
|
||||
|
||||
@@ -224,6 +225,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
LibraryPanelService: &mockLibraryPanelService{},
|
||||
LibraryElementService: &mockLibraryElementService{},
|
||||
SQLStore: mockSQLStore,
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
dashboardService: service.ProvideDashboardService(
|
||||
cfg, dashboardStore, nil, features, accesscontrolmock.NewPermissionsServicesMock(),
|
||||
),
|
||||
@@ -890,6 +892,7 @@ func TestDashboardAPIEndpoint(t *testing.T) {
|
||||
LibraryElementService: &mockLibraryElementService{},
|
||||
dashboardProvisioningService: mockDashboardProvisioningService{},
|
||||
SQLStore: mockSQLStore,
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
}
|
||||
hs.callGetDashboard(sc)
|
||||
|
||||
@@ -927,6 +930,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr
|
||||
LibraryElementService: &libraryElementsService,
|
||||
SQLStore: sc.sqlStore,
|
||||
ProvisioningService: provisioningService,
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
dashboardProvisioningService: service.ProvideDashboardService(
|
||||
cfg, dashboardStore, nil, features, accesscontrolmock.NewPermissionsServicesMock(),
|
||||
),
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
//
|
||||
// Create Annotation.
|
||||
//
|
||||
// Creates an annotation in the Grafana database. The dashboardId and panelId fields are optional. If they are not specified then a global annotation is created and can be queried in any dashboard that adds the Grafana annotations data source. When creating a region annotation include the timeEnd property.
|
||||
// Creates an annotation in the Grafana database. The dashboardId and panelId fields are optional. If they are not specified then an organization annotation is created and can be queried in any dashboard that adds the Grafana annotations data source. When creating a region annotation include the timeEnd property.
|
||||
// The format for `time` and `timeEnd` should be epoch numbers in millisecond resolution.
|
||||
// The response for this HTTP request is slightly different in versions prior to v6.4. In prior versions you would also get an endId if you where creating a region. But in 6.4 regions are represented using a single event with time and timeEnd properties.
|
||||
//
|
||||
@@ -141,7 +141,7 @@ type GetAnnotationsParams struct {
|
||||
// in:query
|
||||
// required:false
|
||||
Limit int64 `json:"limit"`
|
||||
// Use this to filter global annotations. Organization annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. You can filter by multiple tags.
|
||||
// Use this to filter organization annotations. Organization annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. You can filter by multiple tags.
|
||||
// in:query
|
||||
// required:false
|
||||
// type: array
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
{
|
||||
"name": "annotations",
|
||||
"description": "Grafana Annotations feature released in Grafana 4.6. Annotations are saved in the Grafana database (sqlite, mysql or postgres). Annotations can be global annotations that can be shown on any dashboard by configuring an annotation data source - they are filtered by tags. Or they can be tied to a panel on a dashboard and are then only shown on that panel."
|
||||
"description": "Grafana Annotations feature released in Grafana 4.6. Annotations are saved in the Grafana database (sqlite, mysql or postgres). Annotations can be organization annotations that can be shown on any dashboard by configuring an annotation data source - they are filtered by tags. Or they can be tied to a panel on a dashboard and are then only shown on that panel."
|
||||
},
|
||||
{
|
||||
"name": "library_elements",
|
||||
@@ -81,4 +81,4 @@
|
||||
"description": "Grafana Alerting Prometheus-compatible endpoints"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+35
-25
@@ -7,31 +7,41 @@ import (
|
||||
)
|
||||
|
||||
type DashboardMeta struct {
|
||||
IsStarred bool `json:"isStarred,omitempty"`
|
||||
IsHome bool `json:"isHome,omitempty"`
|
||||
IsSnapshot bool `json:"isSnapshot,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
CanSave bool `json:"canSave"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanAdmin bool `json:"canAdmin"`
|
||||
CanStar bool `json:"canStar"`
|
||||
CanDelete bool `json:"canDelete"`
|
||||
Slug string `json:"slug"`
|
||||
Url string `json:"url"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Version int `json:"version"`
|
||||
HasAcl bool `json:"hasAcl"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
FolderUid string `json:"folderUid"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderUrl string `json:"folderUrl"`
|
||||
Provisioned bool `json:"provisioned"`
|
||||
ProvisionedExternalId string `json:"provisionedExternalId"`
|
||||
IsStarred bool `json:"isStarred,omitempty"`
|
||||
IsHome bool `json:"isHome,omitempty"`
|
||||
IsSnapshot bool `json:"isSnapshot,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
CanSave bool `json:"canSave"`
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanAdmin bool `json:"canAdmin"`
|
||||
CanStar bool `json:"canStar"`
|
||||
CanDelete bool `json:"canDelete"`
|
||||
Slug string `json:"slug"`
|
||||
Url string `json:"url"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Version int `json:"version"`
|
||||
HasAcl bool `json:"hasAcl"`
|
||||
IsFolder bool `json:"isFolder"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
FolderUid string `json:"folderUid"`
|
||||
FolderTitle string `json:"folderTitle"`
|
||||
FolderUrl string `json:"folderUrl"`
|
||||
Provisioned bool `json:"provisioned"`
|
||||
ProvisionedExternalId string `json:"provisionedExternalId"`
|
||||
AnnotationsPermissions *AnnotationPermission `json:"annotationsPermissions"`
|
||||
}
|
||||
type AnnotationPermission struct {
|
||||
Dashboard AnnotationActions `json:"dashboard"`
|
||||
Organization AnnotationActions `json:"organization"`
|
||||
}
|
||||
|
||||
type AnnotationActions struct {
|
||||
CanEdit bool `json:"canEdit"`
|
||||
CanDelete bool `json:"canDelete"`
|
||||
}
|
||||
|
||||
type DashboardFullWithMeta struct {
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
)
|
||||
|
||||
func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
cfg := &setting.Cfg{}
|
||||
httpClientProvider := httpclient.NewProvider()
|
||||
tracer, err := tracing.InitializeTracerForTest()
|
||||
require.NoError(t, err)
|
||||
@@ -128,11 +129,9 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
return ctx, req
|
||||
}
|
||||
|
||||
cfg := &setting.Cfg{}
|
||||
|
||||
t.Run("When matching route path", func(t *testing.T) {
|
||||
ctx, req := setUp()
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/v4/some/method", cfg, httpClientProvider,
|
||||
&oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
@@ -145,7 +144,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
t.Run("When matching route path and has dynamic url", func(t *testing.T) {
|
||||
ctx, req := setUp()
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/common/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
proxy.matchedRoute = routes[3]
|
||||
@@ -157,7 +156,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
t.Run("When matching route path with no url", func(t *testing.T) {
|
||||
ctx, req := setUp()
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
proxy.matchedRoute = routes[4]
|
||||
@@ -168,7 +167,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
t.Run("When matching route path and has dynamic body", func(t *testing.T) {
|
||||
ctx, req := setUp()
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/body", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
proxy.matchedRoute = routes[5]
|
||||
@@ -182,7 +181,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
t.Run("Validating request", func(t *testing.T) {
|
||||
t.Run("plugin route with valid role", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
@@ -191,7 +190,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
t.Run("plugin route with admin role and user is editor", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
@@ -201,7 +200,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
t.Run("plugin route with admin role and user is admin", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
ctx.SignedInUser.OrgRole = models.ROLE_ADMIN
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
err = proxy.validateRequest()
|
||||
@@ -280,8 +279,6 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
client = newFakeHTTPClient(t, json)
|
||||
defer func() { client = originalClient }()
|
||||
|
||||
cfg := &setting.Cfg{}
|
||||
|
||||
jd, err := ds.JsonData.Map()
|
||||
require.NoError(t, err)
|
||||
dsInfo := DSInfo{
|
||||
@@ -293,7 +290,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, routes[0], dsInfo, cfg)
|
||||
@@ -309,7 +306,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
req, err := http.NewRequest("GET", "http://localhost/asd", nil)
|
||||
require.NoError(t, err)
|
||||
client = newFakeHTTPClient(t, json2)
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "pathwithtoken2", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, routes[1], dsInfo, cfg)
|
||||
@@ -326,7 +323,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
client = newFakeHTTPClient(t, []byte{})
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, routes[0], dsInfo, cfg)
|
||||
@@ -349,7 +346,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{BuildVersion: "5.3.0"}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
@@ -376,7 +373,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -401,7 +398,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -430,7 +427,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
var pluginRoutes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, pluginRoutes, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -454,7 +451,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
@@ -519,7 +516,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &mockAuthToken, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
@@ -588,13 +585,14 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
createAuthTest(t, secretsService, models.DS_ES, "http://localhost:9200", authTypeBasic, authCheckHeader, true),
|
||||
}
|
||||
for _, test := range tests {
|
||||
runDatasourceAuthTest(t, secretsService, test)
|
||||
runDatasourceAuthTest(t, secretsService, cfg, test)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// test DataSourceProxy request handling.
|
||||
func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
cfg := &setting.Cfg{}
|
||||
httpClientProvider := httpclient.NewProvider()
|
||||
var writeErr error
|
||||
|
||||
@@ -653,7 +651,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -672,7 +670,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -687,7 +685,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -710,7 +708,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -736,7 +734,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -761,7 +759,7 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -781,14 +779,14 @@ func TestNewDataSourceProxy_InvalidURL(t *testing.T) {
|
||||
Type: "test",
|
||||
Url: "://host/root",
|
||||
}
|
||||
cfg := setting.Cfg{}
|
||||
cfg := &setting.Cfg{}
|
||||
tracer, err := tracing.InitializeTracerForTest()
|
||||
require.NoError(t, err)
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.Error(t, err)
|
||||
assert.True(t, strings.HasPrefix(err.Error(), `validation of data source URL "://host/root" failed`))
|
||||
}
|
||||
@@ -802,15 +800,15 @@ func TestNewDataSourceProxy_ProtocolLessURL(t *testing.T) {
|
||||
Type: "test",
|
||||
Url: "127.0.01:5432",
|
||||
}
|
||||
cfg := setting.Cfg{}
|
||||
cfg := &setting.Cfg{}
|
||||
tracer, err := tracing.InitializeTracerForTest()
|
||||
require.NoError(t, err)
|
||||
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
_, err = NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -844,7 +842,7 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) {
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
cfg := setting.Cfg{}
|
||||
cfg := &setting.Cfg{}
|
||||
ds := models.DataSource{
|
||||
Type: "mssql",
|
||||
Url: tc.url,
|
||||
@@ -853,8 +851,8 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) {
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
p, err := NewDataSourceProxy(&ds, routes, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
p, err := NewDataSourceProxy(&ds, routes, &ctx, "api/method", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
if tc.err == nil {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &url.URL{
|
||||
@@ -881,7 +879,7 @@ func getDatasourceProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *sett
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
@@ -993,14 +991,14 @@ func createAuthTest(t *testing.T, secretsService secrets.Service, dsType string,
|
||||
return test
|
||||
}
|
||||
|
||||
func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, test *testCase) {
|
||||
func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, cfg *setting.Cfg, test *testCase) {
|
||||
ctx := &models.ReqContext{}
|
||||
tracer, err := tracing.InitializeTracerForTest()
|
||||
require.NoError(t, err)
|
||||
|
||||
var routes []*plugins.Route
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(test.datasource, routes, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1013,6 +1011,7 @@ func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, test *t
|
||||
}
|
||||
|
||||
func Test_PathCheck(t *testing.T) {
|
||||
cfg := &setting.Cfg{}
|
||||
// Ensure that we test routes appropriately. This test reproduces a historical bug where two routes were defined with different role requirements but the same method and the more privileged route was tested first. Here we ensure auth checks are applied based on the correct route, not just the method.
|
||||
routes := []*plugins.Route{
|
||||
{
|
||||
@@ -1043,7 +1042,7 @@ func Test_PathCheck(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
secretsStore := kvstore.SetupTestService(t)
|
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
dsService := datasourceservice.ProvideService(bus.New(), nil, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewPermissionsServicesMock())
|
||||
proxy, err := NewDataSourceProxy(&models.DataSource{}, routes, ctx, "b", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, secretsService)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
|
||||
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
||||
"github.com/grafana/grafana-azure-sdk-go/aztokenprovider"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/aztokenprovider"
|
||||
)
|
||||
|
||||
type azureAccessTokenProvider struct {
|
||||
@@ -17,8 +19,8 @@ type azureAccessTokenProvider struct {
|
||||
}
|
||||
|
||||
func newAzureAccessTokenProvider(ctx context.Context, cfg *setting.Cfg, authParams *plugins.JWTTokenAuth) (*azureAccessTokenProvider, error) {
|
||||
credentials := getAzureCredentials(cfg, authParams)
|
||||
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(cfg, credentials)
|
||||
credentials := getAzureCredentials(cfg.Azure, authParams)
|
||||
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(cfg.Azure, credentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -33,7 +35,7 @@ func (provider *azureAccessTokenProvider) GetAccessToken() (string, error) {
|
||||
return provider.tokenProvider.GetAccessToken(provider.ctx, provider.scopes)
|
||||
}
|
||||
|
||||
func getAzureCredentials(cfg *setting.Cfg, authParams *plugins.JWTTokenAuth) azcredentials.AzureCredentials {
|
||||
func getAzureCredentials(settings *azsettings.AzureSettings, authParams *plugins.JWTTokenAuth) azcredentials.AzureCredentials {
|
||||
authType := strings.ToLower(authParams.Params["azure_auth_type"])
|
||||
clientId := authParams.Params["client_id"]
|
||||
|
||||
@@ -43,7 +45,7 @@ func getAzureCredentials(cfg *setting.Cfg, authParams *plugins.JWTTokenAuth) azc
|
||||
// before managed identities where introduced, therefore use client secret authentication
|
||||
// * If authType and other fields aren't set then it means the datasource never been configured
|
||||
// and managed identity is the default authentication choice as long as managed identities are enabled
|
||||
isManagedIdentity := authType == "msi" || (authType == "" && clientId == "" && cfg.Azure.ManagedIdentityEnabled)
|
||||
isManagedIdentity := authType == "msi" || (authType == "" && clientId == "" && settings.ManagedIdentityEnabled)
|
||||
|
||||
if isManagedIdentity {
|
||||
return &azcredentials.AzureManagedIdentityCredentials{}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package httpclientprovider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/aztokenprovider"
|
||||
)
|
||||
|
||||
const azureMiddlewareName = "AzureAuthentication.Provider"
|
||||
|
||||
func AzureMiddleware(cfg *setting.Cfg) httpclient.Middleware {
|
||||
return httpclient.NamedMiddlewareFunc(azureMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper {
|
||||
credentials, err := getAzureCredentials(opts.CustomOptions)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
} else if credentials == nil {
|
||||
return next
|
||||
}
|
||||
|
||||
tokenProvider, err := aztokenprovider.NewAzureAccessTokenProvider(cfg, credentials)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
scopes, err := getAzureEndpointScopes(opts.CustomOptions)
|
||||
if err != nil {
|
||||
return errorResponse(err)
|
||||
}
|
||||
|
||||
return aztokenprovider.ApplyAuth(tokenProvider, scopes, next)
|
||||
})
|
||||
}
|
||||
|
||||
func errorResponse(err error) http.RoundTripper {
|
||||
return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return nil, fmt.Errorf("invalid Azure configuration: %s", err)
|
||||
})
|
||||
}
|
||||
|
||||
func getAzureCredentials(customOptions map[string]interface{}) (azcredentials.AzureCredentials, error) {
|
||||
if untypedValue, ok := customOptions["_azureCredentials"]; !ok {
|
||||
return nil, nil
|
||||
} else if value, ok := untypedValue.(azcredentials.AzureCredentials); !ok {
|
||||
err := fmt.Errorf("the field 'azureCredentials' should be a valid credentials object")
|
||||
return nil, err
|
||||
} else {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
func getAzureEndpointResourceId(customOptions map[string]interface{}) (*url.URL, error) {
|
||||
var value string
|
||||
if untypedValue, ok := customOptions["azureEndpointResourceId"]; !ok {
|
||||
err := fmt.Errorf("the field 'azureEndpointResourceId' should be set")
|
||||
return nil, err
|
||||
} else if value, ok = untypedValue.(string); !ok {
|
||||
err := fmt.Errorf("the field 'azureEndpointResourceId' should be a string")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceId, err := url.Parse(value)
|
||||
if err != nil || resourceId.Scheme == "" || resourceId.Host == "" {
|
||||
err := fmt.Errorf("invalid endpoint Resource ID URL '%s'", value)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resourceId, nil
|
||||
}
|
||||
|
||||
func getAzureEndpointScopes(customOptions map[string]interface{}) ([]string, error) {
|
||||
resourceId, err := getAzureEndpointResourceId(customOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceId.Path = path.Join(resourceId.Path, ".default")
|
||||
scopes := []string{resourceId.String()}
|
||||
|
||||
return scopes, nil
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics/metricutil"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/mwitkow/go-conntrack"
|
||||
)
|
||||
@@ -17,7 +16,7 @@ import (
|
||||
var newProviderFunc = sdkhttpclient.NewProvider
|
||||
|
||||
// New creates a new HTTP client provider with pre-configured middlewares.
|
||||
func New(cfg *setting.Cfg, tracer tracing.Tracer, features featuremgmt.FeatureToggles) *sdkhttpclient.Provider {
|
||||
func New(cfg *setting.Cfg, tracer tracing.Tracer) *sdkhttpclient.Provider {
|
||||
logger := log.New("httpclient")
|
||||
userAgent := fmt.Sprintf("Grafana/%s", cfg.BuildVersion)
|
||||
|
||||
@@ -36,10 +35,6 @@ func New(cfg *setting.Cfg, tracer tracing.Tracer, features featuremgmt.FeatureTo
|
||||
|
||||
setDefaultTimeoutOptions(cfg)
|
||||
|
||||
if features.IsEnabled(featuremgmt.FlagHttpclientproviderAzureAuth) {
|
||||
middlewares = append(middlewares, AzureMiddleware(cfg))
|
||||
}
|
||||
|
||||
return newProviderFunc(sdkhttpclient.ProviderOptions{
|
||||
Middlewares: middlewares,
|
||||
ConfigureTransport: func(opts sdkhttpclient.Options, transport *http.Transport) {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -23,7 +22,7 @@ func TestHTTPClientProvider(t *testing.T) {
|
||||
})
|
||||
tracer, err := tracing.InitializeTracerForTest()
|
||||
require.NoError(t, err)
|
||||
_ = New(&setting.Cfg{SigV4AuthEnabled: false}, tracer, featuremgmt.WithFeatures())
|
||||
_ = New(&setting.Cfg{SigV4AuthEnabled: false}, tracer)
|
||||
require.Len(t, providerOpts, 1)
|
||||
o := providerOpts[0]
|
||||
require.Len(t, o.Middlewares, 6)
|
||||
@@ -47,7 +46,7 @@ func TestHTTPClientProvider(t *testing.T) {
|
||||
})
|
||||
tracer, err := tracing.InitializeTracerForTest()
|
||||
require.NoError(t, err)
|
||||
_ = New(&setting.Cfg{SigV4AuthEnabled: true}, tracer, featuremgmt.WithFeatures())
|
||||
_ = New(&setting.Cfg{SigV4AuthEnabled: true}, tracer)
|
||||
require.Len(t, providerOpts, 1)
|
||||
o := providerOpts[0]
|
||||
require.Len(t, o.Middlewares, 7)
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
// New returns a new Logger that has this logger's context plus the given context
|
||||
// New returns a new contextual Logger that has this logger's context plus the given context.
|
||||
New(ctx ...interface{}) *ConcreteLogger
|
||||
|
||||
Log(keyvals ...interface{}) error
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package level
|
||||
|
||||
import "github.com/go-kit/log"
|
||||
import (
|
||||
"github.com/go-kit/log"
|
||||
gokitlevel "github.com/go-kit/log/level"
|
||||
)
|
||||
|
||||
// Error returns a logger that includes a Key/ErrorValue pair.
|
||||
func Error(logger log.Logger) log.Logger {
|
||||
@@ -53,6 +56,12 @@ func (l *logger) Log(keyvals ...interface{}) error {
|
||||
levelAllowed = l.allowed&v.level != 0
|
||||
break
|
||||
}
|
||||
|
||||
if v, ok := keyvals[i].(gokitlevel.Value); ok {
|
||||
hasLevel = true
|
||||
levelAllowed = l.allowed&levelFromGokitLevel(v) != 0
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasLevel && l.squelchNoLevel {
|
||||
return l.errNoLevel
|
||||
@@ -196,6 +205,35 @@ const (
|
||||
levelError
|
||||
)
|
||||
|
||||
func IsKey(v interface{}) bool {
|
||||
return v != nil && (v == Key() || v == gokitlevel.Key())
|
||||
}
|
||||
|
||||
func GetValue(v interface{}) Value {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if val, ok := v.(Value); ok {
|
||||
return val
|
||||
}
|
||||
|
||||
if val, ok := v.(gokitlevel.Value); ok {
|
||||
switch val {
|
||||
case gokitlevel.InfoValue():
|
||||
return InfoValue()
|
||||
case gokitlevel.WarnValue():
|
||||
return WarnValue()
|
||||
case gokitlevel.ErrorValue():
|
||||
return ErrorValue()
|
||||
case gokitlevel.DebugValue():
|
||||
return DebugValue()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type levelValue struct {
|
||||
name string
|
||||
level
|
||||
@@ -203,3 +241,16 @@ type levelValue struct {
|
||||
|
||||
func (v *levelValue) String() string { return v.name }
|
||||
func (v *levelValue) levelVal() {}
|
||||
|
||||
func levelFromGokitLevel(l gokitlevel.Value) level {
|
||||
switch l.String() {
|
||||
case gokitlevel.ErrorValue().String():
|
||||
return levelError
|
||||
case gokitlevel.WarnValue().String():
|
||||
return levelWarn
|
||||
case gokitlevel.DebugValue().String():
|
||||
return levelDebug
|
||||
}
|
||||
|
||||
return levelInfo
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package level_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
gokitlog "github.com/go-kit/log"
|
||||
gokitlevel "github.com/go-kit/log/level"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/log/level"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewFilter(t *testing.T) {
|
||||
newFilteredLoggerScenario(t, "Given all levels is allowed should log all messages", level.AllowAll(), func(t *testing.T, ctx *scenarioContext) {
|
||||
logTestMessages(t, ctx)
|
||||
|
||||
require.Len(t, ctx.loggedArgs, 8)
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[0][2].(string))
|
||||
require.Equal(t, "info", ctx.loggedArgs[0][3].(level.Value).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[1][2].(string))
|
||||
require.Equal(t, "warn", ctx.loggedArgs[1][3].(level.Value).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[2][2].(string))
|
||||
require.Equal(t, "eror", ctx.loggedArgs[2][3].(level.Value).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[3][2].(string))
|
||||
require.Equal(t, "dbug", ctx.loggedArgs[3][3].(level.Value).String())
|
||||
|
||||
require.Equal(t, "level", ctx.loggedArgs[4][0].(string))
|
||||
require.Equal(t, "info", ctx.loggedArgs[4][1].(gokitlevel.Value).String())
|
||||
require.Equal(t, "level", ctx.loggedArgs[5][0].(string))
|
||||
require.Equal(t, "warn", ctx.loggedArgs[5][1].(gokitlevel.Value).String())
|
||||
require.Equal(t, "level", ctx.loggedArgs[6][0].(string))
|
||||
require.Equal(t, "error", ctx.loggedArgs[6][1].(gokitlevel.Value).String())
|
||||
require.Equal(t, "level", ctx.loggedArgs[7][0].(string))
|
||||
require.Equal(t, "debug", ctx.loggedArgs[7][1].(gokitlevel.Value).String())
|
||||
})
|
||||
|
||||
newFilteredLoggerScenario(t, "Given error, warnings, info, debug is allowed should log all messages", level.AllowDebug(), func(t *testing.T, ctx *scenarioContext) {
|
||||
logTestMessages(t, ctx)
|
||||
|
||||
require.Len(t, ctx.loggedArgs, 8)
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[0][2].(string))
|
||||
require.Equal(t, "info", ctx.loggedArgs[0][3].(level.Value).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[1][2].(string))
|
||||
require.Equal(t, "warn", ctx.loggedArgs[1][3].(level.Value).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[2][2].(string))
|
||||
require.Equal(t, "eror", ctx.loggedArgs[2][3].(level.Value).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[3][2].(string))
|
||||
require.Equal(t, "dbug", ctx.loggedArgs[3][3].(level.Value).String())
|
||||
|
||||
require.Equal(t, "level", ctx.loggedArgs[4][0].(string))
|
||||
require.Equal(t, "info", ctx.loggedArgs[4][1].(gokitlevel.Value).String())
|
||||
require.Equal(t, "level", ctx.loggedArgs[5][0].(string))
|
||||
require.Equal(t, "warn", ctx.loggedArgs[5][1].(gokitlevel.Value).String())
|
||||
require.Equal(t, "level", ctx.loggedArgs[6][0].(string))
|
||||
require.Equal(t, "error", ctx.loggedArgs[6][1].(gokitlevel.Value).String())
|
||||
require.Equal(t, "level", ctx.loggedArgs[7][0].(string))
|
||||
require.Equal(t, "debug", ctx.loggedArgs[7][1].(gokitlevel.Value).String())
|
||||
})
|
||||
|
||||
newFilteredLoggerScenario(t, "Given error, warnings is allowed should log error and warning messages", level.AllowWarn(), func(t *testing.T, ctx *scenarioContext) {
|
||||
logTestMessages(t, ctx)
|
||||
|
||||
require.Len(t, ctx.loggedArgs, 4)
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[0][2].(string))
|
||||
require.Equal(t, "warn", ctx.loggedArgs[0][3].(level.Value).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[1][2].(string))
|
||||
require.Equal(t, "eror", ctx.loggedArgs[1][3].(level.Value).String())
|
||||
|
||||
require.Equal(t, "level", ctx.loggedArgs[2][0].(string))
|
||||
require.Equal(t, "warn", ctx.loggedArgs[2][1].(gokitlevel.Value).String())
|
||||
require.Equal(t, "level", ctx.loggedArgs[3][0].(string))
|
||||
require.Equal(t, "error", ctx.loggedArgs[3][1].(gokitlevel.Value).String())
|
||||
})
|
||||
|
||||
newFilteredLoggerScenario(t, "Given error allowed should log error messages", level.AllowError(), func(t *testing.T, ctx *scenarioContext) {
|
||||
logTestMessages(t, ctx)
|
||||
|
||||
require.Len(t, ctx.loggedArgs, 2)
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[0][2].(string))
|
||||
require.Equal(t, "eror", ctx.loggedArgs[0][3].(level.Value).String())
|
||||
|
||||
require.Equal(t, "level", ctx.loggedArgs[1][0].(string))
|
||||
require.Equal(t, "error", ctx.loggedArgs[1][1].(gokitlevel.Value).String())
|
||||
})
|
||||
|
||||
newFilteredLoggerScenario(t, "Given error, warnings, info is allowed should log error, warning and info messages", level.AllowInfo(), func(t *testing.T, ctx *scenarioContext) {
|
||||
logTestMessages(t, ctx)
|
||||
|
||||
require.Len(t, ctx.loggedArgs, 6)
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[0][2].(string))
|
||||
require.Equal(t, "info", ctx.loggedArgs[0][3].(level.Value).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[1][2].(string))
|
||||
require.Equal(t, "warn", ctx.loggedArgs[1][3].(level.Value).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[2][2].(string))
|
||||
require.Equal(t, "eror", ctx.loggedArgs[2][3].(level.Value).String())
|
||||
|
||||
require.Equal(t, "level", ctx.loggedArgs[3][0].(string))
|
||||
require.Equal(t, "info", ctx.loggedArgs[3][1].(gokitlevel.Value).String())
|
||||
require.Equal(t, "level", ctx.loggedArgs[4][0].(string))
|
||||
require.Equal(t, "warn", ctx.loggedArgs[4][1].(gokitlevel.Value).String())
|
||||
require.Equal(t, "level", ctx.loggedArgs[5][0].(string))
|
||||
require.Equal(t, "error", ctx.loggedArgs[5][1].(gokitlevel.Value).String())
|
||||
})
|
||||
|
||||
newFilteredLoggerScenario(t, "Given no levels is allowed should not log any messages", level.AllowNone(), func(t *testing.T, ctx *scenarioContext) {
|
||||
logTestMessages(t, ctx)
|
||||
|
||||
require.Len(t, ctx.loggedArgs, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func logTestMessages(t *testing.T, ctx *scenarioContext) {
|
||||
t.Helper()
|
||||
|
||||
ctx.logger.Info("info msg")
|
||||
ctx.logger.Warn("warn msg")
|
||||
ctx.logger.Error("error msg")
|
||||
ctx.logger.Debug("debug msg")
|
||||
err := gokitlevel.Info(ctx.logger).Log("msg", "gokit info msg")
|
||||
require.NoError(t, err)
|
||||
err = gokitlevel.Warn(ctx.logger).Log("msg", "gokit warn msg")
|
||||
require.NoError(t, err)
|
||||
err = gokitlevel.Error(ctx.logger).Log("msg", "gokit error msg")
|
||||
require.NoError(t, err)
|
||||
err = gokitlevel.Debug(ctx.logger).Log("msg", "gokit debug msg")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
type scenarioContext struct {
|
||||
loggedArgs [][]interface{}
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func newFilteredLoggerScenario(t *testing.T, desc string, option level.Option, fn func(t *testing.T, ctx *scenarioContext)) {
|
||||
t.Helper()
|
||||
|
||||
ctx := &scenarioContext{
|
||||
loggedArgs: [][]interface{}{},
|
||||
}
|
||||
|
||||
l := gokitlog.LoggerFunc(func(i ...interface{}) error {
|
||||
ctx.loggedArgs = append(ctx.loggedArgs, i)
|
||||
return nil
|
||||
})
|
||||
filteredLogger := level.NewFilter(l, option)
|
||||
testLogger := log.New("test")
|
||||
testLogger.Swap(filteredLogger)
|
||||
|
||||
ctx.logger = testLogger
|
||||
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
fn(t, ctx)
|
||||
})
|
||||
}
|
||||
@@ -192,6 +192,15 @@ func (cl *ConcreteLogger) New(ctx ...interface{}) *ConcreteLogger {
|
||||
return newConcreteLogger(gokitlog.With(&cl.SwapLogger), ctx...)
|
||||
}
|
||||
|
||||
// New creates a new logger.
|
||||
// First ctx argument is expected to be the name of the logger.
|
||||
// Note: For a contextual logger, i.e. a logger with a shared
|
||||
// name plus additional contextual information, you must use the
|
||||
// Logger interface New method for it to work as expected.
|
||||
// Example creating a shared logger:
|
||||
// requestLogger := log.New("request-logger")
|
||||
// Example creating a contextual logger:
|
||||
// contextualLogger := requestLogger.New("username", "user123")
|
||||
func New(ctx ...interface{}) *ConcreteLogger {
|
||||
if len(ctx) == 0 {
|
||||
return root.New()
|
||||
|
||||
@@ -30,8 +30,9 @@ func TestLogger(t *testing.T) {
|
||||
log4 := log3.New("key", "value")
|
||||
err = log4.Log("msg", "hello 4")
|
||||
require.NoError(t, err)
|
||||
log3.Error("hello 3 again")
|
||||
|
||||
require.Len(t, ctx.loggedArgs, 4)
|
||||
require.Len(t, ctx.loggedArgs, 5)
|
||||
require.Len(t, ctx.loggedArgs[0], 4)
|
||||
require.Equal(t, "logger", ctx.loggedArgs[0][0].(string))
|
||||
require.Equal(t, "one", ctx.loggedArgs[0][1].(string))
|
||||
@@ -62,6 +63,16 @@ func TestLogger(t *testing.T) {
|
||||
require.Equal(t, "msg", ctx.loggedArgs[3][4].(string))
|
||||
require.Equal(t, "hello 4", ctx.loggedArgs[3][5].(string))
|
||||
|
||||
require.Len(t, ctx.loggedArgs[4], 8)
|
||||
require.Equal(t, "logger", ctx.loggedArgs[4][0].(string))
|
||||
require.Equal(t, "three", ctx.loggedArgs[4][1].(string))
|
||||
require.Equal(t, "t", ctx.loggedArgs[4][2].(string))
|
||||
require.Equal(t, ctx.mockedTime.Format("2006-01-02T15:04:05.99-0700"), ctx.loggedArgs[4][3].(fmt.Stringer).String())
|
||||
require.Equal(t, "lvl", ctx.loggedArgs[4][4].(string))
|
||||
require.Equal(t, level.ErrorValue(), ctx.loggedArgs[4][5].(level.Value))
|
||||
require.Equal(t, "msg", ctx.loggedArgs[4][6].(string))
|
||||
require.Equal(t, "hello 3 again", ctx.loggedArgs[4][7].(string))
|
||||
|
||||
t.Run("When initializing root logger should swap loggers as expected", func(t *testing.T) {
|
||||
swappedLoggedArgs := [][]interface{}{}
|
||||
swapLogger := gokitlog.LoggerFunc(func(i ...interface{}) error {
|
||||
@@ -87,7 +98,7 @@ func TestLogger(t *testing.T) {
|
||||
log3.Error("hello 3")
|
||||
log3.Debug("debug")
|
||||
|
||||
require.Len(t, ctx.loggedArgs, 4)
|
||||
require.Len(t, ctx.loggedArgs, 5)
|
||||
require.Len(t, swappedLoggedArgs, 7, "expected 4 messages for AllowAll logger and 3 messages for AllowInfo logger")
|
||||
})
|
||||
})
|
||||
|
||||
+14
-19
@@ -8,8 +8,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
gokitsyslog "github.com/go-kit/log/syslog"
|
||||
"github.com/grafana/grafana/pkg/infra/log/level"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
@@ -25,31 +25,26 @@ type SysLogHandler struct {
|
||||
|
||||
var selector = func(keyvals ...interface{}) syslog.Priority {
|
||||
for i := 0; i < len(keyvals); i += 2 {
|
||||
if keyvals[i] == level.Key() {
|
||||
if v, ok := keyvals[i+1].(string); ok {
|
||||
switch v {
|
||||
case "emergency":
|
||||
return syslog.LOG_EMERG
|
||||
case "alert":
|
||||
return syslog.LOG_ALERT
|
||||
case "critical":
|
||||
return syslog.LOG_CRIT
|
||||
case "error":
|
||||
if level.IsKey(keyvals[i]) {
|
||||
val := level.GetValue(keyvals[i+1])
|
||||
if val != nil {
|
||||
switch val {
|
||||
case level.ErrorValue():
|
||||
return syslog.LOG_ERR
|
||||
case "warning":
|
||||
case level.WarnValue():
|
||||
return syslog.LOG_WARNING
|
||||
case "notice":
|
||||
return syslog.LOG_NOTICE
|
||||
case "info":
|
||||
case level.InfoValue():
|
||||
return syslog.LOG_INFO
|
||||
case "debug":
|
||||
case level.DebugValue():
|
||||
return syslog.LOG_DEBUG
|
||||
}
|
||||
return syslog.LOG_LOCAL0
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
return syslog.LOG_LOCAL0
|
||||
|
||||
return syslog.LOG_INFO
|
||||
}
|
||||
|
||||
func NewSyslog(sec *ini.Section, format Formatedlogger) *SysLogHandler {
|
||||
@@ -62,7 +57,7 @@ func NewSyslog(sec *ini.Section, format Formatedlogger) *SysLogHandler {
|
||||
handler.Tag = sec.Key("tag").MustString("")
|
||||
|
||||
if err := handler.Init(); err != nil {
|
||||
_ = level.Error(root).Log("Failed to init syslog log handler", "error", err)
|
||||
root.Error("Failed to init syslog log handler", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
handler.logger = gokitsyslog.NewSyslogLogger(handler.syslog, format, gokitsyslog.PrioritySelectorOption(selector))
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -19,7 +21,7 @@ type Cfg struct {
|
||||
AWSAssumeRoleEnabled bool
|
||||
|
||||
// Azure Cloud settings
|
||||
Azure setting.AzureSettings
|
||||
Azure *azsettings.AzureSettings
|
||||
|
||||
BuildVersion string // TODO Remove
|
||||
}
|
||||
|
||||
@@ -84,14 +84,17 @@ func (i *Initializer) awsEnvVars() []string {
|
||||
|
||||
func (i *Initializer) azureEnvVars() []string {
|
||||
var variables []string
|
||||
if i.cfg.Azure.Cloud != "" {
|
||||
variables = append(variables, "AZURE_CLOUD="+i.cfg.Azure.Cloud)
|
||||
}
|
||||
if i.cfg.Azure.ManagedIdentityClientId != "" {
|
||||
variables = append(variables, "AZURE_MANAGED_IDENTITY_CLIENT_ID="+i.cfg.Azure.ManagedIdentityClientId)
|
||||
}
|
||||
if i.cfg.Azure.ManagedIdentityEnabled {
|
||||
variables = append(variables, "AZURE_MANAGED_IDENTITY_ENABLED=true")
|
||||
|
||||
if i.cfg.Azure != nil {
|
||||
if i.cfg.Azure.Cloud != "" {
|
||||
variables = append(variables, "AZURE_CLOUD="+i.cfg.Azure.Cloud)
|
||||
}
|
||||
if i.cfg.Azure.ManagedIdentityClientId != "" {
|
||||
variables = append(variables, "AZURE_MANAGED_IDENTITY_CLIENT_ID="+i.cfg.Azure.ManagedIdentityClientId)
|
||||
}
|
||||
if i.cfg.Azure.ManagedIdentityEnabled {
|
||||
variables = append(variables, "AZURE_MANAGED_IDENTITY_ENABLED=true")
|
||||
}
|
||||
}
|
||||
|
||||
return variables
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestPluginManager_int_init(t *testing.T) {
|
||||
idb := influxdb.ProvideService(hcp)
|
||||
lk := loki.ProvideService(hcp, tracer)
|
||||
otsdb := opentsdb.ProvideService(hcp)
|
||||
pr := prometheus.ProvideService(hcp, tracer)
|
||||
pr := prometheus.ProvideService(hcp, cfg, features, tracer)
|
||||
tmpo := tempo.ProvideService(hcp)
|
||||
td := testdatasource.ProvideService(cfg, features)
|
||||
pg := postgres.ProvideService(cfg)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-azure-sdk-go/azsettings"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@@ -574,9 +575,11 @@ func newScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *managerS
|
||||
cfg.AWSAllowedAuthProviders = []string{"keys", "credentials"}
|
||||
cfg.AWSAssumeRoleEnabled = true
|
||||
|
||||
cfg.Azure.ManagedIdentityEnabled = true
|
||||
cfg.Azure.Cloud = "AzureCloud"
|
||||
cfg.Azure.ManagedIdentityClientId = "client-id"
|
||||
cfg.Azure = &azsettings.AzureSettings{
|
||||
ManagedIdentityEnabled: true,
|
||||
Cloud: "AzureCloud",
|
||||
ManagedIdentityClientId: "client-id",
|
||||
}
|
||||
|
||||
loader := &fakeLoader{}
|
||||
manager := New(cfg, nil, loader)
|
||||
|
||||
@@ -320,11 +320,10 @@ const (
|
||||
ScopeTeamsAll = "teams:*"
|
||||
|
||||
// Annotations related actions
|
||||
ActionAnnotationsCreate = "annotations:create"
|
||||
ActionAnnotationsDelete = "annotations:delete"
|
||||
ActionAnnotationsRead = "annotations:read"
|
||||
ActionAnnotationsWrite = "annotations:write"
|
||||
ActionAnnotationsTagsRead = "annotations.tags:read"
|
||||
ActionAnnotationsCreate = "annotations:create"
|
||||
ActionAnnotationsDelete = "annotations:delete"
|
||||
ActionAnnotationsRead = "annotations:read"
|
||||
ActionAnnotationsWrite = "annotations:write"
|
||||
|
||||
// Dashboard actions
|
||||
ActionDashboardsCreate = "dashboards:create"
|
||||
@@ -382,9 +381,6 @@ var (
|
||||
ScopeAnnotationsID = Scope(ScopeAnnotationsRoot, "id", Parameter(":annotationId"))
|
||||
ScopeAnnotationsTypeDashboard = ScopeAnnotationsProvider.GetResourceScopeType("dashboard")
|
||||
ScopeAnnotationsTypeOrganization = ScopeAnnotationsProvider.GetResourceScopeType("organization")
|
||||
|
||||
// Annotation tag scopes
|
||||
ScopeAnnotationsTagsAll = "annotations:tags:*"
|
||||
)
|
||||
|
||||
const RoleGrafanaAdmin = "Grafana Admin"
|
||||
|
||||
@@ -137,7 +137,7 @@ func (h *ContextHandler) Middleware(mContext *web.Context) {
|
||||
case h.initContextWithAnonymousUser(reqContext):
|
||||
}
|
||||
|
||||
reqContext.Logger = log.New("context", "userId", reqContext.UserId, "orgId", reqContext.OrgId, "uname", reqContext.Login)
|
||||
reqContext.Logger = reqContext.Logger.New("userId", reqContext.UserId, "orgId", reqContext.OrgId, "uname", reqContext.Login)
|
||||
span.AddEvents(
|
||||
[]string{"uname", "orgId", "userId"},
|
||||
[]tracing.EventValue{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user