Merge branch 'main' into encryption/use-secrets-service
This commit is contained in:
+241
-277
@@ -96,6 +96,36 @@ steps:
|
||||
TEST_MAX_WORKERS: 50%
|
||||
image: grafana/build-container:1.4.3
|
||||
name: test-frontend
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--variants linux-x64,linux-x64-musl,osx64,win64,armv6 --no-pull-enterprise
|
||||
@@ -225,38 +255,6 @@ steps:
|
||||
dry_run: true
|
||||
edition: oss
|
||||
ubuntu: false
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
trigger:
|
||||
event:
|
||||
- pull_request
|
||||
@@ -370,6 +368,36 @@ steps:
|
||||
TEST_MAX_WORKERS: 50%
|
||||
image: grafana/build-container:1.4.3
|
||||
name: test-frontend
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
@@ -540,38 +568,6 @@ steps:
|
||||
ubuntu: true
|
||||
username:
|
||||
from_secret: docker_user
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- ./scripts/circle-release-canary-packages.sh
|
||||
depends_on:
|
||||
@@ -585,8 +581,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads
|
||||
depends_on:
|
||||
- end-to-end-tests
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
environment:
|
||||
GCP_GRAFANA_UPLOAD_KEY:
|
||||
from_secret: gcp_key
|
||||
@@ -827,6 +821,36 @@ steps:
|
||||
TEST_MAX_WORKERS: 50%
|
||||
image: grafana/build-container:1.4.3
|
||||
name: test-frontend
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN}
|
||||
--no-pull-enterprise ${DRONE_TAG}
|
||||
@@ -949,38 +973,6 @@ steps:
|
||||
ubuntu: true
|
||||
username:
|
||||
from_secret: docker_user
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- yarn storybook:build
|
||||
- ./bin/grabpl verify-storybook
|
||||
@@ -1003,8 +995,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads
|
||||
depends_on:
|
||||
- end-to-end-tests
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
environment:
|
||||
GCP_GRAFANA_UPLOAD_KEY:
|
||||
from_secret: gcp_key
|
||||
@@ -1209,6 +1199,36 @@ steps:
|
||||
TEST_MAX_WORKERS: 50%
|
||||
image: grafana/build-container:1.4.3
|
||||
name: test-frontend
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN}
|
||||
--no-pull-enterprise ${DRONE_TAG}
|
||||
@@ -1367,38 +1387,6 @@ steps:
|
||||
ubuntu: true
|
||||
username:
|
||||
from_secret: docker_user
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- dockerize -wait tcp://redis:6379/0 -timeout 120s
|
||||
- ./bin/grabpl integration-tests
|
||||
@@ -1432,8 +1420,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition enterprise --packages-bucket grafana-downloads
|
||||
depends_on:
|
||||
- end-to-end-tests
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
- redis-integration-tests
|
||||
- memcached-integration-tests
|
||||
environment:
|
||||
@@ -1496,8 +1482,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition enterprise2 --packages-bucket grafana-downloads-enterprise2
|
||||
depends_on:
|
||||
- end-to-end-tests-enterprise2
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
- redis-integration-tests
|
||||
- memcached-integration-tests
|
||||
environment:
|
||||
@@ -1771,6 +1755,36 @@ steps:
|
||||
TEST_MAX_WORKERS: 50%
|
||||
image: grafana/build-container:1.4.3
|
||||
name: test-frontend
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN}
|
||||
--no-pull-enterprise v7.3.0-test
|
||||
@@ -1885,38 +1899,6 @@ steps:
|
||||
dry_run: true
|
||||
edition: oss
|
||||
ubuntu: true
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- yarn storybook:build
|
||||
- ./bin/grabpl verify-storybook
|
||||
@@ -1939,8 +1921,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads-test
|
||||
depends_on:
|
||||
- end-to-end-tests
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
environment:
|
||||
GCP_GRAFANA_UPLOAD_KEY:
|
||||
from_secret: gcp_key
|
||||
@@ -2142,6 +2122,36 @@ steps:
|
||||
TEST_MAX_WORKERS: 50%
|
||||
image: grafana/build-container:1.4.3
|
||||
name: test-frontend
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN}
|
||||
--no-pull-enterprise v7.3.0-test
|
||||
@@ -2292,38 +2302,6 @@ steps:
|
||||
dry_run: true
|
||||
edition: enterprise
|
||||
ubuntu: true
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- dockerize -wait tcp://redis:6379/0 -timeout 120s
|
||||
- ./bin/grabpl integration-tests
|
||||
@@ -2357,8 +2335,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition enterprise --packages-bucket grafana-downloads-test
|
||||
depends_on:
|
||||
- end-to-end-tests
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
- redis-integration-tests
|
||||
- memcached-integration-tests
|
||||
environment:
|
||||
@@ -2421,8 +2397,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition enterprise2 --packages-bucket grafana-downloads-test
|
||||
depends_on:
|
||||
- end-to-end-tests-enterprise2
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
- redis-integration-tests
|
||||
- memcached-integration-tests
|
||||
environment:
|
||||
@@ -2701,6 +2675,36 @@ steps:
|
||||
TEST_MAX_WORKERS: 50%
|
||||
image: grafana/build-container:1.4.3
|
||||
name: test-frontend
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
@@ -2813,38 +2817,6 @@ steps:
|
||||
dry_run: true
|
||||
edition: oss
|
||||
ubuntu: true
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- yarn storybook:build
|
||||
- ./bin/grabpl verify-storybook
|
||||
@@ -2867,8 +2839,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition oss --packages-bucket grafana-downloads
|
||||
depends_on:
|
||||
- end-to-end-tests
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
environment:
|
||||
GCP_GRAFANA_UPLOAD_KEY:
|
||||
from_secret: gcp_key
|
||||
@@ -3044,6 +3014,36 @@ steps:
|
||||
TEST_MAX_WORKERS: 50%
|
||||
image: grafana/build-container:1.4.3
|
||||
name: test-frontend
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- initialize
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- ./bin/grabpl build-backend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER}
|
||||
--no-pull-enterprise
|
||||
@@ -3190,38 +3190,6 @@ steps:
|
||||
dry_run: true
|
||||
edition: enterprise
|
||||
ubuntu: true
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq postgresql-client
|
||||
- dockerize -wait tcp://postgres:5432 -timeout 120s
|
||||
- psql -p 5432 -h postgres -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database postgres
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: postgres
|
||||
PGPASSWORD: grafanatest
|
||||
POSTGRES_HOST: postgres
|
||||
image: grafana/build-container:1.4.3
|
||||
name: postgres-integration-tests
|
||||
- commands:
|
||||
- apt-get update
|
||||
- apt-get install -yq default-mysql-client
|
||||
- dockerize -wait tcp://mysql:3306 -timeout 120s
|
||||
- cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h mysql -P 3306 -u root
|
||||
-prootpass
|
||||
- go clean -testcache
|
||||
- ./bin/grabpl integration-tests --database mysql
|
||||
depends_on:
|
||||
- test-backend
|
||||
- test-frontend
|
||||
environment:
|
||||
GRAFANA_TEST_DB: mysql
|
||||
MYSQL_HOST: mysql
|
||||
image: grafana/build-container:1.4.3
|
||||
name: mysql-integration-tests
|
||||
- commands:
|
||||
- yarn storybook:build
|
||||
- ./bin/grabpl verify-storybook
|
||||
@@ -3264,8 +3232,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition enterprise --packages-bucket grafana-downloads
|
||||
depends_on:
|
||||
- end-to-end-tests
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
- redis-integration-tests
|
||||
- memcached-integration-tests
|
||||
environment:
|
||||
@@ -3328,8 +3294,6 @@ steps:
|
||||
- ./bin/grabpl upload-packages --edition enterprise2 --packages-bucket grafana-downloads-enterprise2
|
||||
depends_on:
|
||||
- end-to-end-tests-enterprise2
|
||||
- mysql-integration-tests
|
||||
- postgres-integration-tests
|
||||
- redis-integration-tests
|
||||
- memcached-integration-tests
|
||||
environment:
|
||||
@@ -3485,6 +3449,6 @@ kind: secret
|
||||
name: drone_token
|
||||
---
|
||||
kind: signature
|
||||
hmac: 0d26eda19dc6e530d02c734704c8a9d01beb5082c7e14b2609577b8695ed06f5
|
||||
hmac: 103fe906b04e8a336e3113007921d62a57107bc7a86a14442b438fa54406c084
|
||||
|
||||
...
|
||||
|
||||
+20
-12
@@ -14,13 +14,15 @@
|
||||
# Documentation owner: Jita Chatterjee
|
||||
/docs/ @grafana/docs-squad @pkolyvas
|
||||
/contribute/ @marcusolsson @grafana/docs-squad @pkolyvas
|
||||
/docs/sources/developers/plugins/ @marcusolsson @grafana/docs-squad
|
||||
/docs/sources/developers/plugins/ @marcusolsson @grafana/docs-squad @grafana/plugins-platform
|
||||
/docs/sources/developers/plugins/backend @marcusolsson @grafana/docs-squad @grafana/plugins-platform-backend
|
||||
/docs/sources/enterprise/ @osg-grafana @grafana/docs-squad
|
||||
|
||||
# Backend code
|
||||
*.go @grafana/backend-platform
|
||||
go.mod @grafana/backend-platform
|
||||
go.sum @grafana/backend-platform
|
||||
/.bingo @grafana/backend-platform
|
||||
|
||||
# Continuous Integration
|
||||
.drone.yml @grafana/grafana-release-eng
|
||||
@@ -56,25 +58,31 @@ go.sum @grafana/backend-platform
|
||||
/pkg/services/libraryelements @grafana/user-essentials
|
||||
/pkg/services/librarypanels @grafana/user-essentials
|
||||
|
||||
# Plugins
|
||||
/pkg/api/pluginproxy @grafana/plugins-platform-backend
|
||||
/pkg/plugins @grafana/plugins-platform-backend
|
||||
/pkg/services/datasourceproxy @grafana/plugins-platform-backend
|
||||
/pkg/services/datasources @grafana/plugins-platform-backend
|
||||
|
||||
# Backend code docs
|
||||
/contribute/style-guides/backend.md @grafana/backend-platform
|
||||
/contribute/architecture/backend @grafana/backend-platform
|
||||
/contribute/engineering/backend @grafana/backend-platform
|
||||
|
||||
/e2e @grafana/user-essentials
|
||||
/packages @grafana/user-essentials @grafana/plugins-platform @grafana/grafana-bi-squad
|
||||
/packages @grafana/user-essentials @grafana/plugins-platform-frontend @grafana/grafana-bi-squad
|
||||
/packages/grafana-e2e-selectors @grafana/user-essentials
|
||||
/packages/grafana-e2e @grafana/user-essentials
|
||||
/packages/grafana-toolkit @grafana/plugins-platform
|
||||
/packages/grafana-ui/.storybook @grafana/plugins-platform
|
||||
/packages/grafana-toolkit @grafana/plugins-platform-frontend
|
||||
/packages/grafana-ui/.storybook @grafana/plugins-platform-frontend
|
||||
/packages/grafana-ui/src/components/DateTimePickers @grafana/grafana-bi-squad
|
||||
/packages/grafana-ui/src/components/GraphNG @grafana/grafana-bi-squad
|
||||
/packages/grafana-ui/src/components/Table @grafana/grafana-bi-squad
|
||||
/packages/grafana-ui/src/components/TimeSeries @grafana/grafana-bi-squad
|
||||
/packages/grafana-ui/src/components/uPlot @grafana/grafana-bi-squad
|
||||
/packages/grafana-ui/src/utils/storybook @grafana/plugins-platform
|
||||
/packages/grafana-ui/src/utils/storybook @grafana/plugins-platform-frontend
|
||||
/packages/jaeger-ui-components/ @grafana/observability-squad
|
||||
/plugins-bundled @grafana/plugins-platform
|
||||
/plugins-bundled @grafana/plugins-platform-frontend
|
||||
/public @grafana/user-essentials
|
||||
/public/app/core/components/TimePicker @grafana/grafana-bi-squad
|
||||
/public/app/features/canvas/ @grafana/grafana-edge-squad
|
||||
@@ -93,11 +101,11 @@ go.sum @grafana/backend-platform
|
||||
/public/app/plugins/panel/geomap @grafana/grafana-edge-squad
|
||||
/public/app/plugins/panel/canvas @grafana/grafana-edge-squad
|
||||
/public/app/plugins/panel/icon @grafana/grafana-edge-squad
|
||||
/scripts/build/release-packages.sh @grafana/plugins-platform
|
||||
/scripts/circle-release-next-packages.sh @grafana/plugins-platform
|
||||
/scripts/ci-frontend-metrics.sh @grafana/user-essentials @grafana/plugins-platform @grafana/grafana-bi-squad
|
||||
/scripts/ci-reference-docs-build.sh @grafana/plugins-platform
|
||||
/scripts/ci-reference-docs-lint.sh @grafana/plugins-platform
|
||||
/scripts/build/release-packages.sh @grafana/plugins-platform-frontend
|
||||
/scripts/circle-release-next-packages.sh @grafana/plugins-platform-frontend
|
||||
/scripts/ci-frontend-metrics.sh @grafana/user-essentials @grafana/plugins-platform-frontend @grafana/grafana-bi-squad
|
||||
/scripts/ci-reference-docs-build.sh @grafana/plugins-platform-frontend
|
||||
/scripts/ci-reference-docs-lint.sh @grafana/plugins-platform-frontend
|
||||
/scripts/grunt @grafana/frontend-ops
|
||||
/scripts/webpack @grafana/frontend-ops
|
||||
/scripts/generate-a11y-report.sh @grafana/user-essentials
|
||||
@@ -111,7 +119,7 @@ lerna.json @grafana/frontend-ops
|
||||
.pa11yci-pr.conf.js @grafana/user-essentials
|
||||
|
||||
# @grafana/ui component documentation
|
||||
*.mdx @marcusolsson @jessover9000 @grafana/plugins-platform
|
||||
*.mdx @marcusolsson @jessover9000 @grafana/plugins-platform-frontend
|
||||
|
||||
# Core datasources
|
||||
/public/app/plugins/datasource/cloudwatch @grafana/cloud-datasources @grafana/observability-squad
|
||||
|
||||
@@ -153,7 +153,7 @@ clean: ## Clean up intermediate build artifacts.
|
||||
# This repository's configuration is protected (https://readme.drone.io/signature/).
|
||||
# Use this make target to regenerate the configuration YAML files when
|
||||
# you modify starlark files.
|
||||
drone:
|
||||
drone: $(DRONE)
|
||||
@if [ "$(DRONE_VERSION)" != "1.4.0" ]; then\
|
||||
echo "WARN: You are using drone-cli ${DRONE_VERSION}. Please update your LOCAL version to 1.4.0. Using latest bingo version...";\
|
||||
fi
|
||||
|
||||
@@ -11,7 +11,7 @@ weight = 300
|
||||
The LDAP integration in Grafana allows your Grafana users to login with their LDAP credentials. You can also specify mappings between LDAP
|
||||
group memberships and Grafana Organization user roles.
|
||||
|
||||
> [Enhanced LDAP authentication]({{< relref "../enterprise/enhanced_ldap.md" >}}) is available in [Grafana Cloud Pro and Advanced](https://grafana.com/docs/grafana-cloud/) and in [Grafana Enterprise]({{< relref "../enterprise" >}}).
|
||||
> [Enhanced LDAP authentication]({{< relref "../enterprise/enhanced_ldap.md" >}}) is available in [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/) and in [Grafana Enterprise]({{< relref "../enterprise" >}}).
|
||||
|
||||
> Refer to [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) in Grafana Enterprise to understand how you can control access with fine-grained permissions.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ weight = 600
|
||||
|
||||
The enhanced LDAP integration adds additional functionality on top of the [LDAP integration]({{< relref "../auth/ldap.md" >}}) available in the open source edition of Grafana.
|
||||
|
||||
> Enhanced LDAP integration is only available in Grafana Enterprise.
|
||||
> Enhanced LDAP integration is only available in [Grafana Cloud Advanced](https://grafana.com/docs/grafana-cloud/) and in [Grafana Enterprise]({{< relref "../enterprise" >}}).
|
||||
|
||||
> Refer to [Fine-grained access control]({{< relref "../enterprise/access-control/_index.md" >}}) in Grafana Enterprise to understand how you can control access with fine-grained permissions.
|
||||
|
||||
|
||||
@@ -91,7 +91,8 @@ margin-bottom: 25px;
|
||||
.footer {
|
||||
background-color: #2e2e2e;
|
||||
color: #999999;
|
||||
margin-top: 20px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
<tr>
|
||||
<td colspan="2" class="annotations">
|
||||
[[ range .Annotations.SortedPairs ]]
|
||||
<p>[[ .Name ]]: [[ .Value ]]</p>
|
||||
<p><span class="annotations-heading">[[ .Name ]]:</span> <span class="annotations-value">[[ .Value ]]</span></p>
|
||||
[[ end ]]
|
||||
</td>
|
||||
</tr>
|
||||
[[ end ]]
|
||||
<tr>
|
||||
<td valign="top" class="labels-heading">Labels:</td>
|
||||
<td>
|
||||
<td colspan="2">
|
||||
<span class="labels-heading">Labels:</span>
|
||||
<ul class="labels-list">
|
||||
[[ range .Labels.SortedPairs ]]<li>[[ .Name ]]: [[ .Value ]]</li>[[ end ]]
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<td colspan="2" class="actions">
|
||||
[[ if .SilenceURL ]]
|
||||
<a
|
||||
href="[[ .SilenceURL ]]"
|
||||
@@ -116,10 +116,24 @@
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
}
|
||||
.annotations-heading {
|
||||
font-weight: bold;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.annotations-value {
|
||||
padding-left: 8px;
|
||||
}
|
||||
.labels-list {
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
padding-left: 8px;
|
||||
}
|
||||
.actions {
|
||||
padding: 24px 0 12px 0;
|
||||
}
|
||||
.section-heading {
|
||||
color: #2c3235;
|
||||
@@ -132,6 +146,7 @@
|
||||
font-weight: bold;
|
||||
padding: 0 0 0 12px;
|
||||
text-decoration: underline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.status-tag {
|
||||
color: #ffffff;
|
||||
@@ -213,6 +228,6 @@
|
||||
</table>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
</table>
|
||||
[[ end ]]
|
||||
[[ end ]]
|
||||
|
||||
@@ -186,6 +186,7 @@ require (
|
||||
github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f // indirect
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211019132340-3ff525a010d5
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0 // indirect
|
||||
|
||||
@@ -1204,6 +1204,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.7.0 h1:D+Lhxi3P/7vpyDHUK/fdX9bL2mRz8hLG04ucNf1E02o=
|
||||
github.com/grafana/grafana-aws-sdk v0.7.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211019132340-3ff525a010d5 h1:o7w/t0nLNfkERMdj09U0h3Fl63z8ws1CxwiImeUKLIk=
|
||||
github.com/grafana/grafana-google-sdk-go v0.0.0-20211019132340-3ff525a010d5/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.79.0/go.mod h1:NvxLzGkVhnoBKwzkst6CFfpMFKwAdIUZ1q8ssuLeF60=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.114.0 h1:9I55IXw7mOT71tZ/pdqCaWGz8vxfz31CXjaDtBV9ZBo=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "8.2.1",
|
||||
"testing": "8.2.1"
|
||||
"stable": "8.2.2",
|
||||
"testing": "8.2.2"
|
||||
}
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@
|
||||
"@types/logfmt": "^1.2.1",
|
||||
"@types/lru-cache": "^5.1.0",
|
||||
"@types/mousetrap": "1.6.3",
|
||||
"@types/node": "13.7.0",
|
||||
"@types/node": "14.17.27",
|
||||
"@types/papaparse": "5.2.0",
|
||||
"@types/pluralize": "^0.0.29",
|
||||
"@types/prismjs": "1.16.0",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"@types/jquery": "3.3.38",
|
||||
"@types/lodash": "4.14.123",
|
||||
"@types/marked": "1.1.0",
|
||||
"@types/node": "10.14.1",
|
||||
"@types/node": "14.17.27",
|
||||
"@types/papaparse": "5.2.0",
|
||||
"@types/react": "17.0.30",
|
||||
"@types/rollup-plugin-visualizer": "4.2.1",
|
||||
|
||||
@@ -53,6 +53,7 @@ export interface FeatureToggles {
|
||||
prometheusMonaco: boolean;
|
||||
newNavigation: boolean;
|
||||
fullRangeLogsVolume: boolean;
|
||||
autoLoadFullRangeLogsVolume: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "16.0.0",
|
||||
"@rollup/plugin-node-resolve": "10.0.0",
|
||||
"@types/node": "13.7.7",
|
||||
"@types/node": "14.17.27",
|
||||
"@types/rollup-plugin-visualizer": "4.2.1",
|
||||
"@types/systemjs": "^0.20.6",
|
||||
"rollup": "2.33.3",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "16.0.0",
|
||||
"@rollup/plugin-node-resolve": "10.0.0",
|
||||
"@types/node": "13.7.7",
|
||||
"@types/node": "14.17.27",
|
||||
"@types/rollup-plugin-visualizer": "4.2.1",
|
||||
"date-fns": "2.21.3",
|
||||
"rollup": "2.33.3",
|
||||
|
||||
@@ -69,6 +69,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
|
||||
prometheusMonaco: false,
|
||||
newNavigation: false,
|
||||
fullRangeLogsVolume: false,
|
||||
autoLoadFullRangeLogsVolume: false,
|
||||
};
|
||||
licenseInfo: LicenseInfo = {} as LicenseInfo;
|
||||
rendererAvailable = false;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/inquirer": "^6.0.3",
|
||||
"@types/jest": "26.0.15",
|
||||
"@types/node": "^12.0.4",
|
||||
"@types/node": "^14.0.0",
|
||||
"@types/prettier": "^2.4.0",
|
||||
"@types/react-dev-utils": "^9.0.4",
|
||||
"@types/rimraf": "^3.0.0",
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
"@types/jquery": "3.3.38",
|
||||
"@types/lodash": "4.14.123",
|
||||
"@types/mock-raf": "1.0.2",
|
||||
"@types/node": "10.14.1",
|
||||
"@types/node": "14.17.27",
|
||||
"@types/papaparse": "5.2.0",
|
||||
"@types/react": "17.0.30",
|
||||
"@types/react-beautiful-dnd": "13.1.1",
|
||||
|
||||
@@ -33,4 +33,39 @@ describe('ErrorBoundary', () => {
|
||||
expect(context.contexts.react).toHaveProperty('componentStack');
|
||||
expect(context.contexts.react.componentStack).toMatch(/^\s+at ErrorThrower (.*)\s+at ErrorBoundary (.*)\s*$/);
|
||||
});
|
||||
|
||||
it('should recover when when recover props change', async () => {
|
||||
const problem = new Error('things went terribly wrong');
|
||||
let renderCount = 0;
|
||||
|
||||
const { rerender } = render(
|
||||
<ErrorBoundary dependencies={[1, 2]}>
|
||||
{({ error }) => {
|
||||
if (!error) {
|
||||
renderCount += 1;
|
||||
return <ErrorThrower error={problem} />;
|
||||
} else {
|
||||
return <p>{error.message}</p>;
|
||||
}
|
||||
}}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
await screen.findByText(problem.message);
|
||||
|
||||
rerender(
|
||||
<ErrorBoundary dependencies={[1, 3]}>
|
||||
{({ error }) => {
|
||||
if (!error) {
|
||||
renderCount += 1;
|
||||
return <ErrorThrower error={problem} />;
|
||||
} else {
|
||||
return <p>{error.message}</p>;
|
||||
}
|
||||
}}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
expect(renderCount).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,12 @@ export interface ErrorBoundaryApi {
|
||||
|
||||
interface Props {
|
||||
children: (r: ErrorBoundaryApi) => ReactNode;
|
||||
/** Will re-render children after error if recover values changes */
|
||||
dependencies?: any[];
|
||||
/** Callback called on error */
|
||||
onError?: (error: Error) => void;
|
||||
/** Callback error state is cleared due to recover props change */
|
||||
onRecover?: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -29,10 +35,29 @@ export class ErrorBoundary extends PureComponent<Props, State> {
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
captureException(error, { contexts: { react: { componentStack: errorInfo.componentStack } } });
|
||||
this.setState({
|
||||
error: error,
|
||||
errorInfo: errorInfo,
|
||||
});
|
||||
this.setState({ error, errorInfo });
|
||||
|
||||
if (this.props.onError) {
|
||||
this.props.onError(error);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const { dependencies, onRecover } = this.props;
|
||||
|
||||
if (this.state.error) {
|
||||
if (dependencies && prevProps.dependencies) {
|
||||
for (let i = 0; i < dependencies.length; i++) {
|
||||
if (dependencies[i] !== prevProps.dependencies[i]) {
|
||||
this.setState({ error: null, errorInfo: null });
|
||||
if (onRecover) {
|
||||
onRecover();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -60,6 +85,9 @@ export interface ErrorBoundaryAlertProps {
|
||||
|
||||
/** 'page' will render full page error with stacktrace. 'alertbox' will render an <Alert />. Default 'alertbox' */
|
||||
style?: 'page' | 'alertbox';
|
||||
|
||||
/** Will re-render children after error if recover values changes */
|
||||
dependencies?: any[];
|
||||
}
|
||||
|
||||
export class ErrorBoundaryAlert extends PureComponent<ErrorBoundaryAlertProps> {
|
||||
@@ -69,10 +97,10 @@ export class ErrorBoundaryAlert extends PureComponent<ErrorBoundaryAlertProps> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { title, children, style } = this.props;
|
||||
const { title, children, style, dependencies } = this.props;
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<ErrorBoundary dependencies={dependencies}>
|
||||
{({ error, errorInfo }) => {
|
||||
if (!errorInfo) {
|
||||
return children;
|
||||
|
||||
@@ -88,6 +88,7 @@ const CustomControl = (props: any) => {
|
||||
|
||||
export function SelectBase<T>({
|
||||
allowCustomValue = false,
|
||||
allowCreateWhileLoading = false,
|
||||
'aria-label': ariaLabel,
|
||||
autoFocus = false,
|
||||
backspaceRemovesValue = true,
|
||||
@@ -227,6 +228,7 @@ export function SelectBase<T>({
|
||||
|
||||
if (allowCustomValue) {
|
||||
ReactSelectComponent = Creatable as any;
|
||||
creatableProps.allowCreateWhileLoading = allowCreateWhileLoading;
|
||||
creatableProps.formatCreateLabel = formatCreateLabel ?? ((input: string) => `Create: ${input}`);
|
||||
creatableProps.onCreateOption = onCreateOption;
|
||||
creatableProps.isValidNewOption = isValidNewOption;
|
||||
|
||||
@@ -9,6 +9,7 @@ export type InputActionMeta = {
|
||||
export interface SelectCommonProps<T> {
|
||||
/** Aria label applied to the input field */
|
||||
['aria-label']?: string;
|
||||
allowCreateWhileLoading?: boolean;
|
||||
allowCustomValue?: boolean;
|
||||
/** Focus is set to the Select when rendered*/
|
||||
autoFocus?: boolean;
|
||||
|
||||
@@ -92,6 +92,15 @@ export class Sparkline extends PureComponent<SparklineProps, State> {
|
||||
getYRange(field: Field) {
|
||||
let { min, max } = this.state.alignedDataFrame.fields[1].state?.range!;
|
||||
|
||||
if (min === max) {
|
||||
if (min === 0) {
|
||||
max = 100;
|
||||
} else {
|
||||
min = 0;
|
||||
max! *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
Math.max(min!, field.config.min ?? -Infinity),
|
||||
Math.min(max!, field.config.max ?? Infinity),
|
||||
|
||||
+7
-7
@@ -303,11 +303,11 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
}, reqOrgAdmin)
|
||||
|
||||
apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings)
|
||||
apiRoute.Any("/datasources/proxy/:id/*", reqSignedIn, hs.ProxyDataSourceRequest)
|
||||
apiRoute.Any("/datasources/proxy/:id", reqSignedIn, hs.ProxyDataSourceRequest)
|
||||
apiRoute.Any("/datasources/:id/resources", hs.CallDatasourceResource)
|
||||
apiRoute.Any("/datasources/:id/resources/*", hs.CallDatasourceResource)
|
||||
apiRoute.Any("/datasources/:id/health", routing.Wrap(hs.CheckDatasourceHealth))
|
||||
apiRoute.Any("/datasources/proxy/:id/*", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), hs.ProxyDataSourceRequest)
|
||||
apiRoute.Any("/datasources/proxy/:id", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), hs.ProxyDataSourceRequest)
|
||||
apiRoute.Any("/datasources/:id/resources", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), hs.CallDatasourceResource)
|
||||
apiRoute.Any("/datasources/:id/resources/*", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), hs.CallDatasourceResource)
|
||||
apiRoute.Any("/datasources/:id/health", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), routing.Wrap(hs.CheckDatasourceHealth))
|
||||
|
||||
// Folders
|
||||
apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) {
|
||||
@@ -373,10 +373,10 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
apiRoute.Get("/search/", routing.Wrap(Search))
|
||||
|
||||
// metrics
|
||||
apiRoute.Post("/tsdb/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetrics))
|
||||
apiRoute.Post("/tsdb/query", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetrics))
|
||||
|
||||
// DataSource w/ expressions
|
||||
apiRoute.Post("/ds/query", bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetricsV2))
|
||||
apiRoute.Post("/ds/query", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), bind(dtos.MetricRequest{}), routing.Wrap(hs.QueryMetricsV2))
|
||||
|
||||
apiRoute.Group("/alerts", func(alertsRoute routing.RouteRegister) {
|
||||
alertsRoute.Post("/test", bind(dtos.AlertTestCommand{}), routing.Wrap(hs.AlertTest))
|
||||
|
||||
@@ -105,8 +105,7 @@ func getTokenProvider(ctx context.Context, cfg *setting.Cfg, ds DSInfo, pluginRo
|
||||
if jwtTokenAuth == nil {
|
||||
return nil, fmt.Errorf("'jwtTokenAuth' not configured for authentication type '%s'", authType)
|
||||
}
|
||||
provider := newGceAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth)
|
||||
return provider, nil
|
||||
return newGceAccessTokenProvider(ctx, ds, pluginRoute, jwtTokenAuth), nil
|
||||
|
||||
case "jwt":
|
||||
if jwtTokenAuth == nil {
|
||||
|
||||
@@ -2,43 +2,31 @@ package pluginproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
googletokenprovider "github.com/grafana/grafana-google-sdk-go/pkg/tokenprovider"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
type gceAccessTokenProvider struct {
|
||||
datasourceId int64
|
||||
datasourceUpdated time.Time
|
||||
ctx context.Context
|
||||
route *plugins.AppPluginRoute
|
||||
authParams *plugins.JwtTokenAuth
|
||||
source googletokenprovider.TokenProvider
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func newGceAccessTokenProvider(ctx context.Context, ds DSInfo, pluginRoute *plugins.AppPluginRoute,
|
||||
authParams *plugins.JwtTokenAuth) *gceAccessTokenProvider {
|
||||
cfg := googletokenprovider.Config{
|
||||
RoutePath: pluginRoute.Path,
|
||||
RouteMethod: pluginRoute.Method,
|
||||
DataSourceID: ds.ID,
|
||||
DataSourceUpdated: ds.Updated,
|
||||
Scopes: authParams.Scopes,
|
||||
}
|
||||
return &gceAccessTokenProvider{
|
||||
datasourceId: ds.ID,
|
||||
datasourceUpdated: ds.Updated,
|
||||
ctx: ctx,
|
||||
route: pluginRoute,
|
||||
authParams: authParams,
|
||||
source: googletokenprovider.NewGceAccessTokenProvider(cfg),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *gceAccessTokenProvider) GetAccessToken() (string, error) {
|
||||
tokenSrc, err := google.DefaultTokenSource(provider.ctx, provider.authParams.Scopes...)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get default token from meta data server", "error", err)
|
||||
return "", err
|
||||
} else {
|
||||
token, err := tokenSrc.Token()
|
||||
if err != nil {
|
||||
logger.Error("Failed to get default access token from meta data server", "error", err)
|
||||
return "", err
|
||||
} else {
|
||||
return token.AccessToken, nil
|
||||
}
|
||||
}
|
||||
return provider.source.GetAccessToken(provider.ctx)
|
||||
}
|
||||
|
||||
@@ -2,95 +2,46 @@ package pluginproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
googletokenprovider "github.com/grafana/grafana-google-sdk-go/pkg/tokenprovider"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
oauthJwtTokenCache = oauthJwtTokenCacheType{
|
||||
cache: map[string]*oauth2.Token{},
|
||||
}
|
||||
)
|
||||
|
||||
type oauthJwtTokenCacheType struct {
|
||||
cache map[string]*oauth2.Token
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
type jwtAccessTokenProvider struct {
|
||||
datasourceId int64
|
||||
datasourceUpdated time.Time
|
||||
ctx context.Context
|
||||
route *plugins.AppPluginRoute
|
||||
authParams *plugins.JwtTokenAuth
|
||||
source googletokenprovider.TokenProvider
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func newJwtAccessTokenProvider(ctx context.Context, ds DSInfo, pluginRoute *plugins.AppPluginRoute,
|
||||
authParams *plugins.JwtTokenAuth) *jwtAccessTokenProvider {
|
||||
jwtConf := &googletokenprovider.JwtTokenConfig{}
|
||||
if val, ok := authParams.Params["client_email"]; ok {
|
||||
jwtConf.Email = val
|
||||
}
|
||||
|
||||
if val, ok := authParams.Params["private_key"]; ok {
|
||||
jwtConf.PrivateKey = []byte(val)
|
||||
}
|
||||
|
||||
if val, ok := authParams.Params["token_uri"]; ok {
|
||||
jwtConf.URI = val
|
||||
}
|
||||
|
||||
cfg := googletokenprovider.Config{
|
||||
RoutePath: pluginRoute.Path,
|
||||
RouteMethod: pluginRoute.Method,
|
||||
DataSourceID: ds.ID,
|
||||
DataSourceUpdated: ds.Updated,
|
||||
Scopes: authParams.Scopes,
|
||||
JwtTokenConfig: jwtConf,
|
||||
}
|
||||
|
||||
return &jwtAccessTokenProvider{
|
||||
datasourceId: ds.ID,
|
||||
datasourceUpdated: ds.Updated,
|
||||
ctx: ctx,
|
||||
route: pluginRoute,
|
||||
authParams: authParams,
|
||||
source: googletokenprovider.NewJwtAccessTokenProvider(cfg),
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *jwtAccessTokenProvider) GetAccessToken() (string, error) {
|
||||
oauthJwtTokenCache.Lock()
|
||||
defer oauthJwtTokenCache.Unlock()
|
||||
if cachedToken, found := oauthJwtTokenCache.cache[provider.getAccessTokenCacheKey()]; found {
|
||||
if cachedToken.Expiry.After(timeNow().Add(time.Second * 10)) {
|
||||
logger.Debug("Using token from cache")
|
||||
return cachedToken.AccessToken, nil
|
||||
}
|
||||
}
|
||||
|
||||
conf := &jwt.Config{}
|
||||
|
||||
if val, ok := provider.authParams.Params["client_email"]; ok {
|
||||
conf.Email = val
|
||||
}
|
||||
|
||||
if val, ok := provider.authParams.Params["private_key"]; ok {
|
||||
conf.PrivateKey = []byte(val)
|
||||
}
|
||||
|
||||
if val, ok := provider.authParams.Params["token_uri"]; ok {
|
||||
conf.TokenURL = val
|
||||
}
|
||||
|
||||
conf.Scopes = provider.authParams.Scopes
|
||||
|
||||
token, err := getTokenSource(conf, provider.ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
oauthJwtTokenCache.cache[provider.getAccessTokenCacheKey()] = token
|
||||
|
||||
logger.Info("Got new access token", "ExpiresOn", token.Expiry)
|
||||
|
||||
return token.AccessToken, nil
|
||||
}
|
||||
|
||||
// getTokenSource gets a token source.
|
||||
// Stubbable by tests.
|
||||
var getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
tokenSrc := conf.TokenSource(ctx)
|
||||
token, err := tokenSrc.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (provider *jwtAccessTokenProvider) getAccessTokenCacheKey() string {
|
||||
return fmt.Sprintf("%v_%v_%v_%v", provider.datasourceId, provider.datasourceUpdated.Unix(), provider.route.Path, provider.route.Method)
|
||||
return provider.source.GetAccessToken(provider.ctx)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package pluginproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -13,105 +12,12 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
var (
|
||||
token map[string]interface{}
|
||||
)
|
||||
|
||||
func TestAccessToken_pluginWithJWTTokenAuthRoute(t *testing.T) {
|
||||
pluginRoute := &plugins.AppPluginRoute{
|
||||
Path: "pathwithjwttoken1",
|
||||
URL: "https://api.jwt.io/some/path",
|
||||
Method: "GET",
|
||||
JwtTokenAuth: &plugins.JwtTokenAuth{
|
||||
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||
Scopes: []string{
|
||||
"https://www.testapi.com/auth/monitoring.read",
|
||||
"https://www.testapi.com/auth/cloudplatformprojects.readonly",
|
||||
},
|
||||
Params: map[string]string{
|
||||
"token_uri": "{{.JsonData.tokenUri}}",
|
||||
"client_email": "{{.JsonData.clientEmail}}",
|
||||
"private_key": "{{.SecureJsonData.privateKey}}",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
authParams := &plugins.JwtTokenAuth{
|
||||
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
||||
Scopes: []string{
|
||||
"https://www.testapi.com/auth/monitoring.read",
|
||||
"https://www.testapi.com/auth/cloudplatformprojects.readonly",
|
||||
},
|
||||
Params: map[string]string{
|
||||
"token_uri": "login.url.com/token",
|
||||
"client_email": "test@test.com",
|
||||
"private_key": "testkey",
|
||||
},
|
||||
}
|
||||
|
||||
setUp := func(t *testing.T, fn func(*jwt.Config, context.Context) (*oauth2.Token, error)) {
|
||||
origFn := getTokenSource
|
||||
t.Cleanup(func() {
|
||||
getTokenSource = origFn
|
||||
})
|
||||
|
||||
getTokenSource = fn
|
||||
}
|
||||
|
||||
ds := DSInfo{ID: 1, Updated: time.Now()}
|
||||
|
||||
t.Run("should fetch token using JWT private key", func(t *testing.T) {
|
||||
setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{AccessToken: "abc"}, nil
|
||||
})
|
||||
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams)
|
||||
token, err := provider.GetAccessToken()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "abc", token)
|
||||
})
|
||||
|
||||
t.Run("should set JWT config values", func(t *testing.T) {
|
||||
setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
assert.Equal(t, "test@test.com", conf.Email)
|
||||
assert.Equal(t, []byte("testkey"), conf.PrivateKey)
|
||||
assert.Equal(t, 2, len(conf.Scopes))
|
||||
assert.Equal(t, "https://www.testapi.com/auth/monitoring.read", conf.Scopes[0])
|
||||
assert.Equal(t, "https://www.testapi.com/auth/cloudplatformprojects.readonly", conf.Scopes[1])
|
||||
assert.Equal(t, "login.url.com/token", conf.TokenURL)
|
||||
|
||||
return &oauth2.Token{AccessToken: "abc"}, nil
|
||||
})
|
||||
|
||||
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams)
|
||||
_, err := provider.GetAccessToken()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("should use cached token on second call", func(t *testing.T) {
|
||||
setUp(t, func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{
|
||||
AccessToken: "abc",
|
||||
Expiry: time.Now().Add(1 * time.Minute)}, nil
|
||||
})
|
||||
provider := newJwtAccessTokenProvider(context.Background(), ds, pluginRoute, authParams)
|
||||
token1, err := provider.GetAccessToken()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "abc", token1)
|
||||
|
||||
getTokenSource = func(conf *jwt.Config, ctx context.Context) (*oauth2.Token, error) {
|
||||
return &oauth2.Token{AccessToken: "error: cache not used"}, nil
|
||||
}
|
||||
token2, err := provider.GetAccessToken()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "abc", token2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccessToken_pluginWithTokenAuthRoute(t *testing.T) {
|
||||
apiHandler := http.NewServeMux()
|
||||
server := httptest.NewServer(apiHandler)
|
||||
|
||||
+19
-1
@@ -10,6 +10,7 @@ const (
|
||||
ActionProvisioningReload = "provisioning:reload"
|
||||
|
||||
ActionDatasourcesRead = "datasources:read"
|
||||
ActionDatasourcesQuery = "datasources:query"
|
||||
ActionDatasourcesCreate = "datasources:create"
|
||||
ActionDatasourcesWrite = "datasources:write"
|
||||
ActionDatasourcesDelete = "datasources:delete"
|
||||
@@ -63,11 +64,17 @@ func (hs *HTTPServer) declareFixedRoles() error {
|
||||
Action: ActionDatasourcesWrite,
|
||||
Scope: ScopeDatasourcesAll,
|
||||
},
|
||||
{Action: ActionDatasourcesCreate},
|
||||
{
|
||||
Action: ActionDatasourcesCreate,
|
||||
},
|
||||
{
|
||||
Action: ActionDatasourcesDelete,
|
||||
Scope: ScopeDatasourcesAll,
|
||||
},
|
||||
{
|
||||
Action: ActionDatasourcesQuery,
|
||||
Scope: ScopeDatasourcesAll,
|
||||
},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(models.ROLE_ADMIN)},
|
||||
@@ -86,6 +93,17 @@ func (hs *HTTPServer) declareFixedRoles() error {
|
||||
},
|
||||
Grants: []string{string(models.ROLE_VIEWER)},
|
||||
},
|
||||
{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Version: 1,
|
||||
Name: "fixed:datasources:compatibility:querier",
|
||||
Description: "Query data sources when data source permissions are not in use",
|
||||
Permissions: []accesscontrol.Permission{
|
||||
{Action: ActionDatasourcesQuery},
|
||||
},
|
||||
},
|
||||
Grants: []string{string(models.ROLE_VIEWER)},
|
||||
},
|
||||
}
|
||||
|
||||
return hs.AccessControl.DeclareFixedRoles(registrations...)
|
||||
|
||||
@@ -99,7 +99,7 @@ func (kv *kvStoreSQL) Del(ctx context.Context, orgId int64, namespace string, ke
|
||||
func (kv *kvStoreSQL) Keys(ctx context.Context, orgId int64, namespace string, keyPrefix string) ([]Key, error) {
|
||||
var keys []Key
|
||||
err := kv.sqlStore.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error {
|
||||
query := dbSession.Where("namespace = ?", namespace).And("key LIKE ?", keyPrefix+"%")
|
||||
query := dbSession.Where("namespace = ?", namespace).And("\"key\" LIKE ?", keyPrefix+"%")
|
||||
if orgId != AllOrganizations {
|
||||
query.And("org_id = ?", orgId)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReplaceIllegalCharswithUnderscore(t *testing.T) {
|
||||
@@ -80,57 +81,55 @@ func TestWhenAlertManagerShouldNotify(t *testing.T) {
|
||||
|
||||
//nolint:goconst
|
||||
func TestAlertmanagerNotifier(t *testing.T) {
|
||||
Convey("Alertmanager notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "alertmanager",
|
||||
Type: "alertmanager",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "alertmanager",
|
||||
Type: "alertmanager",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("from settings", func() {
|
||||
json := `{ "url": "http://127.0.0.1:9093/", "basicAuthUser": "user", "basicAuthPassword": "password" }`
|
||||
t.Run("from settings", func(t *testing.T) {
|
||||
json := `{ "url": "http://127.0.0.1:9093/", "basicAuthUser": "user", "basicAuthPassword": "password" }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "alertmanager",
|
||||
Type: "alertmanager",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "alertmanager",
|
||||
Type: "alertmanager",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
alertmanagerNotifier := not.(*AlertmanagerNotifier)
|
||||
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
alertmanagerNotifier := not.(*AlertmanagerNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(alertmanagerNotifier.BasicAuthUser, ShouldEqual, "user")
|
||||
So(alertmanagerNotifier.BasicAuthPassword, ShouldEqual, "password")
|
||||
So(alertmanagerNotifier.URL, ShouldResemble, []string{"http://127.0.0.1:9093/"})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, alertmanagerNotifier.BasicAuthUser, "user")
|
||||
require.Equal(t, alertmanagerNotifier.BasicAuthPassword, "password")
|
||||
require.Equal(t, alertmanagerNotifier.URL, []string{"http://127.0.0.1:9093/"})
|
||||
})
|
||||
|
||||
Convey("from settings with multiple alertmanager", func() {
|
||||
json := `{ "url": "http://alertmanager1:9093,http://alertmanager2:9093" }`
|
||||
t.Run("from settings with multiple alertmanager", func(t *testing.T) {
|
||||
json := `{ "url": "http://alertmanager1:9093,http://alertmanager2:9093" }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "alertmanager",
|
||||
Type: "alertmanager",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "alertmanager",
|
||||
Type: "alertmanager",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
alertmanagerNotifier := not.(*AlertmanagerNotifier)
|
||||
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
alertmanagerNotifier := not.(*AlertmanagerNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(alertmanagerNotifier.URL, ShouldResemble, []string{"http://alertmanager1:9093", "http://alertmanager2:9093"})
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, alertmanagerNotifier.URL, []string{"http://alertmanager1:9093", "http://alertmanager2:9093"})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestShouldSendAlertNotification(t *testing.T) {
|
||||
@@ -184,38 +185,36 @@ func TestShouldSendAlertNotification(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBaseNotifier(t *testing.T) {
|
||||
Convey("default constructor for notifiers", t, func() {
|
||||
bJSON := simplejson.New()
|
||||
bJSON := simplejson.New()
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Uid: "1",
|
||||
Name: "name",
|
||||
Type: "email",
|
||||
Settings: bJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Uid: "1",
|
||||
Name: "name",
|
||||
Type: "email",
|
||||
Settings: bJSON,
|
||||
}
|
||||
|
||||
Convey("can parse false value", func() {
|
||||
bJSON.Set("uploadImage", false)
|
||||
t.Run("can parse false value", func(t *testing.T) {
|
||||
bJSON.Set("uploadImage", false)
|
||||
|
||||
base := NewNotifierBase(model)
|
||||
So(base.UploadImage, ShouldBeFalse)
|
||||
})
|
||||
base := NewNotifierBase(model)
|
||||
require.False(t, base.UploadImage)
|
||||
})
|
||||
|
||||
Convey("can parse true value", func() {
|
||||
bJSON.Set("uploadImage", true)
|
||||
t.Run("can parse true value", func(t *testing.T) {
|
||||
bJSON.Set("uploadImage", true)
|
||||
|
||||
base := NewNotifierBase(model)
|
||||
So(base.UploadImage, ShouldBeTrue)
|
||||
})
|
||||
base := NewNotifierBase(model)
|
||||
require.True(t, base.UploadImage)
|
||||
})
|
||||
|
||||
Convey("default value should be true for backwards compatibility", func() {
|
||||
base := NewNotifierBase(model)
|
||||
So(base.UploadImage, ShouldBeTrue)
|
||||
})
|
||||
t.Run("default value should be true for backwards compatibility", func(t *testing.T) {
|
||||
base := NewNotifierBase(model)
|
||||
require.True(t, base.UploadImage)
|
||||
})
|
||||
|
||||
Convey("default value should be false for backwards compatibility", func() {
|
||||
base := NewNotifierBase(model)
|
||||
So(base.DisableResolveMessage, ShouldBeFalse)
|
||||
})
|
||||
t.Run("default value should be false for backwards compatibility", func(t *testing.T) {
|
||||
base := NewNotifierBase(model)
|
||||
require.False(t, base.DisableResolveMessage)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,51 +9,50 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDingDingNotifier(t *testing.T) {
|
||||
Convey("Dingding notifier tests", t, func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "dingding_testing",
|
||||
Type: "dingding",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "dingding_testing",
|
||||
Type: "dingding",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("settings should trigger incident", func() {
|
||||
json := `{ "url": "https://www.google.com" }`
|
||||
_, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("settings should trigger incident", func(t *testing.T) {
|
||||
json := `{ "url": "https://www.google.com" }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "dingding_testing",
|
||||
Type: "dingding",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "dingding_testing",
|
||||
Type: "dingding",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
notifier := not.(*DingDingNotifier)
|
||||
not, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
notifier := not.(*DingDingNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(notifier.Name, ShouldEqual, "dingding_testing")
|
||||
So(notifier.Type, ShouldEqual, "dingding")
|
||||
So(notifier.URL, ShouldEqual, "https://www.google.com")
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "dingding_testing", notifier.Name)
|
||||
require.Equal(t, "dingding", notifier.Type)
|
||||
require.Equal(t, "https://www.google.com", notifier.URL)
|
||||
|
||||
Convey("genBody should not panic", func() {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateAlerting,
|
||||
Message: `{host="localhost"}`,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
_, err = notifier.genBody(evalContext, "")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
t.Run("genBody should not panic", func(t *testing.T) {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateAlerting,
|
||||
Message: `{host="localhost"}`,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
_, err = notifier.genBody(evalContext, "")
|
||||
require.Nil(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,51 +6,50 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDiscordNotifier(t *testing.T) {
|
||||
Convey("Discord notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "discord_testing",
|
||||
Type: "discord",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "discord_testing",
|
||||
Type: "discord",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("settings should trigger incident", func() {
|
||||
json := `
|
||||
t.Run("settings should trigger incident", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"avatar_url": "https://grafana.com/img/fav32.png",
|
||||
"content": "@everyone Please check this notification",
|
||||
"url": "https://web.hook/"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "discord_testing",
|
||||
Type: "discord",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "discord_testing",
|
||||
Type: "discord",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
discordNotifier := not.(*DiscordNotifier)
|
||||
not, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
discordNotifier := not.(*DiscordNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(discordNotifier.Name, ShouldEqual, "discord_testing")
|
||||
So(discordNotifier.Type, ShouldEqual, "discord")
|
||||
So(discordNotifier.AvatarURL, ShouldEqual, "https://grafana.com/img/fav32.png")
|
||||
So(discordNotifier.Content, ShouldEqual, "@everyone Please check this notification")
|
||||
So(discordNotifier.WebhookURL, ShouldEqual, "https://web.hook/")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "discord_testing", discordNotifier.Name)
|
||||
require.Equal(t, "discord", discordNotifier.Type)
|
||||
require.Equal(t, "https://grafana.com/img/fav32.png", discordNotifier.AvatarURL)
|
||||
require.Equal(t, "@everyone Please check this notification", discordNotifier.Content)
|
||||
require.Equal(t, "https://web.hook/", discordNotifier.WebhookURL)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,74 +6,73 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEmailNotifier(t *testing.T) {
|
||||
Convey("Email notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "email",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "email",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("from settings", func() {
|
||||
json := `
|
||||
t.Run("from settings", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"addresses": "ops@grafana.org"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "email",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "email",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
emailNotifier := not.(*EmailNotifier)
|
||||
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
emailNotifier := not.(*EmailNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(emailNotifier.Name, ShouldEqual, "ops")
|
||||
So(emailNotifier.Type, ShouldEqual, "email")
|
||||
So(emailNotifier.Addresses[0], ShouldEqual, "ops@grafana.org")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "ops", emailNotifier.Name)
|
||||
require.Equal(t, "email", emailNotifier.Type)
|
||||
require.Equal(t, "ops@grafana.org", emailNotifier.Addresses[0])
|
||||
})
|
||||
|
||||
Convey("from settings with two emails", func() {
|
||||
json := `
|
||||
t.Run("from settings with two emails", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"addresses": "ops@grafana.org;dev@grafana.org"
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "email",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "email",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
emailNotifier := not.(*EmailNotifier)
|
||||
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
emailNotifier := not.(*EmailNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(emailNotifier.Name, ShouldEqual, "ops")
|
||||
So(emailNotifier.Type, ShouldEqual, "email")
|
||||
So(len(emailNotifier.Addresses), ShouldEqual, 2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "ops", emailNotifier.Name)
|
||||
require.Equal(t, "email", emailNotifier.Type)
|
||||
require.Equal(t, 2, len(emailNotifier.Addresses))
|
||||
|
||||
So(emailNotifier.Addresses[0], ShouldEqual, "ops@grafana.org")
|
||||
So(emailNotifier.Addresses[1], ShouldEqual, "dev@grafana.org")
|
||||
})
|
||||
require.Equal(t, "ops@grafana.org", emailNotifier.Addresses[0])
|
||||
require.Equal(t, "dev@grafana.org", emailNotifier.Addresses[1])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,47 +6,46 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGoogleChatNotifier(t *testing.T) {
|
||||
Convey("Google Hangouts Chat notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "googlechat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "googlechat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("from settings", func() {
|
||||
json := `
|
||||
t.Run("from settings", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"url": "http://google.com"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "googlechat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "googlechat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
webhookNotifier := not.(*GoogleChatNotifier)
|
||||
not, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
webhookNotifier := not.(*GoogleChatNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(webhookNotifier.Name, ShouldEqual, "ops")
|
||||
So(webhookNotifier.Type, ShouldEqual, "googlechat")
|
||||
So(webhookNotifier.URL, ShouldEqual, "http://google.com")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "ops", webhookNotifier.Name)
|
||||
require.Equal(t, "googlechat", webhookNotifier.Type)
|
||||
require.Equal(t, "http://google.com", webhookNotifier.URL)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,75 +6,74 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
//nolint:goconst
|
||||
func TestHipChatNotifier(t *testing.T) {
|
||||
Convey("HipChat notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "hipchat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "hipchat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("from settings", func() {
|
||||
json := `
|
||||
t.Run("from settings", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"url": "http://google.com"
|
||||
}`
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "hipchat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "hipchat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
hipchatNotifier := not.(*HipChatNotifier)
|
||||
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
hipchatNotifier := not.(*HipChatNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(hipchatNotifier.Name, ShouldEqual, "ops")
|
||||
So(hipchatNotifier.Type, ShouldEqual, "hipchat")
|
||||
So(hipchatNotifier.URL, ShouldEqual, "http://google.com")
|
||||
So(hipchatNotifier.APIKey, ShouldEqual, "")
|
||||
So(hipchatNotifier.RoomID, ShouldEqual, "")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "ops", hipchatNotifier.Name)
|
||||
require.Equal(t, "hipchat", hipchatNotifier.Type)
|
||||
require.Equal(t, "http://google.com", hipchatNotifier.URL)
|
||||
require.Equal(t, "", hipchatNotifier.APIKey)
|
||||
require.Equal(t, "", hipchatNotifier.RoomID)
|
||||
})
|
||||
|
||||
Convey("from settings with Recipient and Mention", func() {
|
||||
json := `
|
||||
t.Run("from settings with Recipient and Mention", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"url": "http://www.hipchat.com",
|
||||
"apikey": "1234",
|
||||
"roomid": "1234"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "hipchat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "hipchat",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
hipchatNotifier := not.(*HipChatNotifier)
|
||||
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
hipchatNotifier := not.(*HipChatNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(hipchatNotifier.Name, ShouldEqual, "ops")
|
||||
So(hipchatNotifier.Type, ShouldEqual, "hipchat")
|
||||
So(hipchatNotifier.URL, ShouldEqual, "http://www.hipchat.com")
|
||||
So(hipchatNotifier.APIKey, ShouldEqual, "1234")
|
||||
So(hipchatNotifier.RoomID, ShouldEqual, "1234")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "ops", hipchatNotifier.Name)
|
||||
require.Equal(t, "hipchat", hipchatNotifier.Type)
|
||||
require.Equal(t, "http://www.hipchat.com", hipchatNotifier.URL)
|
||||
require.Equal(t, "1234", hipchatNotifier.APIKey)
|
||||
require.Equal(t, "1234", hipchatNotifier.RoomID)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,49 +6,48 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestKafkaNotifier(t *testing.T) {
|
||||
Convey("Kafka notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "kafka_testing",
|
||||
Type: "kafka",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "kafka_testing",
|
||||
Type: "kafka",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("settings should send an event to kafka", func() {
|
||||
json := `
|
||||
t.Run("settings should send an event to kafka", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"kafkaRestProxy": "http://localhost:8082",
|
||||
"kafkaTopic": "topic1"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "kafka_testing",
|
||||
Type: "kafka",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "kafka_testing",
|
||||
Type: "kafka",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
kafkaNotifier := not.(*KafkaNotifier)
|
||||
not, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
kafkaNotifier := not.(*KafkaNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(kafkaNotifier.Name, ShouldEqual, "kafka_testing")
|
||||
So(kafkaNotifier.Type, ShouldEqual, "kafka")
|
||||
So(kafkaNotifier.Endpoint, ShouldEqual, "http://localhost:8082")
|
||||
So(kafkaNotifier.Topic, ShouldEqual, "topic1")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "kafka_testing", kafkaNotifier.Name)
|
||||
require.Equal(t, "kafka", kafkaNotifier.Type)
|
||||
require.Equal(t, "http://localhost:8082", kafkaNotifier.Endpoint)
|
||||
require.Equal(t, "topic1", kafkaNotifier.Topic)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,43 +6,42 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestLineNotifier(t *testing.T) {
|
||||
Convey("Line notifier tests", t, func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "line_testing",
|
||||
Type: "line",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "line_testing",
|
||||
Type: "line",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("settings should trigger incident", func() {
|
||||
json := `
|
||||
_, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("settings should trigger incident", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"token": "abcdefgh0123456789"
|
||||
}`
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "line_testing",
|
||||
Type: "line",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "line_testing",
|
||||
Type: "line",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
lineNotifier := not.(*LineNotifier)
|
||||
not, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
lineNotifier := not.(*LineNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(lineNotifier.Name, ShouldEqual, "line_testing")
|
||||
So(lineNotifier.Type, ShouldEqual, "line")
|
||||
So(lineNotifier.Token, ShouldEqual, "abcdefgh0123456789")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "line_testing", lineNotifier.Name)
|
||||
require.Equal(t, "line", lineNotifier.Type)
|
||||
require.Equal(t, "abcdefgh0123456789", lineNotifier.Token)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package notifiers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
@@ -10,217 +12,216 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOpsGenieNotifier(t *testing.T) {
|
||||
Convey("OpsGenie notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("settings should trigger incident", func() {
|
||||
json := `
|
||||
t.Run("settings should trigger incident", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"apiKey": "abcdefgh0123456789"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
opsgenieNotifier := not.(*OpsGenieNotifier)
|
||||
not, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
opsgenieNotifier := not.(*OpsGenieNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(opsgenieNotifier.Name, ShouldEqual, "opsgenie_testing")
|
||||
So(opsgenieNotifier.Type, ShouldEqual, "opsgenie")
|
||||
So(opsgenieNotifier.APIKey, ShouldEqual, "abcdefgh0123456789")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "opsgenie_testing", opsgenieNotifier.Name)
|
||||
require.Equal(t, "opsgenie", opsgenieNotifier.Type)
|
||||
require.Equal(t, "abcdefgh0123456789", opsgenieNotifier.APIKey)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Handling notification tags", func() {
|
||||
Convey("invalid sendTagsAs value should return error", func() {
|
||||
json := `{
|
||||
t.Run("Handling notification tags", func(t *testing.T) {
|
||||
t.Run("invalid sendTagsAs value should return error", func(t *testing.T) {
|
||||
json := `{
|
||||
"apiKey": "abcdefgh0123456789",
|
||||
"sendTagsAs": "not_a_valid_value"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldHaveSameTypeAs, alerting.ValidationError{})
|
||||
So(err.Error(), ShouldEndWith, "Invalid value for sendTagsAs: \"not_a_valid_value\"")
|
||||
})
|
||||
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, reflect.TypeOf(err), reflect.TypeOf(alerting.ValidationError{}))
|
||||
require.True(t, strings.HasSuffix(err.Error(), "Invalid value for sendTagsAs: \"not_a_valid_value\""))
|
||||
})
|
||||
|
||||
Convey("alert payload should include tag pairs only as an array in the tags key when sendAsTags is not set", func() {
|
||||
json := `{
|
||||
t.Run("alert payload should include tag pairs only as an array in the tags key when sendAsTags is not set", func(t *testing.T) {
|
||||
json := `{
|
||||
"apiKey": "abcdefgh0123456789"
|
||||
}`
|
||||
|
||||
tagPairs := []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "aKey", Value: "aValue"},
|
||||
tagPairs := []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "aKey", Value: "aValue"},
|
||||
}
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
|
||||
|
||||
opsgenieNotifier := notifier.(*OpsGenieNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
details := make(map[string]interface{})
|
||||
bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error {
|
||||
bodyJSON, err := simplejson.NewJson([]byte(cmd.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
|
||||
|
||||
opsgenieNotifier := notifier.(*OpsGenieNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
details := make(map[string]interface{})
|
||||
bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error {
|
||||
bodyJSON, err := simplejson.NewJson([]byte(cmd.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
alertErr := opsgenieNotifier.createAlert(evalContext)
|
||||
|
||||
So(notifierErr, ShouldBeNil)
|
||||
So(alertErr, ShouldBeNil)
|
||||
So(tags, ShouldResemble, []string{"keyOnly", "aKey:aValue"})
|
||||
So(details, ShouldResemble, map[string]interface{}{"url": ""})
|
||||
return err
|
||||
})
|
||||
|
||||
Convey("alert payload should include tag pairs only as a map in the details key when sendAsTags=details", func() {
|
||||
json := `{
|
||||
alertErr := opsgenieNotifier.createAlert(evalContext)
|
||||
|
||||
require.Nil(t, notifierErr)
|
||||
require.Nil(t, alertErr)
|
||||
require.Equal(t, tags, []string{"keyOnly", "aKey:aValue"})
|
||||
require.Equal(t, details, map[string]interface{}{"url": ""})
|
||||
})
|
||||
|
||||
t.Run("alert payload should include tag pairs only as a map in the details key when sendAsTags=details", func(t *testing.T) {
|
||||
json := `{
|
||||
"apiKey": "abcdefgh0123456789",
|
||||
"sendTagsAs": "details"
|
||||
}`
|
||||
|
||||
tagPairs := []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "aKey", Value: "aValue"},
|
||||
tagPairs := []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "aKey", Value: "aValue"},
|
||||
}
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
|
||||
|
||||
opsgenieNotifier := notifier.(*OpsGenieNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
details := make(map[string]interface{})
|
||||
bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error {
|
||||
bodyJSON, err := simplejson.NewJson([]byte(cmd.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
|
||||
|
||||
opsgenieNotifier := notifier.(*OpsGenieNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
details := make(map[string]interface{})
|
||||
bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error {
|
||||
bodyJSON, err := simplejson.NewJson([]byte(cmd.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
alertErr := opsgenieNotifier.createAlert(evalContext)
|
||||
|
||||
So(notifierErr, ShouldBeNil)
|
||||
So(alertErr, ShouldBeNil)
|
||||
So(tags, ShouldResemble, []string{})
|
||||
So(details, ShouldResemble, map[string]interface{}{"keyOnly": "", "aKey": "aValue", "url": ""})
|
||||
return err
|
||||
})
|
||||
|
||||
Convey("alert payload should include tag pairs as both a map in the details key and an array in the tags key when sendAsTags=both", func() {
|
||||
json := `{
|
||||
alertErr := opsgenieNotifier.createAlert(evalContext)
|
||||
|
||||
require.Nil(t, notifierErr)
|
||||
require.Nil(t, alertErr)
|
||||
require.Equal(t, tags, []string{})
|
||||
require.Equal(t, details, map[string]interface{}{"keyOnly": "", "aKey": "aValue", "url": ""})
|
||||
})
|
||||
|
||||
t.Run("alert payload should include tag pairs as both a map in the details key and an array in the tags key when sendAsTags=both", func(t *testing.T) {
|
||||
json := `{
|
||||
"apiKey": "abcdefgh0123456789",
|
||||
"sendTagsAs": "both"
|
||||
}`
|
||||
|
||||
tagPairs := []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "aKey", Value: "aValue"},
|
||||
tagPairs := []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "aKey", Value: "aValue"},
|
||||
}
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
|
||||
|
||||
opsgenieNotifier := notifier.(*OpsGenieNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
details := make(map[string]interface{})
|
||||
bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error {
|
||||
bodyJSON, err := simplejson.NewJson([]byte(cmd.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
|
||||
|
||||
opsgenieNotifier := notifier.(*OpsGenieNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
details := make(map[string]interface{})
|
||||
bus.AddHandlerCtx("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error {
|
||||
bodyJSON, err := simplejson.NewJson([]byte(cmd.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
alertErr := opsgenieNotifier.createAlert(evalContext)
|
||||
|
||||
So(notifierErr, ShouldBeNil)
|
||||
So(alertErr, ShouldBeNil)
|
||||
So(tags, ShouldResemble, []string{"keyOnly", "aKey:aValue"})
|
||||
So(details, ShouldResemble, map[string]interface{}{"keyOnly": "", "aKey": "aValue", "url": ""})
|
||||
return err
|
||||
})
|
||||
|
||||
alertErr := opsgenieNotifier.createAlert(evalContext)
|
||||
|
||||
require.Nil(t, notifierErr)
|
||||
require.Nil(t, alertErr)
|
||||
require.Equal(t, tags, []string{"keyOnly", "aKey:aValue"})
|
||||
require.Equal(t, details, map[string]interface{}{"keyOnly": "", "aKey": "aValue", "url": ""})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func presenceComparer(a, b string) bool {
|
||||
@@ -26,516 +27,512 @@ func presenceComparer(a, b string) bool {
|
||||
}
|
||||
|
||||
func TestPagerdutyNotifier(t *testing.T) {
|
||||
Convey("Pagerduty notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pageduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pageduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err = NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err = NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("severity should override default", func() {
|
||||
json := `{ "integrationKey": "abcdefgh0123456789", "severity": "info", "tags": ["foo"]}`
|
||||
t.Run("severity should override default", func(t *testing.T) {
|
||||
json := `{ "integrationKey": "abcdefgh0123456789", "severity": "info", "tags": ["foo"]}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(pagerdutyNotifier.Name, ShouldEqual, "pagerduty_testing")
|
||||
So(pagerdutyNotifier.Type, ShouldEqual, "pagerduty")
|
||||
So(pagerdutyNotifier.Key, ShouldEqual, "abcdefgh0123456789")
|
||||
So(pagerdutyNotifier.Severity, ShouldEqual, "info")
|
||||
So(pagerdutyNotifier.AutoResolve, ShouldBeFalse)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "pagerduty_testing", pagerdutyNotifier.Name)
|
||||
require.Equal(t, "pagerduty", pagerdutyNotifier.Type)
|
||||
require.Equal(t, "abcdefgh0123456789", pagerdutyNotifier.Key)
|
||||
require.Equal(t, "info", pagerdutyNotifier.Severity)
|
||||
require.False(t, pagerdutyNotifier.AutoResolve)
|
||||
})
|
||||
|
||||
Convey("auto resolve and severity should have expected defaults", func() {
|
||||
json := `{ "integrationKey": "abcdefgh0123456789" }`
|
||||
t.Run("auto resolve and severity should have expected defaults", func(t *testing.T) {
|
||||
json := `{ "integrationKey": "abcdefgh0123456789" }`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(pagerdutyNotifier.Name, ShouldEqual, "pagerduty_testing")
|
||||
So(pagerdutyNotifier.Type, ShouldEqual, "pagerduty")
|
||||
So(pagerdutyNotifier.Key, ShouldEqual, "abcdefgh0123456789")
|
||||
So(pagerdutyNotifier.Severity, ShouldEqual, "critical")
|
||||
So(pagerdutyNotifier.AutoResolve, ShouldBeFalse)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "pagerduty_testing", pagerdutyNotifier.Name)
|
||||
require.Equal(t, "pagerduty", pagerdutyNotifier.Type)
|
||||
require.Equal(t, "abcdefgh0123456789", pagerdutyNotifier.Key)
|
||||
require.Equal(t, "critical", pagerdutyNotifier.Severity)
|
||||
require.False(t, pagerdutyNotifier.AutoResolve)
|
||||
})
|
||||
|
||||
Convey("settings should trigger incident", func() {
|
||||
json := `
|
||||
t.Run("settings should trigger incident", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"integrationKey": "abcdefgh0123456789",
|
||||
"autoResolve": false
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(pagerdutyNotifier.Name, ShouldEqual, "pagerduty_testing")
|
||||
So(pagerdutyNotifier.Type, ShouldEqual, "pagerduty")
|
||||
So(pagerdutyNotifier.Key, ShouldEqual, "abcdefgh0123456789")
|
||||
So(pagerdutyNotifier.AutoResolve, ShouldBeFalse)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "pagerduty_testing", pagerdutyNotifier.Name)
|
||||
require.Equal(t, "pagerduty", pagerdutyNotifier.Type)
|
||||
require.Equal(t, "abcdefgh0123456789", pagerdutyNotifier.Key)
|
||||
require.False(t, pagerdutyNotifier.AutoResolve)
|
||||
})
|
||||
|
||||
Convey("should return properly formatted default v2 event payload", func() {
|
||||
json := `{
|
||||
t.Run("should return properly formatted default v2 event payload", func(t *testing.T) {
|
||||
json := `{
|
||||
"integrationKey": "abcdefgh0123456789",
|
||||
"autoResolve": false
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldBeNil)
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Nil(t, err)
|
||||
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
So(err, ShouldBeNil)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
So(err, ShouldBeNil)
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
require.Nil(t, err)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
require.Nil(t, err)
|
||||
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"component": "Grafana",
|
||||
"source": "<<PRESENCE>>",
|
||||
"custom_details": map[string]interface{}{
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "critical",
|
||||
"summary": "someRule - someMessage",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
So(diff, ShouldBeEmpty)
|
||||
})
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"component": "Grafana",
|
||||
"source": "<<PRESENCE>>",
|
||||
"custom_details": map[string]interface{}{
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "critical",
|
||||
"summary": "someRule - someMessage",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
require.Empty(t, diff)
|
||||
})
|
||||
|
||||
Convey("should return properly formatted default v2 event payload with empty message", func() {
|
||||
json := `{
|
||||
t.Run("should return properly formatted default v2 event payload with empty message", func(t *testing.T) {
|
||||
json := `{
|
||||
"integrationKey": "abcdefgh0123456789",
|
||||
"autoResolve": false
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldBeNil)
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Nil(t, err)
|
||||
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
So(err, ShouldBeNil)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
So(err, ShouldBeNil)
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
require.Nil(t, err)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
require.Nil(t, err)
|
||||
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"component": "Grafana",
|
||||
"source": "<<PRESENCE>>",
|
||||
"custom_details": map[string]interface{}{
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "critical",
|
||||
"summary": "someRule",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
So(diff, ShouldBeEmpty)
|
||||
})
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"component": "Grafana",
|
||||
"source": "<<PRESENCE>>",
|
||||
"custom_details": map[string]interface{}{
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "critical",
|
||||
"summary": "someRule",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
require.Empty(t, diff)
|
||||
})
|
||||
|
||||
Convey("should return properly formatted payload with message moved to details", func() {
|
||||
json := `{
|
||||
t.Run("should return properly formatted payload with message moved to details", func(t *testing.T) {
|
||||
json := `{
|
||||
"integrationKey": "abcdefgh0123456789",
|
||||
"autoResolve": false,
|
||||
"messageInDetails": true
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldBeNil)
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Nil(t, err)
|
||||
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
evalContext.EvalMatches = []*alerting.EvalMatch{
|
||||
{
|
||||
// nil is a terrible value to test with, but the cmp.Diff doesn't
|
||||
// like comparing actual floats. So this is roughly the equivalent
|
||||
// of <<PRESENCE>>
|
||||
Value: null.FloatFromPtr(nil),
|
||||
Metric: "someMetric",
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
evalContext.EvalMatches = []*alerting.EvalMatch{
|
||||
{
|
||||
// nil is a terrible value to test with, but the cmp.Diff doesn't
|
||||
// like comparing actual floats. So this is roughly the equivalent
|
||||
// of <<PRESENCE>>
|
||||
Value: null.FloatFromPtr(nil),
|
||||
Metric: "someMetric",
|
||||
},
|
||||
}
|
||||
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
require.NoError(t, err)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"component": "Grafana",
|
||||
"source": "<<PRESENCE>>",
|
||||
"custom_details": map[string]interface{}{
|
||||
"message": "someMessage",
|
||||
"queries": map[string]interface{}{
|
||||
"someMetric": nil,
|
||||
},
|
||||
}
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "critical",
|
||||
"summary": "someRule",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
require.Empty(t, diff)
|
||||
})
|
||||
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
So(err, ShouldBeNil)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"component": "Grafana",
|
||||
"source": "<<PRESENCE>>",
|
||||
"custom_details": map[string]interface{}{
|
||||
"message": "someMessage",
|
||||
"queries": map[string]interface{}{
|
||||
"someMetric": nil,
|
||||
},
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "critical",
|
||||
"summary": "someRule",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
So(diff, ShouldBeEmpty)
|
||||
})
|
||||
|
||||
Convey("should return properly formatted v2 event payload when using override tags", func() {
|
||||
json := `{
|
||||
t.Run("should return properly formatted v2 event payload when using override tags", func(t *testing.T) {
|
||||
json := `{
|
||||
"integrationKey": "abcdefgh0123456789",
|
||||
"autoResolve": false
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.NoError(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldBeNil)
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "group", Value: "aGroup"},
|
||||
{Key: "class", Value: "aClass"},
|
||||
{Key: "component", Value: "aComponent"},
|
||||
{Key: "severity", Value: "warning"},
|
||||
{Key: "dedup_key", Value: "key-" + strings.Repeat("x", 260)},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
|
||||
evalContext.IsTestRun = true
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "group", Value: "aGroup"},
|
||||
{Key: "class", Value: "aClass"},
|
||||
{Key: "component", Value: "aComponent"},
|
||||
{Key: "severity", Value: "warning"},
|
||||
{Key: "dedup_key", Value: "key-" + strings.Repeat("x", 260)},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
So(err, ShouldBeNil)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
So(err, ShouldBeNil)
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
require.NoError(t, err)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "key-" + strings.Repeat("x", 250),
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"source": "<<PRESENCE>>",
|
||||
"component": "aComponent",
|
||||
"custom_details": map[string]interface{}{
|
||||
"group": "aGroup",
|
||||
"class": "aClass",
|
||||
"component": "aComponent",
|
||||
"severity": "warning",
|
||||
"dedup_key": "key-" + strings.Repeat("x", 250),
|
||||
"keyOnly": "",
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "warning",
|
||||
"summary": "someRule - someMessage",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
"class": "aClass",
|
||||
"group": "aGroup",
|
||||
},
|
||||
"images": []interface{}{
|
||||
map[string]interface{}{
|
||||
"src": "http://somewhere.com/omg_dont_panic.png",
|
||||
},
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
So(diff, ShouldBeEmpty)
|
||||
})
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "key-" + strings.Repeat("x", 250),
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"source": "<<PRESENCE>>",
|
||||
"component": "aComponent",
|
||||
"custom_details": map[string]interface{}{
|
||||
"group": "aGroup",
|
||||
"class": "aClass",
|
||||
"component": "aComponent",
|
||||
"severity": "warning",
|
||||
"dedup_key": "key-" + strings.Repeat("x", 250),
|
||||
"keyOnly": "",
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "warning",
|
||||
"summary": "someRule - someMessage",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
"class": "aClass",
|
||||
"group": "aGroup",
|
||||
},
|
||||
"images": []interface{}{
|
||||
map[string]interface{}{
|
||||
"src": "http://somewhere.com/omg_dont_panic.png",
|
||||
},
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
require.Empty(t, diff)
|
||||
})
|
||||
|
||||
Convey("should support multiple levels of severity", func() {
|
||||
json := `{
|
||||
t.Run("should support multiple levels of severity", func(t *testing.T) {
|
||||
json := `{
|
||||
"integrationKey": "abcdefgh0123456789",
|
||||
"autoResolve": false
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.NoError(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldBeNil)
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "group", Value: "aGroup"},
|
||||
{Key: "class", Value: "aClass"},
|
||||
{Key: "component", Value: "aComponent"},
|
||||
{Key: "severity", Value: "info"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
|
||||
evalContext.IsTestRun = true
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "group", Value: "aGroup"},
|
||||
{Key: "class", Value: "aClass"},
|
||||
{Key: "component", Value: "aComponent"},
|
||||
{Key: "severity", Value: "info"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
So(err, ShouldBeNil)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
So(err, ShouldBeNil)
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
require.NoError(t, err)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"source": "<<PRESENCE>>",
|
||||
"component": "aComponent",
|
||||
"custom_details": map[string]interface{}{
|
||||
"group": "aGroup",
|
||||
"class": "aClass",
|
||||
"component": "aComponent",
|
||||
"severity": "info",
|
||||
"keyOnly": "",
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "info",
|
||||
"summary": "someRule - someMessage",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
"class": "aClass",
|
||||
"group": "aGroup",
|
||||
},
|
||||
"images": []interface{}{
|
||||
map[string]interface{}{
|
||||
"src": "http://somewhere.com/omg_dont_panic.png",
|
||||
},
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
So(diff, ShouldBeEmpty)
|
||||
})
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"source": "<<PRESENCE>>",
|
||||
"component": "aComponent",
|
||||
"custom_details": map[string]interface{}{
|
||||
"group": "aGroup",
|
||||
"class": "aClass",
|
||||
"component": "aComponent",
|
||||
"severity": "info",
|
||||
"keyOnly": "",
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "info",
|
||||
"summary": "someRule - someMessage",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
"class": "aClass",
|
||||
"group": "aGroup",
|
||||
},
|
||||
"images": []interface{}{
|
||||
map[string]interface{}{
|
||||
"src": "http://somewhere.com/omg_dont_panic.png",
|
||||
},
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
require.Empty(t, diff)
|
||||
})
|
||||
|
||||
Convey("should ignore invalid severity for PD but keep the tag", func() {
|
||||
json := `{
|
||||
t.Run("should ignore invalid severity for PD but keep the tag", func(t *testing.T) {
|
||||
json := `{
|
||||
"integrationKey": "abcdefgh0123456789",
|
||||
"autoResolve": false,
|
||||
"severity": "critical"
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.NoError(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "pagerduty_testing",
|
||||
Type: "pagerduty",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldBeNil)
|
||||
not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
pagerdutyNotifier := not.(*PagerdutyNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "group", Value: "aGroup"},
|
||||
{Key: "class", Value: "aClass"},
|
||||
{Key: "component", Value: "aComponent"},
|
||||
{Key: "severity", Value: "llama"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
|
||||
evalContext.IsTestRun = true
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "group", Value: "aGroup"},
|
||||
{Key: "class", Value: "aClass"},
|
||||
{Key: "component", Value: "aComponent"},
|
||||
{Key: "severity", Value: "llama"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
So(err, ShouldBeNil)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
So(err, ShouldBeNil)
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
require.NoError(t, err)
|
||||
payload, err := simplejson.NewJson(payloadJSON)
|
||||
require.NoError(t, err)
|
||||
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"source": "<<PRESENCE>>",
|
||||
"component": "aComponent",
|
||||
"custom_details": map[string]interface{}{
|
||||
"group": "aGroup",
|
||||
"class": "aClass",
|
||||
"component": "aComponent",
|
||||
"severity": "llama",
|
||||
"keyOnly": "",
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "critical",
|
||||
"summary": "someRule - someMessage",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
"class": "aClass",
|
||||
"group": "aGroup",
|
||||
},
|
||||
"images": []interface{}{
|
||||
map[string]interface{}{
|
||||
"src": "http://somewhere.com/omg_dont_panic.png",
|
||||
},
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
So(diff, ShouldBeEmpty)
|
||||
})
|
||||
})
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"client": "Grafana",
|
||||
"client_url": "",
|
||||
"dedup_key": "alertId-0",
|
||||
"event_action": "trigger",
|
||||
"links": []interface{}{
|
||||
map[string]interface{}{
|
||||
"href": "",
|
||||
},
|
||||
},
|
||||
"payload": map[string]interface{}{
|
||||
"source": "<<PRESENCE>>",
|
||||
"component": "aComponent",
|
||||
"custom_details": map[string]interface{}{
|
||||
"group": "aGroup",
|
||||
"class": "aClass",
|
||||
"component": "aComponent",
|
||||
"severity": "llama",
|
||||
"keyOnly": "",
|
||||
"state": "alerting",
|
||||
},
|
||||
"severity": "critical",
|
||||
"summary": "someRule - someMessage",
|
||||
"timestamp": "<<PRESENCE>>",
|
||||
"class": "aClass",
|
||||
"group": "aGroup",
|
||||
},
|
||||
"images": []interface{}{
|
||||
map[string]interface{}{
|
||||
"src": "http://somewhere.com/omg_dont_panic.png",
|
||||
},
|
||||
},
|
||||
"routing_key": "abcdefgh0123456789",
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparer))
|
||||
require.Empty(t, diff)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,28 +10,28 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPushoverNotifier(t *testing.T) {
|
||||
Convey("Pushover notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "Pushover",
|
||||
Type: "pushover",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "Pushover",
|
||||
Type: "pushover",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("from settings", func() {
|
||||
json := `
|
||||
t.Run("from settings", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"apiToken": "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve",
|
||||
"userKey": "tzNZYf36y0ohWwXo4XoUrB61rz1A4o",
|
||||
@@ -41,58 +41,55 @@ func TestPushoverNotifier(t *testing.T) {
|
||||
"okSound": "magic"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "Pushover",
|
||||
Type: "pushover",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "Pushover",
|
||||
Type: "pushover",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
pushoverNotifier := not.(*PushoverNotifier)
|
||||
not, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
pushoverNotifier := not.(*PushoverNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(pushoverNotifier.Name, ShouldEqual, "Pushover")
|
||||
So(pushoverNotifier.Type, ShouldEqual, "pushover")
|
||||
So(pushoverNotifier.APIToken, ShouldEqual, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve")
|
||||
So(pushoverNotifier.UserKey, ShouldEqual, "tzNZYf36y0ohWwXo4XoUrB61rz1A4o")
|
||||
So(pushoverNotifier.AlertingPriority, ShouldEqual, 1)
|
||||
So(pushoverNotifier.OKPriority, ShouldEqual, 2)
|
||||
So(pushoverNotifier.AlertingSound, ShouldEqual, "pushover")
|
||||
So(pushoverNotifier.OKSound, ShouldEqual, "magic")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "Pushover", pushoverNotifier.Name)
|
||||
require.Equal(t, "pushover", pushoverNotifier.Type)
|
||||
require.Equal(t, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve", pushoverNotifier.APIToken)
|
||||
require.Equal(t, "tzNZYf36y0ohWwXo4XoUrB61rz1A4o", pushoverNotifier.UserKey)
|
||||
require.Equal(t, 1, pushoverNotifier.AlertingPriority)
|
||||
require.Equal(t, 2, pushoverNotifier.OKPriority)
|
||||
require.Equal(t, "pushover", pushoverNotifier.AlertingSound)
|
||||
require.Equal(t, "magic", pushoverNotifier.OKSound)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenPushoverBody(t *testing.T) {
|
||||
Convey("Pushover body generation tests", t, func() {
|
||||
Convey("Given common sounds", func() {
|
||||
sirenSound := "siren_sound_tst"
|
||||
successSound := "success_sound_tst"
|
||||
notifier := &PushoverNotifier{AlertingSound: sirenSound, OKSound: successSound}
|
||||
t.Run("Given common sounds", func(t *testing.T) {
|
||||
sirenSound := "siren_sound_tst"
|
||||
successSound := "success_sound_tst"
|
||||
notifier := &PushoverNotifier{AlertingSound: sirenSound, OKSound: successSound}
|
||||
|
||||
Convey("When alert is firing - should use siren sound", func() {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
_, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "")
|
||||
t.Run("When alert is firing - should use siren sound", func(t *testing.T) {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
_, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "")
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(strings.Contains(pushoverBody.String(), sirenSound), ShouldBeTrue)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.True(t, strings.Contains(pushoverBody.String(), sirenSound))
|
||||
})
|
||||
|
||||
Convey("When alert is ok - should use success sound", func() {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
_, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "")
|
||||
t.Run("When alert is ok - should use success sound", func(t *testing.T) {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
_, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "")
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(strings.Contains(pushoverBody.String(), successSound), ShouldBeTrue)
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.True(t, strings.Contains(pushoverBody.String(), successSound))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,51 +6,50 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSensuNotifier(t *testing.T) {
|
||||
Convey("Sensu notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "sensu",
|
||||
Type: "sensu",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "sensu",
|
||||
Type: "sensu",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("from settings", func() {
|
||||
json := `
|
||||
t.Run("from settings", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"url": "http://sensu-api.example.com:4567/results",
|
||||
"source": "grafana_instance_01",
|
||||
"handler": "myhandler"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "sensu",
|
||||
Type: "sensu",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "sensu",
|
||||
Type: "sensu",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
sensuNotifier := not.(*SensuNotifier)
|
||||
not, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
sensuNotifier := not.(*SensuNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(sensuNotifier.Name, ShouldEqual, "sensu")
|
||||
So(sensuNotifier.Type, ShouldEqual, "sensu")
|
||||
So(sensuNotifier.URL, ShouldEqual, "http://sensu-api.example.com:4567/results")
|
||||
So(sensuNotifier.Source, ShouldEqual, "grafana_instance_01")
|
||||
So(sensuNotifier.Handler, ShouldEqual, "myhandler")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "sensu", sensuNotifier.Name)
|
||||
require.Equal(t, "sensu", sensuNotifier.Type)
|
||||
require.Equal(t, "http://sensu-api.example.com:4567/results", sensuNotifier.URL)
|
||||
require.Equal(t, "grafana_instance_01", sensuNotifier.Source)
|
||||
require.Equal(t, "myhandler", sensuNotifier.Handler)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,69 +6,68 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTeamsNotifier(t *testing.T) {
|
||||
Convey("Teams notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "teams",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "teams",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("from settings", func() {
|
||||
json := `
|
||||
t.Run("from settings", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"url": "http://google.com"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "teams",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "teams",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
teamsNotifier := not.(*TeamsNotifier)
|
||||
not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
teamsNotifier := not.(*TeamsNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(teamsNotifier.Name, ShouldEqual, "ops")
|
||||
So(teamsNotifier.Type, ShouldEqual, "teams")
|
||||
So(teamsNotifier.URL, ShouldEqual, "http://google.com")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "ops", teamsNotifier.Name)
|
||||
require.Equal(t, "teams", teamsNotifier.Type)
|
||||
require.Equal(t, "http://google.com", teamsNotifier.URL)
|
||||
})
|
||||
|
||||
Convey("from settings with Recipient and Mention", func() {
|
||||
json := `
|
||||
t.Run("from settings with Recipient and Mention", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"url": "http://google.com"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "teams",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "ops",
|
||||
Type: "teams",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
teamsNotifier := not.(*TeamsNotifier)
|
||||
not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
teamsNotifier := not.(*TeamsNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(teamsNotifier.Name, ShouldEqual, "ops")
|
||||
So(teamsNotifier.Type, ShouldEqual, "teams")
|
||||
So(teamsNotifier.URL, ShouldEqual, "http://google.com")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "ops", teamsNotifier.Name)
|
||||
require.Equal(t, "teams", teamsNotifier.Type)
|
||||
require.Equal(t, "http://google.com", teamsNotifier.URL)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,51 +9,67 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTelegramNotifier(t *testing.T) {
|
||||
Convey("Telegram notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "telegram_testing",
|
||||
Type: "telegram",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "telegram_testing",
|
||||
Type: "telegram",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("settings should trigger incident", func() {
|
||||
json := `
|
||||
t.Run("settings should trigger incident", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"bottoken": "abcdefgh0123456789",
|
||||
"chatid": "-1234567890"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "telegram_testing",
|
||||
Type: "telegram",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "telegram_testing",
|
||||
Type: "telegram",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
telegramNotifier := not.(*TelegramNotifier)
|
||||
not, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
telegramNotifier := not.(*TelegramNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(telegramNotifier.Name, ShouldEqual, "telegram_testing")
|
||||
So(telegramNotifier.Type, ShouldEqual, "telegram")
|
||||
So(telegramNotifier.BotToken, ShouldEqual, "abcdefgh0123456789")
|
||||
So(telegramNotifier.ChatID, ShouldEqual, "-1234567890")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "telegram_testing", telegramNotifier.Name)
|
||||
require.Equal(t, "telegram", telegramNotifier.Type)
|
||||
require.Equal(t, "abcdefgh0123456789", telegramNotifier.BotToken)
|
||||
require.Equal(t, "-1234567890", telegramNotifier.ChatID)
|
||||
})
|
||||
|
||||
Convey("generateCaption should generate a message with all pertinent details", func() {
|
||||
t.Run("generateCaption should generate a message with all pertinent details", func(t *testing.T) {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message.",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
|
||||
caption := generateImageCaption(evalContext, "http://grafa.url/abcdef", "")
|
||||
require.LessOrEqual(t, len(caption), 1024)
|
||||
require.Contains(t, caption, "Some kind of message.")
|
||||
require.Contains(t, caption, "[OK] This is an alarm")
|
||||
require.Contains(t, caption, "http://grafa.url/abcdef")
|
||||
})
|
||||
|
||||
t.Run("When generating a message", func(t *testing.T) {
|
||||
t.Run("URL should be skipped if it's too long", func(t *testing.T) {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
Name: "This is an alarm",
|
||||
@@ -61,65 +77,48 @@ func TestTelegramNotifier(t *testing.T) {
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
|
||||
caption := generateImageCaption(evalContext, "http://grafa.url/abcdef", "")
|
||||
So(len(caption), ShouldBeLessThanOrEqualTo, 1024)
|
||||
So(caption, ShouldContainSubstring, "Some kind of message.")
|
||||
So(caption, ShouldContainSubstring, "[OK] This is an alarm")
|
||||
So(caption, ShouldContainSubstring, "http://grafa.url/abcdef")
|
||||
caption := generateImageCaption(evalContext,
|
||||
"http://grafa.url/abcdefaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"foo bar")
|
||||
require.LessOrEqual(t, len(caption), 1024)
|
||||
require.Contains(t, caption, "Some kind of message.")
|
||||
require.Contains(t, caption, "[OK] This is an alarm")
|
||||
require.Contains(t, caption, "foo bar")
|
||||
require.NotContains(t, caption, "http")
|
||||
})
|
||||
|
||||
Convey("When generating a message", func() {
|
||||
Convey("URL should be skipped if it's too long", func() {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message.",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
t.Run("Message should be trimmed if it's too long", func(t *testing.T) {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis scelerisque. Nulla ipsum ex, iaculis vitae vehicula sit amet, fermentum eu eros.",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
|
||||
caption := generateImageCaption(evalContext,
|
||||
"http://grafa.url/abcdefaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"foo bar")
|
||||
So(len(caption), ShouldBeLessThanOrEqualTo, 1024)
|
||||
So(caption, ShouldContainSubstring, "Some kind of message.")
|
||||
So(caption, ShouldContainSubstring, "[OK] This is an alarm")
|
||||
So(caption, ShouldContainSubstring, "foo bar")
|
||||
So(caption, ShouldNotContainSubstring, "http")
|
||||
})
|
||||
caption := generateImageCaption(evalContext,
|
||||
"http://grafa.url/foo",
|
||||
"")
|
||||
require.LessOrEqual(t, len(caption), 1024)
|
||||
require.Contains(t, caption, "[OK] This is an alarm")
|
||||
require.NotContains(t, caption, "http")
|
||||
require.Contains(t, caption, "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri")
|
||||
})
|
||||
|
||||
Convey("Message should be trimmed if it's too long", func() {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis scelerisque. Nulla ipsum ex, iaculis vitae vehicula sit amet, fermentum eu eros.",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
t.Run("Metrics should be skipped if they don't fit", func(t *testing.T) {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
|
||||
caption := generateImageCaption(evalContext,
|
||||
"http://grafa.url/foo",
|
||||
"")
|
||||
So(len(caption), ShouldBeLessThanOrEqualTo, 1024)
|
||||
So(caption, ShouldContainSubstring, "[OK] This is an alarm")
|
||||
So(caption, ShouldNotContainSubstring, "http")
|
||||
So(caption, ShouldContainSubstring, "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri")
|
||||
})
|
||||
|
||||
Convey("Metrics should be skipped if they don't fit", func() {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
|
||||
caption := generateImageCaption(evalContext,
|
||||
"http://grafa.url/foo",
|
||||
"foo bar long song")
|
||||
So(len(caption), ShouldBeLessThanOrEqualTo, 1024)
|
||||
So(caption, ShouldContainSubstring, "[OK] This is an alarm")
|
||||
So(caption, ShouldNotContainSubstring, "http")
|
||||
So(caption, ShouldNotContainSubstring, "foo bar")
|
||||
})
|
||||
caption := generateImageCaption(evalContext,
|
||||
"http://grafa.url/foo",
|
||||
"foo bar long song")
|
||||
require.LessOrEqual(t, len(caption), 1024)
|
||||
require.Contains(t, caption, "[OK] This is an alarm")
|
||||
require.NotContains(t, caption, "http")
|
||||
require.NotContains(t, caption, "foo bar")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -8,118 +8,117 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestThreemaNotifier(t *testing.T) {
|
||||
Convey("Threema notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("valid settings should be parsed successfully", func() {
|
||||
json := `
|
||||
t.Run("valid settings should be parsed successfully", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"gateway_id": "*3MAGWID",
|
||||
"recipient_id": "ECHOECHO",
|
||||
"api_secret": "1234"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldBeNil)
|
||||
threemaNotifier := not.(*ThreemaNotifier)
|
||||
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Nil(t, err)
|
||||
threemaNotifier := not.(*ThreemaNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(threemaNotifier.Name, ShouldEqual, "threema_testing")
|
||||
So(threemaNotifier.Type, ShouldEqual, "threema")
|
||||
So(threemaNotifier.GatewayID, ShouldEqual, "*3MAGWID")
|
||||
So(threemaNotifier.RecipientID, ShouldEqual, "ECHOECHO")
|
||||
So(threemaNotifier.APISecret, ShouldEqual, "1234")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "threema_testing", threemaNotifier.Name)
|
||||
require.Equal(t, "threema", threemaNotifier.Type)
|
||||
require.Equal(t, "*3MAGWID", threemaNotifier.GatewayID)
|
||||
require.Equal(t, "ECHOECHO", threemaNotifier.RecipientID)
|
||||
require.Equal(t, "1234", threemaNotifier.APISecret)
|
||||
})
|
||||
|
||||
Convey("invalid Threema Gateway IDs should be rejected (prefix)", func() {
|
||||
json := `
|
||||
t.Run("invalid Threema Gateway IDs should be rejected (prefix)", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"gateway_id": "ECHOECHO",
|
||||
"recipient_id": "ECHOECHO",
|
||||
"api_secret": "1234"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(not, ShouldBeNil)
|
||||
var valErr alerting.ValidationError
|
||||
So(errors.As(err, &valErr), ShouldBeTrue)
|
||||
So(valErr.Reason, ShouldEqual, "Invalid Threema Gateway ID: Must start with a *")
|
||||
})
|
||||
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Nil(t, not)
|
||||
var valErr alerting.ValidationError
|
||||
require.True(t, errors.As(err, &valErr))
|
||||
require.Equal(t, "Invalid Threema Gateway ID: Must start with a *", valErr.Reason)
|
||||
})
|
||||
|
||||
Convey("invalid Threema Gateway IDs should be rejected (length)", func() {
|
||||
json := `
|
||||
t.Run("invalid Threema Gateway IDs should be rejected (length)", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"gateway_id": "*ECHOECHO",
|
||||
"recipient_id": "ECHOECHO",
|
||||
"api_secret": "1234"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(not, ShouldBeNil)
|
||||
var valErr alerting.ValidationError
|
||||
So(errors.As(err, &valErr), ShouldBeTrue)
|
||||
So(valErr.Reason, ShouldEqual, "Invalid Threema Gateway ID: Must be 8 characters long")
|
||||
})
|
||||
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Nil(t, not)
|
||||
var valErr alerting.ValidationError
|
||||
require.True(t, errors.As(err, &valErr))
|
||||
require.Equal(t, "Invalid Threema Gateway ID: Must be 8 characters long", valErr.Reason)
|
||||
})
|
||||
|
||||
Convey("invalid Threema Recipient IDs should be rejected (length)", func() {
|
||||
json := `
|
||||
t.Run("invalid Threema Recipient IDs should be rejected (length)", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"gateway_id": "*3MAGWID",
|
||||
"recipient_id": "ECHOECH",
|
||||
"api_secret": "1234"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "threema_testing",
|
||||
Type: "threema",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(not, ShouldBeNil)
|
||||
var valErr alerting.ValidationError
|
||||
So(errors.As(err, &valErr), ShouldBeTrue)
|
||||
So(valErr.Reason, ShouldEqual, "Invalid Threema Recipient ID: Must be 8 characters long")
|
||||
})
|
||||
not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Nil(t, not)
|
||||
var valErr alerting.ValidationError
|
||||
require.True(t, errors.As(err, &valErr))
|
||||
require.Equal(t, "Invalid Threema Recipient ID: Must be 8 characters long", valErr.Reason)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func presenceComparerInt(a, b int64) bool {
|
||||
@@ -23,140 +24,138 @@ func presenceComparerInt(a, b int64) bool {
|
||||
return a == b
|
||||
}
|
||||
func TestVictoropsNotifier(t *testing.T) {
|
||||
Convey("Victorops notifier tests", t, func() {
|
||||
Convey("Parsing alert notification from settings", func() {
|
||||
Convey("empty settings should return error", func() {
|
||||
json := `{ }`
|
||||
t.Run("Parsing alert notification from settings", func(t *testing.T) {
|
||||
t.Run("empty settings should return error", func(t *testing.T) {
|
||||
json := `{ }`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "victorops_testing",
|
||||
Type: "victorops",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "victorops_testing",
|
||||
Type: "victorops",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
_, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
Convey("from settings", func() {
|
||||
json := `
|
||||
t.Run("from settings", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"url": "http://google.com"
|
||||
}`
|
||||
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "victorops_testing",
|
||||
Type: "victorops",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
settingsJSON, _ := simplejson.NewJson([]byte(json))
|
||||
model := &models.AlertNotification{
|
||||
Name: "victorops_testing",
|
||||
Type: "victorops",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
victoropsNotifier := not.(*VictoropsNotifier)
|
||||
not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
victoropsNotifier := not.(*VictoropsNotifier)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(victoropsNotifier.Name, ShouldEqual, "victorops_testing")
|
||||
So(victoropsNotifier.Type, ShouldEqual, "victorops")
|
||||
So(victoropsNotifier.URL, ShouldEqual, "http://google.com")
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "victorops_testing", victoropsNotifier.Name)
|
||||
require.Equal(t, "victorops", victoropsNotifier.Type)
|
||||
require.Equal(t, "http://google.com", victoropsNotifier.URL)
|
||||
})
|
||||
|
||||
Convey("should return properly formatted event payload when using severity override tag", func() {
|
||||
json := `
|
||||
t.Run("should return properly formatted event payload when using severity override tag", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"url": "http://google.com"
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "victorops_testing",
|
||||
Type: "victorops",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "victorops_testing",
|
||||
Type: "victorops",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldBeNil)
|
||||
not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Nil(t, err)
|
||||
|
||||
victoropsNotifier := not.(*VictoropsNotifier)
|
||||
victoropsNotifier := not.(*VictoropsNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "severity", Value: "warning"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "severity", Value: "warning"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payload, err := victoropsNotifier.buildEventPayload(evalContext)
|
||||
So(err, ShouldBeNil)
|
||||
payload, err := victoropsNotifier.buildEventPayload(evalContext)
|
||||
require.Nil(t, err)
|
||||
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"alert_url": "",
|
||||
"entity_display_name": "[Alerting] someRule",
|
||||
"entity_id": "someRule",
|
||||
"message_type": "WARNING",
|
||||
"metrics": map[string]interface{}{},
|
||||
"monitoring_tool": "Grafana v",
|
||||
"state_message": "someMessage",
|
||||
"state_start_time": int64(-1),
|
||||
"timestamp": int64(-1),
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparerInt))
|
||||
So(diff, ShouldBeEmpty)
|
||||
})
|
||||
Convey("resolving with severity works properly", func() {
|
||||
json := `
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"alert_url": "",
|
||||
"entity_display_name": "[Alerting] someRule",
|
||||
"entity_id": "someRule",
|
||||
"message_type": "WARNING",
|
||||
"metrics": map[string]interface{}{},
|
||||
"monitoring_tool": "Grafana v",
|
||||
"state_message": "someMessage",
|
||||
"state_start_time": int64(-1),
|
||||
"timestamp": int64(-1),
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparerInt))
|
||||
require.Empty(t, diff)
|
||||
})
|
||||
t.Run("resolving with severity works properly", func(t *testing.T) {
|
||||
json := `
|
||||
{
|
||||
"url": "http://google.com"
|
||||
}`
|
||||
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||
require.Nil(t, err)
|
||||
|
||||
model := &models.AlertNotification{
|
||||
Name: "victorops_testing",
|
||||
Type: "victorops",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
model := &models.AlertNotification{
|
||||
Name: "victorops_testing",
|
||||
Type: "victorops",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
So(err, ShouldBeNil)
|
||||
not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
require.Nil(t, err)
|
||||
|
||||
victoropsNotifier := not.(*VictoropsNotifier)
|
||||
victoropsNotifier := not.(*VictoropsNotifier)
|
||||
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateOK,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "severity", Value: "warning"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateOK,
|
||||
AlertRuleTags: []*models.Tag{
|
||||
{Key: "keyOnly"},
|
||||
{Key: "severity", Value: "warning"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payload, err := victoropsNotifier.buildEventPayload(evalContext)
|
||||
So(err, ShouldBeNil)
|
||||
payload, err := victoropsNotifier.buildEventPayload(evalContext)
|
||||
require.Nil(t, err)
|
||||
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"alert_url": "",
|
||||
"entity_display_name": "[OK] someRule",
|
||||
"entity_id": "someRule",
|
||||
"message_type": "RECOVERY",
|
||||
"metrics": map[string]interface{}{},
|
||||
"monitoring_tool": "Grafana v",
|
||||
"state_message": "someMessage",
|
||||
"state_start_time": int64(-1),
|
||||
"timestamp": int64(-1),
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparerInt))
|
||||
So(diff, ShouldBeEmpty)
|
||||
})
|
||||
diff := cmp.Diff(map[string]interface{}{
|
||||
"alert_url": "",
|
||||
"entity_display_name": "[OK] someRule",
|
||||
"entity_id": "someRule",
|
||||
"message_type": "RECOVERY",
|
||||
"metrics": map[string]interface{}{},
|
||||
"monitoring_tool": "Grafana v",
|
||||
"state_message": "someMessage",
|
||||
"state_start_time": int64(-1),
|
||||
"timestamp": int64(-1),
|
||||
}, payload.Interface(), cmp.Comparer(presenceComparerInt))
|
||||
require.Empty(t, diff)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
sc.initialResult.Result.FolderID = newFolder.Id
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
@@ -117,6 +118,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
sc.initialResult.Result.Name = "New Name"
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated
|
||||
sc.initialResult.Result.Model["title"] = "Text - Library Panel"
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
@@ -138,6 +140,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
sc.initialResult.Result.UID = cmd.UID
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated
|
||||
sc.initialResult.Result.Model["title"] = "Text - Library Panel"
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
@@ -209,6 +212,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
}
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
@@ -234,6 +238,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
}
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
@@ -259,28 +264,29 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
}
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
// Test is disabled currently due to flaky comparison
|
||||
// scenarioWithPanel(t, "When another admin tries to patch a library panel, it should change UpdatedBy successfully and return correct result",
|
||||
// func(t *testing.T, sc scenarioContext) {
|
||||
// cmd := patchLibraryElementCommand{FolderID: -1, Version: 1, Kind: int64(models.PanelElement)}
|
||||
// sc.reqContext.UserId = 2
|
||||
// sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
// resp := sc.service.patchHandler(sc.reqContext, cmd)
|
||||
// var result = validateAndUnMarshalResponse(t, resp)
|
||||
// sc.initialResult.Result.Meta.UpdatedBy.ID = int64(2)
|
||||
// sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
// sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
// sc.initialResult.Result.Version = 2
|
||||
// if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
// t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
// }
|
||||
// })
|
||||
scenarioWithPanel(t, "When another admin tries to patch a library panel, it should change UpdatedBy successfully and return correct result",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
cmd := patchLibraryElementCommand{FolderID: -1, Version: 1, Kind: int64(models.PanelElement)}
|
||||
sc.reqContext.UserId = 2
|
||||
sc.ctx.Req = web.SetURLParams(sc.ctx.Req, map[string]string{":uid": sc.initialResult.Result.UID})
|
||||
resp := sc.service.patchHandler(sc.reqContext, cmd)
|
||||
var result = validateAndUnMarshalResponse(t, resp)
|
||||
sc.initialResult.Result.Meta.UpdatedBy.ID = int64(2)
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
|
||||
scenarioWithPanel(t, "When an admin tries to patch a library panel with a name that already exists, it should fail",
|
||||
func(t *testing.T, sc scenarioContext) {
|
||||
@@ -363,6 +369,7 @@ func TestPatchLibraryElement(t *testing.T) {
|
||||
}
|
||||
sc.initialResult.Result.Meta.CreatedBy.Name = userInDbName
|
||||
sc.initialResult.Result.Meta.CreatedBy.AvatarURL = userInDbAvatar
|
||||
sc.initialResult.Result.Meta.Updated = result.Result.Meta.Updated
|
||||
sc.initialResult.Result.Version = 2
|
||||
if diff := cmp.Diff(sc.initialResult.Result, result.Result, getCompareOptions()...); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
|
||||
@@ -443,8 +443,12 @@ func contextWithTimeoutFromRequest(ctx context.Context, r *http.Request, default
|
||||
|
||||
func newTestReceiversResult(r *notifier.TestReceiversResult) apimodels.TestReceiversResult {
|
||||
v := apimodels.TestReceiversResult{
|
||||
Receivers: make([]apimodels.TestReceiverResult, len(r.Receivers)),
|
||||
NotifedAt: r.NotifedAt,
|
||||
Alert: apimodels.TestReceiversConfigAlertParams{
|
||||
Annotations: r.Alert.Annotations,
|
||||
Labels: r.Alert.Labels,
|
||||
},
|
||||
Receivers: make([]apimodels.TestReceiverResult, len(r.Receivers)),
|
||||
NotifiedAt: r.NotifedAt,
|
||||
}
|
||||
for ix, next := range r.Receivers {
|
||||
configs := make([]apimodels.TestReceiverConfigResult, len(next.Configs))
|
||||
|
||||
@@ -137,6 +137,8 @@ type MultiStatus struct{}
|
||||
|
||||
// swagger:parameters RoutePostTestReceivers
|
||||
type TestReceiversConfigParams struct {
|
||||
// in:body
|
||||
Alert *TestReceiversConfigAlertParams `yaml:"alert,omitempty" json:"alert,omitempty"`
|
||||
// in:body
|
||||
Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
|
||||
}
|
||||
@@ -145,10 +147,16 @@ func (c *TestReceiversConfigParams) ProcessConfig(encrypt EncryptFn) error {
|
||||
return processReceiverConfigs(c.Receivers, encrypt)
|
||||
}
|
||||
|
||||
type TestReceiversConfigAlertParams struct {
|
||||
Annotations model.LabelSet `yaml:"annotations,omitempty" json:"annotations,omitempty"`
|
||||
Labels model.LabelSet `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type TestReceiversResult struct {
|
||||
Receivers []TestReceiverResult `json:"receivers"`
|
||||
NotifedAt time.Time `json:"notified_at"`
|
||||
Alert TestReceiversConfigAlertParams `json:"alert"`
|
||||
Receivers []TestReceiverResult `json:"receivers"`
|
||||
NotifiedAt time.Time `json:"notified_at"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
|
||||
@@ -1065,6 +1065,19 @@
|
||||
"type": "array",
|
||||
"x-go-package": "github.com/prometheus/common/model"
|
||||
},
|
||||
"LabelSet": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/LabelValue"
|
||||
},
|
||||
"description": "A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet\nmay be fully-qualified down to the point where it may resolve to a single\nMetric in the data store or not. All operations that occur within the realm\nof a LabelSet can emit a vector of Metric entities to which the LabelSet may\nmatch.",
|
||||
"type": "object",
|
||||
"x-go-package": "github.com/prometheus/common/model"
|
||||
},
|
||||
"LabelValue": {
|
||||
"title": "A LabelValue is an associated value for a LabelName.",
|
||||
"type": "string",
|
||||
"x-go-package": "github.com/prometheus/common/model"
|
||||
},
|
||||
"Labels": {
|
||||
"description": "Labels is a sorted set of labels. Order has to be guaranteed upon\ninstantiation.",
|
||||
"items": {
|
||||
@@ -2403,12 +2416,27 @@
|
||||
"type": "object",
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"TestReceiversConfigAlertParams": {
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/LabelSet"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/LabelSet"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"TestReceiversResult": {
|
||||
"properties": {
|
||||
"alert": {
|
||||
"$ref": "#/definitions/TestReceiversConfigAlertParams"
|
||||
},
|
||||
"notified_at": {
|
||||
"format": "date-time",
|
||||
"type": "string",
|
||||
"x-go-name": "NotifedAt"
|
||||
"x-go-name": "NotifiedAt"
|
||||
},
|
||||
"receivers": {
|
||||
"items": {
|
||||
@@ -2649,6 +2677,7 @@
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"properties": {
|
||||
"alerts": {
|
||||
"description": "alerts",
|
||||
@@ -2670,9 +2699,7 @@
|
||||
"labels",
|
||||
"receiver"
|
||||
],
|
||||
"type": "object",
|
||||
"x-go-name": "AlertGroup",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroups": {
|
||||
"items": {
|
||||
@@ -2869,7 +2896,6 @@
|
||||
"type": "array"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@@ -2921,14 +2947,17 @@
|
||||
"status",
|
||||
"updatedAt"
|
||||
],
|
||||
"type": "object"
|
||||
"type": "object",
|
||||
"x-go-name": "GettableSilence",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
},
|
||||
"type": "array"
|
||||
"type": "array",
|
||||
"x-go-name": "GettableSilences",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"labelSet": {
|
||||
"additionalProperties": {
|
||||
@@ -3101,7 +3130,6 @@
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "name",
|
||||
@@ -3112,7 +3140,9 @@
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
"type": "object",
|
||||
"x-go-name": "Receiver",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"silence": {
|
||||
"description": "Silence silence",
|
||||
@@ -3699,6 +3729,14 @@
|
||||
"post": {
|
||||
"operationId": "RoutePostTestReceivers",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "body",
|
||||
"name": "alert",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/TestReceiversConfigAlertParams"
|
||||
},
|
||||
"x-go-name": "Alert"
|
||||
},
|
||||
{
|
||||
"in": "body",
|
||||
"name": "receivers",
|
||||
|
||||
@@ -493,6 +493,14 @@
|
||||
"summary": "Test Grafana managed receivers without saving them.",
|
||||
"operationId": "RoutePostTestReceivers",
|
||||
"parameters": [
|
||||
{
|
||||
"x-go-name": "Alert",
|
||||
"name": "alert",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/TestReceiversConfigAlertParams"
|
||||
}
|
||||
},
|
||||
{
|
||||
"x-go-name": "Receivers",
|
||||
"name": "receivers",
|
||||
@@ -2090,6 +2098,19 @@
|
||||
},
|
||||
"x-go-package": "github.com/prometheus/common/model"
|
||||
},
|
||||
"LabelSet": {
|
||||
"description": "A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet\nmay be fully-qualified down to the point where it may resolve to a single\nMetric in the data store or not. All operations that occur within the realm\nof a LabelSet can emit a vector of Metric entities to which the LabelSet may\nmatch.",
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/LabelValue"
|
||||
},
|
||||
"x-go-package": "github.com/prometheus/common/model"
|
||||
},
|
||||
"LabelValue": {
|
||||
"type": "string",
|
||||
"title": "A LabelValue is an associated value for a LabelName.",
|
||||
"x-go-package": "github.com/prometheus/common/model"
|
||||
},
|
||||
"Labels": {
|
||||
"description": "Labels is a sorted set of labels. Order has to be guaranteed upon\ninstantiation.",
|
||||
"type": "array",
|
||||
@@ -3429,13 +3450,28 @@
|
||||
},
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"TestReceiversConfigAlertParams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/LabelSet"
|
||||
},
|
||||
"labels": {
|
||||
"$ref": "#/definitions/LabelSet"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"TestReceiversResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alert": {
|
||||
"$ref": "#/definitions/TestReceiversConfigAlertParams"
|
||||
},
|
||||
"notified_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"x-go-name": "NotifedAt"
|
||||
"x-go-name": "NotifiedAt"
|
||||
},
|
||||
"receivers": {
|
||||
"type": "array",
|
||||
@@ -3675,6 +3711,7 @@
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
},
|
||||
"alertGroup": {
|
||||
"description": "AlertGroup alert group",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"alerts",
|
||||
@@ -3697,8 +3734,6 @@
|
||||
"$ref": "#/definitions/receiver"
|
||||
}
|
||||
},
|
||||
"x-go-name": "AlertGroup",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
},
|
||||
"alertGroups": {
|
||||
@@ -3899,7 +3934,6 @@
|
||||
"$ref": "#/definitions/gettableAlerts"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
@@ -3952,14 +3986,17 @@
|
||||
"x-go-name": "UpdatedAt"
|
||||
}
|
||||
},
|
||||
"x-go-name": "GettableSilence",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
},
|
||||
"x-go-name": "GettableSilences",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/gettableSilences"
|
||||
},
|
||||
"labelSet": {
|
||||
@@ -4134,7 +4171,6 @@
|
||||
"$ref": "#/definitions/postableSilence"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
@@ -4146,6 +4182,8 @@
|
||||
"x-go-name": "Name"
|
||||
}
|
||||
},
|
||||
"x-go-name": "Receiver",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
|
||||
"$ref": "#/definitions/receiver"
|
||||
},
|
||||
"silence": {
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
@@ -48,7 +47,7 @@ func NewAlertmanagerNotifier(model *NotificationChannelConfig, _ *template.Templ
|
||||
basicAuthPassword := fn(context.Background(), model.SecureSettings, "basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString())
|
||||
|
||||
return &AlertmanagerNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
DisableResolveMessage: model.DisableResolveMessage,
|
||||
@@ -63,7 +62,7 @@ func NewAlertmanagerNotifier(model *NotificationChannelConfig, _ *template.Templ
|
||||
|
||||
// AlertmanagerNotifier sends alert notifications to the alert manager
|
||||
type AlertmanagerNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
|
||||
urls []*url.URL
|
||||
basicAuthUser string
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// Base is the base implementation of a notifier. It contains the common fields across all notifier types.
|
||||
type Base struct {
|
||||
Name string
|
||||
Type string
|
||||
UID string
|
||||
IsDefault bool
|
||||
DisableResolveMessage bool
|
||||
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (n *Base) GetDisableResolveMessage() bool {
|
||||
return n.DisableResolveMessage
|
||||
}
|
||||
|
||||
func NewBase(model *models.AlertNotification) *Base {
|
||||
return &Base{
|
||||
UID: model.Uid,
|
||||
Name: model.Name,
|
||||
IsDefault: model.IsDefault,
|
||||
Type: model.Type,
|
||||
DisableResolveMessage: model.DisableResolveMessage,
|
||||
log: log.New("alerting.notifier." + model.Name),
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
)
|
||||
|
||||
const defaultDingdingMsgType = "link"
|
||||
@@ -31,7 +30,7 @@ func NewDingDingNotifier(model *NotificationChannelConfig, t *template.Template)
|
||||
msgType := model.Settings.Get("msgType").MustString(defaultDingdingMsgType)
|
||||
|
||||
return &DingDingNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
@@ -48,7 +47,7 @@ func NewDingDingNotifier(model *NotificationChannelConfig, t *template.Template)
|
||||
|
||||
// DingDingNotifier is responsible for sending alert notifications to ding ding.
|
||||
type DingDingNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
MsgType string
|
||||
URL string
|
||||
Message string
|
||||
|
||||
@@ -13,12 +13,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type DiscordNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
Content string
|
||||
@@ -41,7 +40,7 @@ func NewDiscordNotifier(model *NotificationChannelConfig, t *template.Template)
|
||||
content := model.Settings.Get("message").MustString(`{{ template "default.message" . }}`)
|
||||
|
||||
return &DiscordNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -11,14 +11,13 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// EmailNotifier is responsible for sending
|
||||
// alert notifications over email.
|
||||
type EmailNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
Addresses []string
|
||||
SingleEmail bool
|
||||
Message string
|
||||
@@ -44,7 +43,7 @@ func NewEmailNotifier(model *NotificationChannelConfig, t *template.Template) (*
|
||||
addresses := util.SplitEmails(addressesString)
|
||||
|
||||
return &EmailNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -12,14 +12,13 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// GoogleChatNotifier is responsible for sending
|
||||
// alert notifications to Google chat.
|
||||
type GoogleChatNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
URL string
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
@@ -32,7 +31,7 @@ func NewGoogleChatNotifier(model *NotificationChannelConfig, t *template.Templat
|
||||
}
|
||||
|
||||
return &GoogleChatNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -13,13 +13,12 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
)
|
||||
|
||||
// KafkaNotifier is responsible for sending
|
||||
// alert notifications to Kafka.
|
||||
type KafkaNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
Endpoint string
|
||||
Topic string
|
||||
log log.Logger
|
||||
@@ -38,7 +37,7 @@ func NewKafkaNotifier(model *NotificationChannelConfig, t *template.Template) (*
|
||||
}
|
||||
|
||||
return &KafkaNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
@@ -26,7 +25,7 @@ func NewLineNotifier(model *NotificationChannelConfig, t *template.Template, fn
|
||||
}
|
||||
|
||||
return &LineNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
@@ -42,7 +41,7 @@ func NewLineNotifier(model *NotificationChannelConfig, t *template.Template, fn
|
||||
// LineNotifier is responsible for sending
|
||||
// alert notifications to LINE.
|
||||
type LineNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
Token string
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
@@ -31,7 +30,7 @@ var (
|
||||
|
||||
// OpsgenieNotifier is responsible for sending alert notifications to Opsgenie.
|
||||
type OpsgenieNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
APIKey string
|
||||
APIUrl string
|
||||
AutoClose bool
|
||||
@@ -62,7 +61,7 @@ func NewOpsgenieNotifier(model *NotificationChannelConfig, t *template.Template,
|
||||
}
|
||||
|
||||
return &OpsgenieNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
@@ -28,7 +27,7 @@ var (
|
||||
// PagerdutyNotifier is responsible for sending
|
||||
// alert notifications to pagerduty
|
||||
type PagerdutyNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
Key string
|
||||
Severity string
|
||||
CustomDetails map[string]string
|
||||
@@ -52,7 +51,7 @@ func NewPagerdutyNotifier(model *NotificationChannelConfig, t *template.Template
|
||||
}
|
||||
|
||||
return &PagerdutyNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
@@ -23,7 +22,7 @@ var (
|
||||
// PushoverNotifier is responsible for sending
|
||||
// alert notifications to Pushover
|
||||
type PushoverNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
UserKey string
|
||||
APIToken string
|
||||
AlertingPriority int
|
||||
@@ -69,7 +68,7 @@ func NewPushoverNotifier(model *NotificationChannelConfig, t *template.Template,
|
||||
return nil, receiverInitError{Cfg: *model, Reason: "API token not found"}
|
||||
}
|
||||
return &PushoverNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -10,14 +10,13 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
type SensuGoNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
|
||||
@@ -47,7 +46,7 @@ func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template,
|
||||
}
|
||||
|
||||
return &SensuGoNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
@@ -26,7 +25,7 @@ import (
|
||||
// SlackNotifier is responsible for sending
|
||||
// alert notification to Slack.
|
||||
type SlackNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
|
||||
@@ -106,7 +105,7 @@ func NewSlackNotifier(model *NotificationChannelConfig, t *template.Template, fn
|
||||
}
|
||||
|
||||
return &SlackNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -11,13 +11,12 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
)
|
||||
|
||||
// TeamsNotifier is responsible for sending
|
||||
// alert notifications to Microsoft teams.
|
||||
type TeamsNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
URL string
|
||||
Message string
|
||||
tmpl *template.Template
|
||||
@@ -36,7 +35,7 @@ func NewTeamsNotifier(model *NotificationChannelConfig, t *template.Template) (*
|
||||
}
|
||||
|
||||
return &TeamsNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
@@ -21,7 +20,7 @@ var (
|
||||
// TelegramNotifier is responsible for sending
|
||||
// alert notifications to Telegram.
|
||||
type TelegramNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
BotToken string
|
||||
ChatID string
|
||||
Message string
|
||||
@@ -48,7 +47,7 @@ func NewTelegramNotifier(model *NotificationChannelConfig, t *template.Template,
|
||||
}
|
||||
|
||||
return &TelegramNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
"github.com/prometheus/common/model"
|
||||
@@ -23,7 +22,7 @@ var (
|
||||
// ThreemaNotifier is responsible for sending
|
||||
// alert notifications to Threema.
|
||||
type ThreemaNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
GatewayID string
|
||||
RecipientID string
|
||||
APISecret string
|
||||
@@ -62,7 +61,7 @@ func NewThreemaNotifier(model *NotificationChannelConfig, t *template.Template,
|
||||
}
|
||||
|
||||
return &ThreemaNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -35,7 +34,7 @@ func NewVictoropsNotifier(model *NotificationChannelConfig, t *template.Template
|
||||
}
|
||||
|
||||
return &VictoropsNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
@@ -53,7 +52,7 @@ func NewVictoropsNotifier(model *NotificationChannelConfig, t *template.Template
|
||||
// and handles notification process by formatting POST body according to
|
||||
// Victorops specifications (http://victorops.force.com/knowledgebase/articles/Integration/Alert-Ingestion-API-Documentation/)
|
||||
type VictoropsNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
URL string
|
||||
MessageType string
|
||||
log log.Logger
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
@@ -17,7 +16,7 @@ import (
|
||||
// WebhookNotifier is responsible for sending
|
||||
// alert notifications as webhooks.
|
||||
type WebhookNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
*Base
|
||||
URL string
|
||||
User string
|
||||
Password string
|
||||
@@ -39,7 +38,7 @@ func NewWebHookNotifier(model *NotificationChannelConfig, t *template.Template,
|
||||
return nil, receiverInitError{Cfg: *model, Reason: "could not find url property in settings"}
|
||||
}
|
||||
return &WebhookNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
|
||||
@@ -24,6 +24,7 @@ var (
|
||||
)
|
||||
|
||||
type TestReceiversResult struct {
|
||||
Alert types.Alert
|
||||
Receivers []TestReceiverResult
|
||||
NotifedAt time.Time
|
||||
}
|
||||
@@ -61,19 +62,7 @@ func (e ReceiverTimeoutError) Error() string {
|
||||
func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigParams) (*TestReceiversResult, error) {
|
||||
// now represents the start time of the test
|
||||
now := time.Now()
|
||||
testAlert := &types.Alert{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{
|
||||
model.LabelName("alertname"): "TestAlert",
|
||||
model.LabelName("instance"): "Grafana",
|
||||
},
|
||||
Annotations: model.LabelSet{
|
||||
model.LabelName("summary"): "Notification test",
|
||||
},
|
||||
StartsAt: now,
|
||||
},
|
||||
UpdatedAt: now,
|
||||
}
|
||||
testAlert := newTestAlert(c, now, now)
|
||||
|
||||
// we must set a group key that is unique per test as some receivers use this key to deduplicate alerts
|
||||
ctx = notify.WithGroupKey(ctx, testAlert.Labels.String()+now.String())
|
||||
@@ -97,7 +86,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei
|
||||
Error error
|
||||
}
|
||||
|
||||
newTestReceiversResult := func(results []result, notifiedAt time.Time) *TestReceiversResult {
|
||||
newTestReceiversResult := func(alert types.Alert, results []result, notifiedAt time.Time) *TestReceiversResult {
|
||||
m := make(map[string]TestReceiverResult)
|
||||
for _, receiver := range c.Receivers {
|
||||
// set up the result for this receiver
|
||||
@@ -122,6 +111,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei
|
||||
m[next.ReceiverName] = tmp
|
||||
}
|
||||
v := new(TestReceiversResult)
|
||||
v.Alert = alert
|
||||
v.Receivers = make([]TestReceiverResult, 0, len(c.Receivers))
|
||||
v.NotifedAt = notifiedAt
|
||||
for _, next := range m {
|
||||
@@ -165,7 +155,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei
|
||||
}
|
||||
|
||||
if len(jobs) == 0 {
|
||||
return newTestReceiversResult(invalid, now), nil
|
||||
return newTestReceiversResult(testAlert, invalid, now), nil
|
||||
}
|
||||
|
||||
numWorkers := maxTestReceiversWorkers
|
||||
@@ -188,7 +178,7 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei
|
||||
Config: next.Config,
|
||||
ReceiverName: next.ReceiverName,
|
||||
}
|
||||
if _, err := next.Notifier.Notify(ctx, testAlert); err != nil {
|
||||
if _, err := next.Notifier.Notify(ctx, &testAlert); err != nil {
|
||||
v.Error = err
|
||||
}
|
||||
resultCh <- v
|
||||
@@ -204,7 +194,43 @@ func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei
|
||||
results = append(results, next)
|
||||
}
|
||||
|
||||
return newTestReceiversResult(append(invalid, results...), now), nil
|
||||
return newTestReceiversResult(testAlert, append(invalid, results...), now), nil
|
||||
}
|
||||
|
||||
func newTestAlert(c apimodels.TestReceiversConfigParams, startsAt, updatedAt time.Time) types.Alert {
|
||||
var (
|
||||
defaultAnnotations = model.LabelSet{
|
||||
"summary": "Notification test",
|
||||
}
|
||||
defaultLabels = model.LabelSet{
|
||||
"alertname": "TestAlert",
|
||||
"instance": "Grafana",
|
||||
}
|
||||
)
|
||||
|
||||
alert := types.Alert{
|
||||
Alert: model.Alert{
|
||||
Labels: defaultLabels,
|
||||
Annotations: defaultAnnotations,
|
||||
StartsAt: startsAt,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}
|
||||
|
||||
if c.Alert != nil {
|
||||
if c.Alert.Annotations != nil {
|
||||
for k, v := range c.Alert.Annotations {
|
||||
alert.Annotations[k] = v
|
||||
}
|
||||
}
|
||||
if c.Alert.Labels != nil {
|
||||
for k, v := range c.Alert.Labels {
|
||||
alert.Labels[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return alert
|
||||
}
|
||||
|
||||
func processNotifierError(config *apimodels.PostableGrafanaReceiver, err error) error {
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -129,21 +128,6 @@ func (m *migration) makeReceiverAndRoute(ruleUid string, orgID int64, channelUid
|
||||
return err
|
||||
}
|
||||
|
||||
// Grafana accepts any type of string as a URL for the Slack notification channel.
|
||||
// However, the Alertmanager will fail if provided with an invalid URL we have two options at this point:
|
||||
// Either we fail the migration or remove the URL, we've chosen the latter and assume that the notification
|
||||
// channel was broken to begin with.
|
||||
if c.Type == "slack" {
|
||||
u, ok := decryptedSecureSettings["url"]
|
||||
if ok {
|
||||
_, err := url.Parse(u)
|
||||
if err != nil {
|
||||
m.mg.Logger.Warn("slack notification channel had invalid URL, removing", "name", c.Name, "uid", c.Uid, "org", c.OrgID)
|
||||
delete(decryptedSecureSettings, "url")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
portedChannels = append(portedChannels, &PostableGrafanaReceiver{
|
||||
UID: uid,
|
||||
Name: c.Name,
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
package ualert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func Test_makeReceiverAndRoute(t *testing.T) {
|
||||
emptyMigration := func() *migration {
|
||||
return &migration{
|
||||
mg: &migrator.Migrator{
|
||||
Logger: log.New("test"),
|
||||
},
|
||||
migratedChannelsPerOrg: make(map[int64]map[*notificationChannel]struct{}),
|
||||
portedChannelGroupsPerOrg: make(map[int64]map[string]string),
|
||||
seenChannelUIDs: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
generateChannel := func(channelType string, settings map[string]interface{}, secureSettings map[string]string) *notificationChannel {
|
||||
uid := util.GenerateShortUID()
|
||||
return ¬ificationChannel{
|
||||
ID: rand.Int63(),
|
||||
OrgID: rand.Int63(),
|
||||
Uid: uid,
|
||||
Name: fmt.Sprintf("Test-%s", uid),
|
||||
Type: channelType,
|
||||
DisableResolveMessage: rand.Int63()%2 == 0,
|
||||
IsDefault: rand.Int63()%2 == 0,
|
||||
Settings: simplejson.NewFromAny(settings),
|
||||
SecureSettings: GetEncryptedJsonData(secureSettings),
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("Slack channel is migrated", func(t *testing.T) {
|
||||
t.Run("url is removed if it is invalid (secure settings)", func(t *testing.T) {
|
||||
secureSettings := map[string]string{
|
||||
"url": invalidUri,
|
||||
"token": util.GenerateShortUID(),
|
||||
}
|
||||
settings := map[string]interface{}{
|
||||
"test": "data",
|
||||
"some_map": map[string]interface{}{
|
||||
"test": rand.Int63(),
|
||||
},
|
||||
}
|
||||
|
||||
channel := generateChannel("slack", settings, secureSettings)
|
||||
channelsUid := []interface{}{
|
||||
channel.Uid,
|
||||
}
|
||||
defaultChannels := make([]*notificationChannel, 0)
|
||||
allChannels := map[interface{}]*notificationChannel{
|
||||
channel.Uid: channel,
|
||||
}
|
||||
|
||||
apiReceiver, _, err := emptyMigration().makeReceiverAndRoute(util.GenerateShortUID(), channel.OrgID, channelsUid, defaultChannels, allChannels)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, apiReceiver.GrafanaManagedReceivers, 1)
|
||||
|
||||
receiver := apiReceiver.GrafanaManagedReceivers[0]
|
||||
|
||||
require.NotContains(t, receiver.SecureSettings, "url")
|
||||
require.Contains(t, receiver.SecureSettings, "token")
|
||||
require.Equal(t, secureSettings["token"], receiver.SecureSettings["token"])
|
||||
actualSettings, err := receiver.Settings.Map()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, settings, actualSettings)
|
||||
})
|
||||
|
||||
t.Run("url is removed if it is invalid (settings)", func(t *testing.T) {
|
||||
secureSettings := map[string]string{
|
||||
"token": util.GenerateShortUID(),
|
||||
}
|
||||
settings := map[string]interface{}{
|
||||
"url": invalidUri,
|
||||
"test": "data",
|
||||
"some_map": map[string]interface{}{
|
||||
"test": rand.Int63(),
|
||||
},
|
||||
}
|
||||
|
||||
channel := generateChannel("slack", settings, secureSettings)
|
||||
channelsUid := []interface{}{
|
||||
channel.Uid,
|
||||
}
|
||||
defaultChannels := make([]*notificationChannel, 0)
|
||||
allChannels := map[interface{}]*notificationChannel{
|
||||
channel.Uid: channel,
|
||||
}
|
||||
|
||||
apiReceiver, _, err := emptyMigration().makeReceiverAndRoute(util.GenerateShortUID(), channel.OrgID, channelsUid, defaultChannels, allChannels)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, apiReceiver.GrafanaManagedReceivers, 1)
|
||||
|
||||
receiver := apiReceiver.GrafanaManagedReceivers[0]
|
||||
|
||||
require.NotContains(t, receiver.SecureSettings, "url")
|
||||
require.Contains(t, receiver.SecureSettings, "token")
|
||||
require.Equal(t, secureSettings["token"], receiver.SecureSettings["token"])
|
||||
actualSettings, err := receiver.Settings.Map()
|
||||
require.NoError(t, err)
|
||||
delete(settings, "url")
|
||||
require.Equal(t, settings, actualSettings)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const invalidUri = "�6�M��)uk譹1(�h`$�o�N>mĕ����cS2�dh![ę� ���`csB�!��OSxP�{�"
|
||||
@@ -0,0 +1,23 @@
|
||||
package ualert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
)
|
||||
|
||||
// newTestMigration generates an empty migration to use in tests.
|
||||
func newTestMigration(t *testing.T) *migration {
|
||||
t.Helper()
|
||||
|
||||
return &migration{
|
||||
mg: &migrator.Migrator{
|
||||
|
||||
Logger: log.New("test"),
|
||||
},
|
||||
migratedChannelsPerOrg: make(map[int64]map[*notificationChannel]struct{}),
|
||||
portedChannelGroupsPerOrg: make(map[int64]map[string]string),
|
||||
seenChannelUIDs: make(map[string]struct{}),
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package ualert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -9,11 +11,12 @@ import (
|
||||
"strings"
|
||||
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
pb "github.com/prometheus/alertmanager/silence/silencepb"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
)
|
||||
|
||||
const GENERAL_FOLDER = "General Alerting"
|
||||
@@ -217,6 +220,7 @@ func (m *migration) SQL(dialect migrator.Dialect) string {
|
||||
return "code migration"
|
||||
}
|
||||
|
||||
//nolint: gocyclo
|
||||
func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
m.sess = sess
|
||||
m.mg = mg
|
||||
@@ -377,7 +381,24 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.writeAlertmanagerConfig(orgID, amConfig, allChannelsPerOrg[orgID]); err != nil {
|
||||
// No channels, hence don't require Alertmanager config - skip it.
|
||||
if len(allChannelsPerOrg[orgID]) == 0 {
|
||||
m.mg.Logger.Info("alert migration: no notification channel found, skipping Alertmanager config")
|
||||
continue
|
||||
}
|
||||
|
||||
// Encrypt the secure settings before we continue.
|
||||
if err := amConfig.EncryptSecureSettings(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate the alertmanager configuration produced, this gives a chance to catch bad configuration at migration time.
|
||||
// Validation between legacy and unified alerting can be different (e.g. due to bug fixes) so this would fail the migration in that case.
|
||||
if err := m.validateAlertmanagerConfig(orgID, amConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.writeAlertmanagerConfig(orgID, amConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -389,22 +410,13 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *migration) writeAlertmanagerConfig(orgID int64, amConfig *PostableUserConfig, allChannels map[interface{}]*notificationChannel) error {
|
||||
if len(allChannels) == 0 {
|
||||
// No channels, hence don't require Alertmanager config.
|
||||
m.mg.Logger.Info("alert migration: no notification channel found, skipping Alertmanager config")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := amConfig.EncryptSecureSettings(); err != nil {
|
||||
return err
|
||||
}
|
||||
func (m *migration) writeAlertmanagerConfig(orgID int64, amConfig *PostableUserConfig) error {
|
||||
rawAmConfig, err := json.Marshal(amConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: should we apply the config here? Because Alertmanager can take upto 1 min to pick it up.
|
||||
// We don't need to apply the configuration, given the multi org alertmanager will do an initial sync before the server is ready.
|
||||
_, err = m.sess.Insert(AlertConfiguration{
|
||||
AlertmanagerConfiguration: string(rawAmConfig),
|
||||
// Since we are migration for a snapshot of the code, it is always going to migrate to
|
||||
@@ -419,6 +431,95 @@ func (m *migration) writeAlertmanagerConfig(orgID int64, amConfig *PostableUserC
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateAlertmanagerConfig validates the alertmanager configuration produced by the migration against the receivers.
|
||||
func (m *migration) validateAlertmanagerConfig(orgID int64, config *PostableUserConfig) error {
|
||||
for _, r := range config.AlertmanagerConfig.Receivers {
|
||||
for _, gr := range r.GrafanaManagedReceivers {
|
||||
// First, let's decode the secure settings - given they're stored as base64.
|
||||
secureSettings := make(map[string][]byte, len(gr.SecureSettings))
|
||||
for k, v := range gr.SecureSettings {
|
||||
d, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secureSettings[k] = d
|
||||
}
|
||||
|
||||
var (
|
||||
cfg = &channels.NotificationChannelConfig{
|
||||
UID: gr.UID,
|
||||
OrgID: orgID,
|
||||
Name: gr.Name,
|
||||
Type: gr.Type,
|
||||
DisableResolveMessage: gr.DisableResolveMessage,
|
||||
Settings: gr.Settings,
|
||||
SecureSettings: secureSettings,
|
||||
}
|
||||
err error
|
||||
)
|
||||
|
||||
// decryptFunc represents the legacy way of decrypting data. Before the migration, we don't need any new way,
|
||||
// given that the previous alerting will never support it.
|
||||
decryptFunc := func(_ context.Context, sjd map[string][]byte, key string, fallback string, secret string) string {
|
||||
if value, ok := sjd[key]; ok {
|
||||
decryptedData, err := util.Decrypt(value, secret)
|
||||
if err != nil {
|
||||
m.mg.Logger.Warn("unable to decrypt key '%s' for %s receiver with uid %s, returning fallback.", key, gr.Type, gr.UID)
|
||||
return fallback
|
||||
}
|
||||
return string(decryptedData)
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
switch gr.Type {
|
||||
case "email":
|
||||
_, err = channels.NewEmailNotifier(cfg, nil) // Email notifier already has a default template.
|
||||
case "pagerduty":
|
||||
_, err = channels.NewPagerdutyNotifier(cfg, nil, decryptFunc)
|
||||
case "pushover":
|
||||
_, err = channels.NewPushoverNotifier(cfg, nil, decryptFunc)
|
||||
case "slack":
|
||||
_, err = channels.NewSlackNotifier(cfg, nil, decryptFunc)
|
||||
case "telegram":
|
||||
_, err = channels.NewTelegramNotifier(cfg, nil, decryptFunc)
|
||||
case "victorops":
|
||||
_, err = channels.NewVictoropsNotifier(cfg, nil)
|
||||
case "teams":
|
||||
_, err = channels.NewTeamsNotifier(cfg, nil)
|
||||
case "dingding":
|
||||
_, err = channels.NewDingDingNotifier(cfg, nil)
|
||||
case "kafka":
|
||||
_, err = channels.NewKafkaNotifier(cfg, nil)
|
||||
case "webhook":
|
||||
_, err = channels.NewWebHookNotifier(cfg, nil, decryptFunc)
|
||||
case "sensugo":
|
||||
_, err = channels.NewSensuGoNotifier(cfg, nil, decryptFunc)
|
||||
case "discord":
|
||||
_, err = channels.NewDiscordNotifier(cfg, nil)
|
||||
case "googlechat":
|
||||
_, err = channels.NewGoogleChatNotifier(cfg, nil)
|
||||
case "LINE":
|
||||
_, err = channels.NewLineNotifier(cfg, nil, decryptFunc)
|
||||
case "threema":
|
||||
_, err = channels.NewThreemaNotifier(cfg, nil, decryptFunc)
|
||||
case "opsgenie":
|
||||
_, err = channels.NewOpsgenieNotifier(cfg, nil, decryptFunc)
|
||||
case "prometheus-alertmanager":
|
||||
_, err = channels.NewAlertmanagerNotifier(cfg, nil, decryptFunc)
|
||||
default:
|
||||
return fmt.Errorf("notifier %s is not supported", gr.Type)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type AlertConfiguration struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
OrgID int64 `xorm:"org_id"`
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package ualert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_validateAlertmanagerConfig(t *testing.T) {
|
||||
tc := []struct {
|
||||
name string
|
||||
receivers []*PostableGrafanaReceiver
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "when a slack receiver does not have a valid URL - it should error",
|
||||
receivers: []*PostableGrafanaReceiver{
|
||||
{
|
||||
UID: util.GenerateShortUID(),
|
||||
Name: "SlackWithBadURL",
|
||||
Type: "slack",
|
||||
Settings: simplejson.NewFromAny(map[string]interface{}{}),
|
||||
SecureSettings: map[string]string{"url": invalidUri},
|
||||
},
|
||||
},
|
||||
err: fmt.Errorf("failed to validate receiver \"SlackWithBadURL\" of type \"slack\": invalid URL %q: parse %q: net/url: invalid control character in URL", invalidUri, invalidUri),
|
||||
},
|
||||
{
|
||||
name: "when a slack receiver has an invalid recipient - it should error",
|
||||
receivers: []*PostableGrafanaReceiver{
|
||||
{
|
||||
UID: util.GenerateShortUID(),
|
||||
Name: "SlackWithBadRecipient",
|
||||
Type: "slack",
|
||||
Settings: simplejson.NewFromAny(map[string]interface{}{"recipient": "this-doesnt-pass"}),
|
||||
SecureSettings: map[string]string{"url": "http://webhook.slack.com/myuser"},
|
||||
},
|
||||
},
|
||||
err: errors.New("failed to validate receiver \"SlackWithBadRecipient\" of type \"slack\": recipient on invalid format: \"this-doesnt-pass\""),
|
||||
},
|
||||
{
|
||||
name: "when the configuration is valid - it should not error",
|
||||
receivers: []*PostableGrafanaReceiver{
|
||||
{
|
||||
UID: util.GenerateShortUID(),
|
||||
Name: "SlackWithBadURL",
|
||||
Type: "slack",
|
||||
Settings: simplejson.NewFromAny(map[string]interface{}{"recipient": "#a-good-channel"}),
|
||||
SecureSettings: map[string]string{"url": "http://webhook.slack.com/myuser"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tc {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mg := newTestMigration(t)
|
||||
orgID := int64(1)
|
||||
|
||||
config := configFromReceivers(t, tt.receivers)
|
||||
require.NoError(t, config.EncryptSecureSettings()) // make sure we encrypt the settings
|
||||
err := mg.validateAlertmanagerConfig(orgID, config)
|
||||
if tt.err != nil {
|
||||
require.Error(t, err)
|
||||
require.EqualError(t, err, tt.err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func configFromReceivers(t *testing.T, receivers []*PostableGrafanaReceiver) *PostableUserConfig {
|
||||
t.Helper()
|
||||
|
||||
return &PostableUserConfig{
|
||||
AlertmanagerConfig: PostableApiAlertingConfig{
|
||||
Receivers: []*PostableApiReceiver{
|
||||
{GrafanaManagedReceivers: receivers},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const invalidUri = "�6�M��)uk譹1(�h`$�o�N>mĕ����cS2�dh![ę� ���`csB�!��OSxP�{�"
|
||||
@@ -92,14 +92,14 @@ func TestTestReceivers(t *testing.T) {
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"uid":"",
|
||||
"name":"receiver-1",
|
||||
"type":"email",
|
||||
"disableResolveMessage":false,
|
||||
"settings":{
|
||||
"uid": "",
|
||||
"name": "receiver-1",
|
||||
"type": "email",
|
||||
"disableResolveMessage": false,
|
||||
"settings": {
|
||||
"addresses":"example@email.com"
|
||||
},
|
||||
"secureFields":{}
|
||||
"secureFields": {}
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -109,22 +109,39 @@ func TestTestReceivers(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&result))
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
require.Len(t, result.Receivers, 1)
|
||||
require.Len(t, result.Receivers[0].Configs, 1)
|
||||
require.Equal(t, apimodels.TestReceiversResult{
|
||||
Receivers: []apimodels.TestReceiverResult{{
|
||||
Name: "receiver-1",
|
||||
Configs: []apimodels.TestReceiverConfigResult{{
|
||||
Name: "receiver-1",
|
||||
UID: result.Receivers[0].Configs[0].UID,
|
||||
Status: "ok",
|
||||
}},
|
||||
}},
|
||||
NotifedAt: result.NotifedAt,
|
||||
}, result)
|
||||
|
||||
expectedJSON := fmt.Sprintf(`{
|
||||
"alert": {
|
||||
"annotations": {
|
||||
"summary": "Notification test"
|
||||
},
|
||||
"labels": {
|
||||
"alertname": "TestAlert",
|
||||
"instance": "Grafana"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"name": "receiver-1",
|
||||
"uid": "%s",
|
||||
"status": "ok"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"notified_at": "%s"
|
||||
}`,
|
||||
result.Receivers[0].Configs[0].UID,
|
||||
result.NotifiedAt.Format(time.RFC3339Nano))
|
||||
require.JSONEq(t, expectedJSON, string(b))
|
||||
|
||||
require.Len(t, mockEmails.emails, 1)
|
||||
require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To)
|
||||
@@ -160,38 +177,54 @@ func TestTestReceivers(t *testing.T) {
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"uid":"",
|
||||
"name":"receiver-1",
|
||||
"type":"email",
|
||||
"disableResolveMessage":false,
|
||||
"settings":{},
|
||||
"secureFields":{}
|
||||
"uid": "",
|
||||
"name": "receiver-1",
|
||||
"type": "email",
|
||||
"disableResolveMessage": false,
|
||||
"settings": {},
|
||||
"secureFields": {}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}`, http.StatusBadRequest)
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, resp.Body.Close())
|
||||
})
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
require.Len(t, result.Receivers, 1)
|
||||
require.Len(t, result.Receivers[0].Configs, 1)
|
||||
require.Equal(t, apimodels.TestReceiversResult{
|
||||
Receivers: []apimodels.TestReceiverResult{{
|
||||
Name: "receiver-1",
|
||||
Configs: []apimodels.TestReceiverConfigResult{{
|
||||
Name: "receiver-1",
|
||||
UID: result.Receivers[0].Configs[0].UID,
|
||||
Status: "failed",
|
||||
Error: "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings",
|
||||
}},
|
||||
}},
|
||||
NotifedAt: result.NotifedAt,
|
||||
}, result)
|
||||
|
||||
expectedJSON := fmt.Sprintf(`{
|
||||
"alert": {
|
||||
"annotations": {
|
||||
"summary": "Notification test"
|
||||
},
|
||||
"labels": {
|
||||
"alertname": "TestAlert",
|
||||
"instance": "Grafana"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"name": "receiver-1",
|
||||
"uid": "%s",
|
||||
"status": "failed",
|
||||
"error": "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"notified_at": "%s"
|
||||
}`,
|
||||
result.Receivers[0].Configs[0].UID,
|
||||
result.NotifiedAt.Format(time.RFC3339Nano))
|
||||
require.JSONEq(t, expectedJSON, string(b))
|
||||
})
|
||||
|
||||
t.Run("assert timed out receiver returns 408 Request Timeout", func(t *testing.T) {
|
||||
@@ -225,14 +258,14 @@ func TestTestReceivers(t *testing.T) {
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"uid":"",
|
||||
"name":"receiver-1",
|
||||
"type":"email",
|
||||
"disableResolveMessage":false,
|
||||
"settings":{
|
||||
"uid": "",
|
||||
"name": "receiver-1",
|
||||
"type": "email",
|
||||
"disableResolveMessage": false,
|
||||
"settings": {
|
||||
"addresses":"example@email.com"
|
||||
},
|
||||
"secureFields":{}
|
||||
"secureFields": {}
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -248,23 +281,40 @@ func TestTestReceivers(t *testing.T) {
|
||||
})
|
||||
require.Equal(t, http.StatusRequestTimeout, resp.StatusCode)
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&result))
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
require.Len(t, result.Receivers, 1)
|
||||
require.Len(t, result.Receivers[0].Configs, 1)
|
||||
require.Equal(t, apimodels.TestReceiversResult{
|
||||
Receivers: []apimodels.TestReceiverResult{{
|
||||
Name: "receiver-1",
|
||||
Configs: []apimodels.TestReceiverConfigResult{{
|
||||
Name: "receiver-1",
|
||||
UID: result.Receivers[0].Configs[0].UID,
|
||||
Status: "failed",
|
||||
Error: "the receiver timed out: context deadline exceeded",
|
||||
}},
|
||||
}},
|
||||
NotifedAt: result.NotifedAt,
|
||||
}, result)
|
||||
|
||||
expectedJSON := fmt.Sprintf(`{
|
||||
"alert": {
|
||||
"annotations": {
|
||||
"summary": "Notification test"
|
||||
},
|
||||
"labels": {
|
||||
"alertname": "TestAlert",
|
||||
"instance": "Grafana"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"name": "receiver-1",
|
||||
"uid": "%s",
|
||||
"status": "failed",
|
||||
"error": "the receiver timed out: context deadline exceeded"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"notified_at": "%s"
|
||||
}`,
|
||||
result.Receivers[0].Configs[0].UID,
|
||||
result.NotifiedAt.Format(time.RFC3339Nano))
|
||||
require.JSONEq(t, expectedJSON, string(b))
|
||||
})
|
||||
|
||||
t.Run("assert multiple different errors returns 207 Multi Status", func(t *testing.T) {
|
||||
@@ -298,26 +348,26 @@ func TestTestReceivers(t *testing.T) {
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"uid":"",
|
||||
"name":"receiver-1",
|
||||
"type":"email",
|
||||
"disableResolveMessage":false,
|
||||
"settings":{},
|
||||
"secureFields":{}
|
||||
"uid": "",
|
||||
"name": "receiver-1",
|
||||
"type": "email",
|
||||
"disableResolveMessage": false,
|
||||
"settings": {},
|
||||
"secureFields": {}
|
||||
}
|
||||
]
|
||||
}, {
|
||||
"name":"receiver-2",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"uid":"",
|
||||
"name":"receiver-2",
|
||||
"type":"email",
|
||||
"disableResolveMessage":false,
|
||||
"settings":{
|
||||
"uid": "",
|
||||
"name": "receiver-2",
|
||||
"type": "email",
|
||||
"disableResolveMessage": false,
|
||||
"settings": {
|
||||
"addresses":"example@email.com"
|
||||
},
|
||||
"secureFields":{}
|
||||
"secureFields": {}
|
||||
}
|
||||
]
|
||||
}]
|
||||
@@ -333,32 +383,329 @@ func TestTestReceivers(t *testing.T) {
|
||||
})
|
||||
require.Equal(t, http.StatusMultiStatus, resp.StatusCode)
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&result))
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
require.Len(t, result.Receivers, 2)
|
||||
require.Len(t, result.Receivers[0].Configs, 1)
|
||||
require.Len(t, result.Receivers[1].Configs, 1)
|
||||
require.Equal(t, apimodels.TestReceiversResult{
|
||||
Receivers: []apimodels.TestReceiverResult{{
|
||||
Name: "receiver-1",
|
||||
Configs: []apimodels.TestReceiverConfigResult{{
|
||||
Name: "receiver-1",
|
||||
UID: result.Receivers[0].Configs[0].UID,
|
||||
Status: "failed",
|
||||
Error: "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings",
|
||||
}},
|
||||
}, {
|
||||
Name: "receiver-2",
|
||||
Configs: []apimodels.TestReceiverConfigResult{{
|
||||
Name: "receiver-2",
|
||||
UID: result.Receivers[1].Configs[0].UID,
|
||||
Status: "failed",
|
||||
Error: "the receiver timed out: context deadline exceeded",
|
||||
}},
|
||||
}},
|
||||
NotifedAt: result.NotifedAt,
|
||||
}, result)
|
||||
|
||||
expectedJSON := fmt.Sprintf(`{
|
||||
"alert": {
|
||||
"annotations": {
|
||||
"summary": "Notification test"
|
||||
},
|
||||
"labels": {
|
||||
"alertname": "TestAlert",
|
||||
"instance": "Grafana"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"name": "receiver-1",
|
||||
"uid": "%s",
|
||||
"status": "failed",
|
||||
"error": "the receiver is invalid: failed to validate receiver \"receiver-1\" of type \"email\": could not find addresses in settings"
|
||||
}
|
||||
]
|
||||
}, {
|
||||
"name":"receiver-2",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"name": "receiver-2",
|
||||
"uid": "%s",
|
||||
"status": "failed",
|
||||
"error": "the receiver timed out: context deadline exceeded"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"notified_at": "%s"
|
||||
}`,
|
||||
result.Receivers[0].Configs[0].UID,
|
||||
result.Receivers[1].Configs[0].UID,
|
||||
result.NotifiedAt.Format(time.RFC3339Nano))
|
||||
require.JSONEq(t, expectedJSON, string(b))
|
||||
})
|
||||
}
|
||||
|
||||
func TestTestReceiversAlertCustomization(t *testing.T) {
|
||||
t.Run("assert custom annotations and labels are sent", func(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
store.Bus = bus.GetBus()
|
||||
|
||||
createUser(t, store, models.CreateUserCommand{
|
||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||
Login: "grafana",
|
||||
Password: "password",
|
||||
})
|
||||
|
||||
oldEmailBus := bus.GetHandlerCtx("SendEmailCommandSync")
|
||||
mockEmails := &mockEmailHandler{}
|
||||
bus.AddHandlerCtx("", mockEmails.sendEmailCommandHandlerSync)
|
||||
t.Cleanup(func() {
|
||||
bus.AddHandlerCtx("", oldEmailBus)
|
||||
})
|
||||
|
||||
testReceiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers/test", grafanaListedAddr)
|
||||
// nolint
|
||||
resp := postRequest(t, testReceiversURL, `{
|
||||
"alert": {
|
||||
"annotations": {
|
||||
"annotation1": "value1"
|
||||
},
|
||||
"labels": {
|
||||
"label1": "value1"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"uid":"",
|
||||
"name":"receiver-1",
|
||||
"type":"email",
|
||||
"disableResolveMessage":false,
|
||||
"settings":{
|
||||
"addresses":"example@email.com"
|
||||
},
|
||||
"secureFields":{}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}`, http.StatusOK)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
require.Len(t, result.Receivers, 1)
|
||||
require.Len(t, result.Receivers[0].Configs, 1)
|
||||
|
||||
expectedJSON := fmt.Sprintf(`{
|
||||
"alert": {
|
||||
"annotations": {
|
||||
"annotation1": "value1",
|
||||
"summary": "Notification test"
|
||||
},
|
||||
"labels": {
|
||||
"alertname": "TestAlert",
|
||||
"instance": "Grafana",
|
||||
"label1": "value1"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"name": "receiver-1",
|
||||
"uid": "%s",
|
||||
"status": "ok"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"notified_at": "%s"
|
||||
}`,
|
||||
result.Receivers[0].Configs[0].UID,
|
||||
result.NotifiedAt.Format(time.RFC3339Nano))
|
||||
require.JSONEq(t, expectedJSON, string(b))
|
||||
|
||||
require.Len(t, mockEmails.emails, 1)
|
||||
require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To)
|
||||
})
|
||||
|
||||
t.Run("assert custom annotations can replace default annotations", func(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
store.Bus = bus.GetBus()
|
||||
|
||||
createUser(t, store, models.CreateUserCommand{
|
||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||
Login: "grafana",
|
||||
Password: "password",
|
||||
})
|
||||
|
||||
oldEmailBus := bus.GetHandlerCtx("SendEmailCommandSync")
|
||||
mockEmails := &mockEmailHandler{}
|
||||
bus.AddHandlerCtx("", mockEmails.sendEmailCommandHandlerSync)
|
||||
t.Cleanup(func() {
|
||||
bus.AddHandlerCtx("", oldEmailBus)
|
||||
})
|
||||
|
||||
testReceiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers/test", grafanaListedAddr)
|
||||
// nolint
|
||||
resp := postRequest(t, testReceiversURL, `{
|
||||
"alert": {
|
||||
"annotations": {
|
||||
"summary": "This is a custom annotation"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"uid":"",
|
||||
"name":"receiver-1",
|
||||
"type":"email",
|
||||
"disableResolveMessage":false,
|
||||
"settings":{
|
||||
"addresses":"example@email.com"
|
||||
},
|
||||
"secureFields":{}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}`, http.StatusOK)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
require.Len(t, result.Receivers, 1)
|
||||
require.Len(t, result.Receivers[0].Configs, 1)
|
||||
|
||||
expectedJSON := fmt.Sprintf(`{
|
||||
"alert": {
|
||||
"annotations": {
|
||||
"summary": "This is a custom annotation"
|
||||
},
|
||||
"labels": {
|
||||
"alertname": "TestAlert",
|
||||
"instance": "Grafana"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"name": "receiver-1",
|
||||
"uid": "%s",
|
||||
"status": "ok"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"notified_at": "%s"
|
||||
}`,
|
||||
result.Receivers[0].Configs[0].UID,
|
||||
result.NotifiedAt.Format(time.RFC3339Nano))
|
||||
require.JSONEq(t, expectedJSON, string(b))
|
||||
|
||||
require.Len(t, mockEmails.emails, 1)
|
||||
require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To)
|
||||
})
|
||||
|
||||
t.Run("assert custom labels can replace default label", func(t *testing.T) {
|
||||
// Setup Grafana and its Database
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
store.Bus = bus.GetBus()
|
||||
|
||||
createUser(t, store, models.CreateUserCommand{
|
||||
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||
Login: "grafana",
|
||||
Password: "password",
|
||||
})
|
||||
|
||||
oldEmailBus := bus.GetHandlerCtx("SendEmailCommandSync")
|
||||
mockEmails := &mockEmailHandler{}
|
||||
bus.AddHandlerCtx("", mockEmails.sendEmailCommandHandlerSync)
|
||||
t.Cleanup(func() {
|
||||
bus.AddHandlerCtx("", oldEmailBus)
|
||||
})
|
||||
|
||||
testReceiversURL := fmt.Sprintf("http://grafana:password@%s/api/alertmanager/grafana/config/api/v1/receivers/test", grafanaListedAddr)
|
||||
// nolint
|
||||
resp := postRequest(t, testReceiversURL, `{
|
||||
"alert": {
|
||||
"labels": {
|
||||
"alertname": "This is a custom label"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"uid":"",
|
||||
"name":"receiver-1",
|
||||
"type":"email",
|
||||
"disableResolveMessage":false,
|
||||
"settings":{
|
||||
"addresses":"example@email.com"
|
||||
},
|
||||
"secureFields":{}
|
||||
}
|
||||
]
|
||||
}]
|
||||
}`, http.StatusOK)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var result apimodels.TestReceiversResult
|
||||
require.NoError(t, json.Unmarshal(b, &result))
|
||||
require.Len(t, result.Receivers, 1)
|
||||
require.Len(t, result.Receivers[0].Configs, 1)
|
||||
|
||||
expectedJSON := fmt.Sprintf(`{
|
||||
"alert": {
|
||||
"annotations": {
|
||||
"summary": "Notification test"
|
||||
},
|
||||
"labels": {
|
||||
"alertname": "This is a custom label",
|
||||
"instance": "Grafana"
|
||||
}
|
||||
},
|
||||
"receivers": [{
|
||||
"name":"receiver-1",
|
||||
"grafana_managed_receiver_configs": [
|
||||
{
|
||||
"name": "receiver-1",
|
||||
"uid": "%s",
|
||||
"status": "ok"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"notified_at": "%s"
|
||||
}`,
|
||||
result.Receivers[0].Configs[0].UID,
|
||||
result.NotifiedAt.Format(time.RFC3339Nano))
|
||||
require.JSONEq(t, expectedJSON, string(b))
|
||||
|
||||
require.Len(t, mockEmails.emails, 1)
|
||||
require.Equal(t, []string{"example@email.com"}, mockEmails.emails[0].To)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -181,9 +181,9 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
|
||||
if err != nil {
|
||||
plog.Error("Exemplar query", query.Expr, "failed with", err)
|
||||
result.Responses[query.RefId] = backend.DataResponse{Error: err}
|
||||
continue
|
||||
} else {
|
||||
response[ExemplarQueryType] = exemplarResponse
|
||||
}
|
||||
response[ExemplarQueryType] = exemplarResponse
|
||||
}
|
||||
|
||||
frames, err := parseResponse(response, query)
|
||||
@@ -403,19 +403,28 @@ func matrixToDataFrames(matrix model.Matrix, query *PrometheusQuery) data.Frames
|
||||
|
||||
for _, v := range matrix {
|
||||
tags := make(map[string]string, len(v.Metric))
|
||||
timeVector := make([]time.Time, 0, len(v.Values))
|
||||
values := make([]float64, 0, len(v.Values))
|
||||
for k, v := range v.Metric {
|
||||
tags[string(k)] = string(v)
|
||||
}
|
||||
for _, k := range v.Values {
|
||||
timeVector = append(timeVector, time.Unix(k.Timestamp.Unix(), 0).UTC())
|
||||
values = append(values, float64(k.Value))
|
||||
|
||||
timeField := data.NewFieldFromFieldType(data.FieldTypeTime, len(v.Values))
|
||||
valueField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, len(v.Values))
|
||||
|
||||
for i, k := range v.Values {
|
||||
timeField.Set(i, time.Unix(k.Timestamp.Unix(), 0).UTC())
|
||||
value := float64(k.Value)
|
||||
if !math.IsNaN(value) {
|
||||
valueField.Set(i, &value)
|
||||
}
|
||||
}
|
||||
|
||||
name := formatLegend(v.Metric, query)
|
||||
frame := data.NewFrame(name,
|
||||
data.NewField("Time", nil, timeVector),
|
||||
data.NewField("Value", tags, values).SetConfig(&data.FieldConfig{DisplayNameFromDS: name}))
|
||||
timeField.Name = data.TimeSeriesTimeFieldName
|
||||
valueField.Name = data.TimeSeriesValueFieldName
|
||||
valueField.Config = &data.FieldConfig{DisplayNameFromDS: name}
|
||||
valueField.Labels = tags
|
||||
|
||||
frame := data.NewFrame(name, timeField, valueField)
|
||||
frame.Meta = &data.FrameMeta{
|
||||
Custom: map[string]string{
|
||||
"resultType": "matrix",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -440,6 +441,27 @@ func TestPrometheus_parseResponse(t *testing.T) {
|
||||
require.Equal(t, "UTC", testValue.(time.Time).Location().String())
|
||||
})
|
||||
|
||||
t.Run("matrix response with NaN value should be changed to null", func(t *testing.T) {
|
||||
value := make(map[PrometheusQueryType]interface{})
|
||||
value[RangeQueryType] = p.Matrix{
|
||||
&p.SampleStream{
|
||||
Metric: p.Metric{"app": "Application"},
|
||||
Values: []p.SamplePair{
|
||||
{Value: p.SampleValue(math.NaN()), Timestamp: 1000},
|
||||
},
|
||||
},
|
||||
}
|
||||
query := &PrometheusQuery{
|
||||
LegendFormat: "",
|
||||
}
|
||||
res, err := parseResponse(value, query)
|
||||
require.NoError(t, err)
|
||||
|
||||
var nilPointer *float64
|
||||
require.Equal(t, res[0].Fields[1].Name, "Value")
|
||||
require.Equal(t, res[0].Fields[1].At(0), nilPointer)
|
||||
})
|
||||
|
||||
t.Run("vector response should be parsed normally", func(t *testing.T) {
|
||||
value := make(map[PrometheusQueryType]interface{})
|
||||
value[RangeQueryType] = p.Vector{
|
||||
|
||||
@@ -68,6 +68,7 @@ export const TagFilter: FC<Props> = ({
|
||||
const value = tags.map((tag) => ({ value: tag, label: tag, count: 0 }));
|
||||
|
||||
const selectOptions = {
|
||||
allowCreateWhileLoading: true,
|
||||
allowCustomValue,
|
||||
formatCreateLabel,
|
||||
defaultOptions: true,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { FC, ReactNode, useCallback, useEffect, useState } from 'react';
|
||||
import React, { FC, ReactNode, useCallback, useEffect, useState, useRef } from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Counter, Icon, useStyles2 } from '@grafana/ui';
|
||||
import { PANEL_EDITOR_UI_STATE_STORAGE_KEY } from './state/reducers';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
|
||||
export interface OptionsPaneCategoryProps {
|
||||
id: string;
|
||||
@@ -18,28 +19,47 @@ export interface OptionsPaneCategoryProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const CATEGORY_PARAM_NAME = 'showCategory';
|
||||
|
||||
export const OptionsPaneCategory: FC<OptionsPaneCategoryProps> = React.memo(
|
||||
({ id, title, children, forceOpen, isOpenDefault, renderTitle, className, itemsCount, isNested = false }) => {
|
||||
const initialIsExpanded = isOpenDefault !== false;
|
||||
|
||||
const [savedState, setSavedState] = useLocalStorage(getOptionGroupStorageKey(id), {
|
||||
isExpanded: initialIsExpanded,
|
||||
});
|
||||
|
||||
// `savedState` can be undefined by typescript, so we have to handle that case
|
||||
const [isExpanded, setIsExpanded] = useState(savedState?.isExpanded ?? initialIsExpanded);
|
||||
const styles = useStyles2(getStyles);
|
||||
const [queryParams, updateQueryParams] = useQueryParams();
|
||||
const [isExpanded, setIsExpanded] = useState(savedState?.isExpanded ?? initialIsExpanded);
|
||||
const manualClickTime = useRef(0);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const isOpenFromUrl = queryParams[CATEGORY_PARAM_NAME] === id;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isExpanded && forceOpen && forceOpen > 0) {
|
||||
setIsExpanded(true);
|
||||
if (manualClickTime.current) {
|
||||
// ignore changes since the click handled the expected behavior
|
||||
if (Date.now() - manualClickTime.current < 200) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [forceOpen, isExpanded]);
|
||||
if (isOpenFromUrl || forceOpen) {
|
||||
if (!isExpanded) {
|
||||
setIsExpanded(true);
|
||||
}
|
||||
if (isOpenFromUrl) {
|
||||
ref.current?.scrollIntoView();
|
||||
}
|
||||
}
|
||||
}, [forceOpen, isExpanded, isOpenFromUrl]);
|
||||
|
||||
const onToggle = useCallback(() => {
|
||||
manualClickTime.current = Date.now();
|
||||
updateQueryParams({
|
||||
[CATEGORY_PARAM_NAME]: isExpanded ? undefined : id,
|
||||
});
|
||||
setSavedState({ isExpanded: !isExpanded });
|
||||
setIsExpanded(!isExpanded);
|
||||
}, [setSavedState, setIsExpanded, isExpanded]);
|
||||
}, [setSavedState, setIsExpanded, updateQueryParams, isExpanded, id]);
|
||||
|
||||
if (!renderTitle) {
|
||||
renderTitle = function defaultTitle(isExpanded: boolean) {
|
||||
@@ -78,6 +98,7 @@ export const OptionsPaneCategory: FC<OptionsPaneCategoryProps> = React.memo(
|
||||
className={boxStyles}
|
||||
data-testid="options-category"
|
||||
aria-label={selectors.components.OptionsGroup.group(id)}
|
||||
ref={ref}
|
||||
>
|
||||
<div className={headerStyles} onClick={onToggle} aria-label={selectors.components.OptionsGroup.toggle(id)}>
|
||||
<div className={cx(styles.toggle, 'editor-options-group-toggle')}>
|
||||
|
||||
@@ -21,6 +21,12 @@ standardFieldConfigEditorRegistry.setInit(getStandardFieldConfigs);
|
||||
|
||||
const mockStore = configureMockStore<any, any>();
|
||||
const OptionsPaneSelector = selectors.components.PanelEditor.OptionsPane;
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...(jest.requireActual('react-router-dom') as any),
|
||||
useLocation: () => ({
|
||||
pathname: 'localhost:3000/example/path',
|
||||
}),
|
||||
}));
|
||||
|
||||
class OptionsPaneOptionsTestScenario {
|
||||
onFieldConfigsChange = jest.fn();
|
||||
|
||||
@@ -309,12 +309,17 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
this.props.panel.updateFieldConfig(config);
|
||||
};
|
||||
|
||||
onPanelError = (message: string) => {
|
||||
if (this.state.errorMessage !== message) {
|
||||
this.setState({ errorMessage: message });
|
||||
onPanelError = (error: Error) => {
|
||||
const errorMessage = error.message || DEFAULT_PLUGIN_ERROR;
|
||||
if (this.state.errorMessage !== errorMessage) {
|
||||
this.setState({ errorMessage });
|
||||
}
|
||||
};
|
||||
|
||||
onPanelErrorRecover = () => {
|
||||
this.setState({ errorMessage: undefined });
|
||||
};
|
||||
|
||||
onAnnotationCreate = async (event: AnnotationEventUIModel) => {
|
||||
const isRegion = event.from !== event.to;
|
||||
const anno = {
|
||||
@@ -458,7 +463,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard, panel, isViewing, isEditing, width, height } = this.props;
|
||||
const { dashboard, panel, isViewing, isEditing, width, height, plugin } = this.props;
|
||||
const { errorMessage, data } = this.state;
|
||||
const { transparent } = panel;
|
||||
|
||||
@@ -489,10 +494,13 @@ export class PanelChrome extends PureComponent<Props, State> {
|
||||
alertState={alertState}
|
||||
data={data}
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
<ErrorBoundary
|
||||
dependencies={[data, plugin, panel.getOptions()]}
|
||||
onError={this.onPanelError}
|
||||
onRecover={this.onPanelErrorRecover}
|
||||
>
|
||||
{({ error }) => {
|
||||
if (error) {
|
||||
this.onPanelError(error.message || DEFAULT_PLUGIN_ERROR);
|
||||
return null;
|
||||
}
|
||||
return this.renderPanel(width, height);
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Logs } from './Logs';
|
||||
import { LogsCrossFadeTransition } from './utils/LogsCrossFadeTransition';
|
||||
import { LiveTailControls } from './useLiveTailControls';
|
||||
import { getFieldLinksForExplore } from './utils/links';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
interface LogsContainerProps extends PropsFromRedux {
|
||||
width: number;
|
||||
@@ -150,7 +151,11 @@ export class LogsContainer extends PureComponent<LogsContainerProps> {
|
||||
getFieldLinks={this.getFieldLinks}
|
||||
addResultsToCache={() => addResultsToCache(exploreId)}
|
||||
clearCache={() => clearCache(exploreId)}
|
||||
loadingLogsVolumeAvailable={hasLogsVolumeSupport(datasourceInstance) && !!logsVolumeDataProvider}
|
||||
loadingLogsVolumeAvailable={
|
||||
hasLogsVolumeSupport(datasourceInstance) &&
|
||||
!!logsVolumeDataProvider &&
|
||||
!config.featureToggles.autoLoadFullRangeLogsVolume
|
||||
}
|
||||
onClickLoadLogsVolume={() => loadLogsVolumeData(exploreId)}
|
||||
/>
|
||||
</Collapse>
|
||||
|
||||
@@ -3,6 +3,19 @@ import { noop } from 'lodash';
|
||||
import { shallow } from 'enzyme';
|
||||
import { SecondaryActions } from './SecondaryActions';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...((jest.requireActual('@grafana/runtime') as unknown) as object),
|
||||
config: {
|
||||
...((jest.requireActual('@grafana/runtime') as unknown) as any).config,
|
||||
featureToggles: {
|
||||
fullRangeLogsVolume: true,
|
||||
autoLoadFullRangeLogsVolume: false,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const addQueryRowButtonSelector = '[aria-label="Add row button"]';
|
||||
const richHistoryButtonSelector = '[aria-label="Rich history button"]';
|
||||
const queryInspectorButtonSelector = '[aria-label="Query inspector button"]';
|
||||
@@ -66,4 +79,8 @@ describe('SecondaryActions', () => {
|
||||
wrapper.find(queryInspectorButtonSelector).simulate('click');
|
||||
expect(onClickQueryInspector).toBeCalled();
|
||||
});
|
||||
|
||||
it('does not render load logs volume button when auto loading is enabled', () => {
|
||||
config.featureToggles.autoLoadFullRangeLogsVolume = true;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,6 +33,7 @@ import { reducerTester } from '../../../../test/core/redux/reducerTester';
|
||||
import { configureStore } from '../../../store/configureStore';
|
||||
import { setTimeSrv } from '../../dashboard/services/TimeSrv';
|
||||
import Mock = jest.Mock;
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...((jest.requireActual('@grafana/runtime') as unknown) as object),
|
||||
@@ -40,6 +41,7 @@ jest.mock('@grafana/runtime', () => ({
|
||||
...((jest.requireActual('@grafana/runtime') as unknown) as any).config,
|
||||
featureToggles: {
|
||||
fullRangeLogsVolume: true,
|
||||
autoLoadFullRangeLogsVolume: false,
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -322,6 +324,7 @@ describe('reducer', () => {
|
||||
describe('logs volume', () => {
|
||||
let dispatch: ThunkDispatch,
|
||||
getState: () => StoreState,
|
||||
unsubscribes: Function[],
|
||||
mockLogsVolumeDataProvider: () => Observable<DataQueryResponse>;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -352,11 +355,9 @@ describe('reducer', () => {
|
||||
|
||||
dispatch = store.dispatch;
|
||||
getState = store.getState;
|
||||
});
|
||||
|
||||
it('should cancel any unfinished logs volume queries', async () => {
|
||||
setupQueryResponse(getState());
|
||||
let unsubscribes: Function[] = [];
|
||||
unsubscribes = [];
|
||||
|
||||
mockLogsVolumeDataProvider = () => {
|
||||
return ({
|
||||
@@ -369,7 +370,9 @@ describe('reducer', () => {
|
||||
},
|
||||
} as unknown) as Observable<DataQueryResponse>;
|
||||
};
|
||||
});
|
||||
|
||||
it('should cancel any unfinished logs volume queries', async () => {
|
||||
await dispatch(runQueries(ExploreId.left));
|
||||
// no subscriptions created yet
|
||||
expect(unsubscribes).toHaveLength(0);
|
||||
@@ -391,5 +394,11 @@ describe('reducer', () => {
|
||||
expect(unsubscribes[0]).toBeCalled();
|
||||
expect(unsubscribes[1]).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should load logs volume after running the query', async () => {
|
||||
config.featureToggles.autoLoadFullRangeLogsVolume = true;
|
||||
await dispatch(runQueries(ExploreId.left));
|
||||
expect(unsubscribes).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -485,6 +485,9 @@ export const runQueries = (
|
||||
const { logsVolumeData, absoluteRange } = getState().explore[exploreId]!;
|
||||
if (!canReuseLogsVolumeData(logsVolumeData, queries, absoluteRange)) {
|
||||
dispatch(cleanLogsVolumeAction({ exploreId }));
|
||||
if (config.featureToggles.autoLoadFullRangeLogsVolume) {
|
||||
dispatch(loadLogsVolumeData(exploreId));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dispatch(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { appEvents } from 'app/core/core';
|
||||
import { useAsync } from 'react-use';
|
||||
import { getPanelOptionsWithDefaults, OptionDefaults } from '../../dashboard/state/getPanelOptionsWithDefaults';
|
||||
import { importPanelPlugin } from '../../plugins/importPanelPlugin';
|
||||
import { useTheme2 } from '@grafana/ui';
|
||||
import { ErrorBoundaryAlert, useTheme2 } from '@grafana/ui';
|
||||
|
||||
const defaultFieldConfig = { defaults: {}, overrides: [] };
|
||||
|
||||
@@ -51,24 +51,26 @@ export function PanelRenderer<P extends object = any, F extends object = any>(pr
|
||||
const PanelComponent = plugin.panel;
|
||||
|
||||
return (
|
||||
<PanelComponent
|
||||
id={1}
|
||||
data={dataWithOverrides}
|
||||
title={title}
|
||||
timeRange={dataWithOverrides.timeRange}
|
||||
timeZone={timeZone}
|
||||
options={optionsWithDefaults!.options}
|
||||
fieldConfig={localFieldConfig}
|
||||
transparent={false}
|
||||
width={width}
|
||||
height={height}
|
||||
renderCounter={0}
|
||||
replaceVariables={(str: string) => str}
|
||||
onOptionsChange={onOptionsChange}
|
||||
onFieldConfigChange={setFieldConfig}
|
||||
onChangeTimeRange={onChangeTimeRange}
|
||||
eventBus={appEvents}
|
||||
/>
|
||||
<ErrorBoundaryAlert dependencies={[plugin, data]}>
|
||||
<PanelComponent
|
||||
id={1}
|
||||
data={dataWithOverrides}
|
||||
title={title}
|
||||
timeRange={dataWithOverrides.timeRange}
|
||||
timeZone={timeZone}
|
||||
options={optionsWithDefaults!.options}
|
||||
fieldConfig={localFieldConfig}
|
||||
transparent={false}
|
||||
width={width}
|
||||
height={height}
|
||||
renderCounter={0}
|
||||
replaceVariables={(str: string) => str}
|
||||
onOptionsChange={onOptionsChange}
|
||||
onFieldConfigChange={setFieldConfig}
|
||||
onChangeTimeRange={onChangeTimeRange}
|
||||
eventBus={appEvents}
|
||||
/>
|
||||
</ErrorBoundaryAlert>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { LegendDisplayMode } from '@grafana/schema';
|
||||
import {
|
||||
ApplyFieldOverrideOptions,
|
||||
DataTransformerConfig,
|
||||
@@ -7,7 +6,7 @@ import {
|
||||
NavModelItem,
|
||||
PanelData,
|
||||
} from '@grafana/data';
|
||||
import { Table, TimeSeries } from '@grafana/ui';
|
||||
import { Table } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
@@ -16,6 +15,7 @@ import { PanelQueryRunner } from '../query/state/PanelQueryRunner';
|
||||
import { QueryGroupOptions } from 'app/types';
|
||||
import Page from '../../core/components/Page/Page';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { PanelRenderer } from '../panel/components/PanelRenderer';
|
||||
|
||||
interface State {
|
||||
queryRunner: PanelQueryRunner;
|
||||
@@ -66,12 +66,14 @@ export const TestStuffPage: FC = () => {
|
||||
{({ width }) => {
|
||||
return (
|
||||
<div>
|
||||
<TimeSeries
|
||||
<PanelRenderer
|
||||
title="Hello"
|
||||
pluginId="timeseries"
|
||||
width={width}
|
||||
height={300}
|
||||
frames={data.series}
|
||||
legend={{ displayMode: LegendDisplayMode.List, placement: 'bottom', calcs: [] }}
|
||||
timeRange={data.timeRange}
|
||||
data={data}
|
||||
options={{}}
|
||||
fieldConfig={{ defaults: {}, overrides: [] }}
|
||||
timeZone="browser"
|
||||
/>
|
||||
<Table data={data.series[0]} width={width} height={300} />
|
||||
|
||||
@@ -17,8 +17,6 @@ import { FixedSizeList } from 'react-window';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
|
||||
// Hard limit on labels to render
|
||||
const MAX_LABEL_COUNT = 10000;
|
||||
const MAX_VALUE_COUNT = 50000;
|
||||
const EMPTY_SELECTOR = '{}';
|
||||
const METRIC_LABEL = '__name__';
|
||||
const LIST_ITEM_SIZE = 25;
|
||||
@@ -321,12 +319,6 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
|
||||
const selectedLabels: string[] = lastUsedLabels;
|
||||
languageProvider.start().then(() => {
|
||||
let rawLabels: string[] = languageProvider.getLabelKeys();
|
||||
// TODO too-many-metrics
|
||||
if (rawLabels.length > MAX_LABEL_COUNT) {
|
||||
const error = `Too many labels found (showing only ${MAX_LABEL_COUNT} of ${rawLabels.length})`;
|
||||
rawLabels = rawLabels.slice(0, MAX_LABEL_COUNT);
|
||||
this.setState({ error });
|
||||
}
|
||||
// Get metrics
|
||||
this.fetchValues(METRIC_LABEL, EMPTY_SELECTOR);
|
||||
// Auto-select previously selected labels
|
||||
@@ -394,11 +386,6 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
|
||||
this.updateLabelState(name, { loading: false });
|
||||
return;
|
||||
}
|
||||
if (rawValues.length > MAX_VALUE_COUNT) {
|
||||
const error = `Too many values for ${name} (showing only ${MAX_VALUE_COUNT} of ${rawValues.length})`;
|
||||
rawValues = rawValues.slice(0, MAX_VALUE_COUNT);
|
||||
this.setState({ error });
|
||||
}
|
||||
const values: FacettableValue[] = [];
|
||||
const { metricsMetadata } = languageProvider;
|
||||
for (const labelValue of rawValues) {
|
||||
|
||||
@@ -570,6 +570,45 @@ describe('PrometheusDatasource', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyTemplateVariables', () => {
|
||||
it('should call replace function for legendFormat', () => {
|
||||
const query = {
|
||||
expr: 'test{job="bar"}',
|
||||
legendFormat: '$legend',
|
||||
refId: 'A',
|
||||
};
|
||||
const legend = 'baz';
|
||||
templateSrvStub.replace.mockReturnValue(legend);
|
||||
|
||||
const interpolatedQuery = ds.applyTemplateVariables(query, { legend: { text: legend, value: legend } });
|
||||
expect(interpolatedQuery.legendFormat).toBe(legend);
|
||||
});
|
||||
|
||||
it('should call replace function for expr', () => {
|
||||
const query = {
|
||||
expr: 'test{job="$job"}',
|
||||
refId: 'A',
|
||||
};
|
||||
const job = 'bar';
|
||||
templateSrvStub.replace.mockReturnValue(job);
|
||||
|
||||
const interpolatedQuery = ds.applyTemplateVariables(query, { job: { text: job, value: job } });
|
||||
expect(interpolatedQuery.expr).toBe(job);
|
||||
});
|
||||
|
||||
it('should not call replace function for interval', () => {
|
||||
const query = {
|
||||
expr: 'test{job="bar"}',
|
||||
interval: '$interval',
|
||||
refId: 'A',
|
||||
};
|
||||
const interval = '10s';
|
||||
templateSrvStub.replace.mockReturnValue(interval);
|
||||
|
||||
const interpolatedQuery = ds.applyTemplateVariables(query, { interval: { text: interval, value: interval } });
|
||||
expect(interpolatedQuery.interval).not.toBe(interval);
|
||||
});
|
||||
});
|
||||
describe('metricFindQuery', () => {
|
||||
beforeEach(() => {
|
||||
const query = 'query_result(topk(5,rate(http_request_duration_microseconds_count[$__interval])))';
|
||||
|
||||
@@ -894,6 +894,7 @@ export class PrometheusDatasource extends DataSourceWithBackend<PromQuery, PromO
|
||||
|
||||
return {
|
||||
...target,
|
||||
legendFormat: this.templateSrv.replace(target.legendFormat, variables),
|
||||
expr: this.templateSrv.replace(target.expr, variables, this.interpolateQueryExpr),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,18 +13,17 @@ export class DebugPanel extends Component<Props> {
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
|
||||
if (options.mode === DebugMode.Events) {
|
||||
return <EventBusLoggerPanel eventBus={this.props.eventBus} />;
|
||||
switch (options.mode) {
|
||||
case DebugMode.Events:
|
||||
return <EventBusLoggerPanel eventBus={this.props.eventBus} />;
|
||||
case DebugMode.Cursor:
|
||||
return <CursorView eventBus={this.props.eventBus} />;
|
||||
case DebugMode.State:
|
||||
return <StateView {...this.props} />;
|
||||
case DebugMode.ThrowError:
|
||||
throw new Error('I failed you and for that i am deeply sorry');
|
||||
default:
|
||||
return <RenderInfoViewer {...this.props} />;
|
||||
}
|
||||
|
||||
if (options.mode === DebugMode.Cursor) {
|
||||
return <CursorView eventBus={this.props.eventBus} />;
|
||||
}
|
||||
|
||||
if (options.mode === DebugMode.State) {
|
||||
return <StateView {...this.props} />;
|
||||
}
|
||||
|
||||
return <RenderInfoViewer {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { DebugMode, DebugPanelOptions } from './types';
|
||||
|
||||
export const plugin = new PanelPlugin<DebugPanelOptions>(DebugPanel).useFieldConfig().setPanelOptions((builder) => {
|
||||
builder
|
||||
.addRadio({
|
||||
.addSelect({
|
||||
path: 'mode',
|
||||
name: 'Mode',
|
||||
defaultValue: DebugMode.Render,
|
||||
@@ -14,7 +14,9 @@ export const plugin = new PanelPlugin<DebugPanelOptions>(DebugPanel).useFieldCon
|
||||
{ label: 'Render', value: DebugMode.Render },
|
||||
{ label: 'Events', value: DebugMode.Events },
|
||||
{ label: 'Cursor', value: DebugMode.Cursor },
|
||||
{ label: 'Cursor', value: DebugMode.Cursor },
|
||||
{ label: 'Share state', value: DebugMode.State },
|
||||
{ label: 'Throw error', value: DebugMode.ThrowError },
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ export enum DebugMode {
|
||||
Events = 'events',
|
||||
Cursor = 'cursor',
|
||||
State = 'State',
|
||||
ThrowError = 'ThrowError',
|
||||
}
|
||||
|
||||
export interface DebugPanelOptions {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import { StandardEditorProps, StandardEditorsRegistryItem } from '@grafana/data';
|
||||
import { ComparisonOperation, FeatureStyleConfig } from '../types';
|
||||
import { Button } from '@grafana/ui';
|
||||
import { DEFAULT_STYLE_RULE } from '../layers/data/geojsonMapper';
|
||||
import { StyleRuleEditor, StyleRuleEditorSettings } from './StyleRuleEditor';
|
||||
|
||||
export const GeomapStyleRulesEditor: FC<StandardEditorProps<FeatureStyleConfig[], any, any>> = (props) => {
|
||||
const { value, onChange, context } = props;
|
||||
|
||||
const OPTIONS = getComparisonOperatorOptions();
|
||||
|
||||
const onAddRule = useCallback(() => {
|
||||
onChange([...value, DEFAULT_STYLE_RULE]);
|
||||
}, [onChange, value]);
|
||||
|
||||
const onRuleChange = useCallback(
|
||||
(idx) => (style: FeatureStyleConfig | undefined) => {
|
||||
const copyStyles = [...value];
|
||||
if (style) {
|
||||
copyStyles[idx] = style;
|
||||
} else {
|
||||
//assume undefined is only returned on delete
|
||||
copyStyles.splice(idx, 1);
|
||||
}
|
||||
onChange(copyStyles);
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const styleOptions =
|
||||
value &&
|
||||
value.map((style, idx: number) => {
|
||||
const itemSettings: StandardEditorsRegistryItem<any, StyleRuleEditorSettings> = {
|
||||
settings: { options: OPTIONS },
|
||||
} as any;
|
||||
|
||||
return (
|
||||
<StyleRuleEditor
|
||||
value={style}
|
||||
onChange={onRuleChange(idx)}
|
||||
context={context}
|
||||
item={itemSettings}
|
||||
key={`${idx}-${style.rule}`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{styleOptions}
|
||||
<Button size="sm" icon="plus" onClick={onAddRule} variant="secondary" aria-label={'Add geomap style rule'}>
|
||||
{'Add style rule'}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getComparisonOperatorOptions = () => {
|
||||
const options = [];
|
||||
for (const value of Object.values(ComparisonOperation)) {
|
||||
options.push({ value: value, label: value });
|
||||
}
|
||||
return options;
|
||||
};
|
||||
@@ -0,0 +1,163 @@
|
||||
import React, { ChangeEvent, FC, useCallback } from 'react';
|
||||
import { GrafanaTheme2, SelectableValue, StandardEditorProps } from '@grafana/data';
|
||||
import { ComparisonOperation, FeatureStyleConfig } from '../types';
|
||||
import { Button, ColorPicker, InlineField, InlineFieldRow, Input, Select, useStyles2 } from '@grafana/ui';
|
||||
import { css } from '@emotion/css';
|
||||
import { NumberInput } from 'app/features/dimensions/editors/NumberInput';
|
||||
|
||||
export interface StyleRuleEditorSettings {
|
||||
options: SelectableValue[];
|
||||
}
|
||||
|
||||
export const StyleRuleEditor: FC<StandardEditorProps<FeatureStyleConfig, any, any, StyleRuleEditorSettings>> = (
|
||||
props
|
||||
) => {
|
||||
const { value, onChange, item } = props;
|
||||
const settings: StyleRuleEditorSettings = item.settings;
|
||||
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const LABEL_WIDTH = 10;
|
||||
|
||||
const onChangeComparisonProperty = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({
|
||||
...value,
|
||||
rule: {
|
||||
...value.rule,
|
||||
property: e.currentTarget.value,
|
||||
operation: value.rule?.operation ?? ComparisonOperation.EQ,
|
||||
value: value.rule?.value ?? '',
|
||||
},
|
||||
});
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const onChangeComparison = useCallback(
|
||||
(selection: SelectableValue) => {
|
||||
onChange({
|
||||
...value,
|
||||
rule: {
|
||||
...value.rule,
|
||||
operation: selection.value ?? ComparisonOperation.EQ,
|
||||
property: value.rule?.property ?? '',
|
||||
value: value.rule?.value ?? '',
|
||||
},
|
||||
});
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const onChangeComparisonValue = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange({
|
||||
...value,
|
||||
rule: {
|
||||
...value.rule,
|
||||
value: e.currentTarget.value,
|
||||
operation: value.rule?.operation ?? ComparisonOperation.EQ,
|
||||
property: value.rule?.property ?? '',
|
||||
},
|
||||
});
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const onChangeColor = useCallback(
|
||||
(c: string) => {
|
||||
onChange({ ...value, fillColor: c });
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const onChangeStrokeWidth = useCallback(
|
||||
(num: number | undefined) => {
|
||||
onChange({ ...value, strokeWidth: num ?? value.strokeWidth ?? 1 });
|
||||
},
|
||||
[onChange, value]
|
||||
);
|
||||
|
||||
const onDelete = useCallback(() => {
|
||||
onChange(undefined);
|
||||
}, [onChange]);
|
||||
|
||||
return (
|
||||
<div className={styles.rule}>
|
||||
<InlineFieldRow className={styles.row}>
|
||||
<InlineField label="Rule" labelWidth={LABEL_WIDTH} grow={true}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={'Feature property'}
|
||||
value={`${value?.rule?.property}`}
|
||||
onChange={onChangeComparisonProperty}
|
||||
aria-label={'Feature property'}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField className={styles.inline} grow={true}>
|
||||
<Select
|
||||
menuShouldPortal
|
||||
value={`${value?.rule?.operation}` ?? ComparisonOperation.EQ}
|
||||
options={settings.options}
|
||||
onChange={onChangeComparison}
|
||||
aria-label={'Comparison operator'}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField className={styles.inline} grow={true}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={'value'}
|
||||
value={`${value?.rule?.value}`}
|
||||
onChange={onChangeComparisonValue}
|
||||
aria-label={'Comparison value'}
|
||||
/>
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
<InlineFieldRow className={styles.row}>
|
||||
<InlineField label="Style" labelWidth={LABEL_WIDTH} className={styles.color}>
|
||||
<ColorPicker color={value?.fillColor} onChange={onChangeColor} />
|
||||
</InlineField>
|
||||
<InlineField label="Stroke" className={styles.inline} grow={true}>
|
||||
<NumberInput
|
||||
value={value?.strokeWidth ?? 1}
|
||||
min={1}
|
||||
max={20}
|
||||
step={0.5}
|
||||
aria-label={'Stroke width'}
|
||||
onChange={onChangeStrokeWidth}
|
||||
/>
|
||||
</InlineField>
|
||||
<Button
|
||||
size="md"
|
||||
icon="trash-alt"
|
||||
onClick={() => onDelete()}
|
||||
variant="secondary"
|
||||
aria-label={'Delete style rule'}
|
||||
className={styles.button}
|
||||
></Button>
|
||||
</InlineFieldRow>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
rule: css`
|
||||
margin-bottom: ${theme.spacing(1)};
|
||||
`,
|
||||
row: css`
|
||||
display: flex;
|
||||
margin-bottom: 4px;
|
||||
`,
|
||||
inline: css`
|
||||
margin-bottom: 0;
|
||||
margin-left: 4px;
|
||||
`,
|
||||
color: css`
|
||||
align-items: center;
|
||||
margin-bottom: 0;
|
||||
margin-right: 4px;
|
||||
`,
|
||||
button: css`
|
||||
margin-left: 4px;
|
||||
`,
|
||||
});
|
||||
@@ -7,15 +7,16 @@ import { Feature } from 'ol';
|
||||
import { Geometry } from 'ol/geom';
|
||||
import { getGeoMapStyle } from '../../utils/getGeoMapStyle';
|
||||
import { checkFeatureMatchesStyleRule } from '../../utils/checkFeatureMatchesStyleRule';
|
||||
import { FeatureStyleConfig } from '../../types';
|
||||
import { ComparisonOperation, FeatureStyleConfig } from '../../types';
|
||||
import { Stroke, Style } from 'ol/style';
|
||||
import { FeatureLike } from 'ol/Feature';
|
||||
import { GeomapStyleRulesEditor } from '../../editor/GeomapStyleRulesEditor';
|
||||
export interface GeoJSONMapperConfig {
|
||||
// URL for a geojson file
|
||||
src?: string;
|
||||
|
||||
// Styles that can be applied
|
||||
styles?: FeatureStyleConfig[];
|
||||
styles: FeatureStyleConfig[];
|
||||
}
|
||||
|
||||
const defaultOptions: GeoJSONMapperConfig = {
|
||||
@@ -23,6 +24,16 @@ const defaultOptions: GeoJSONMapperConfig = {
|
||||
styles: [],
|
||||
};
|
||||
|
||||
export const DEFAULT_STYLE_RULE: FeatureStyleConfig = {
|
||||
fillColor: '#1F60C4',
|
||||
strokeWidth: 1,
|
||||
rule: {
|
||||
property: '',
|
||||
operation: ComparisonOperation.EQ,
|
||||
value: '',
|
||||
},
|
||||
};
|
||||
|
||||
export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
|
||||
id: 'geojson-value-mapper',
|
||||
name: 'Map values to GeoJSON file',
|
||||
@@ -44,8 +55,8 @@ export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
|
||||
|
||||
const defaultStyle = new Style({
|
||||
stroke: new Stroke({
|
||||
color: '#1F60C4',
|
||||
width: 1,
|
||||
color: DEFAULT_STYLE_RULE.fillColor,
|
||||
width: DEFAULT_STYLE_RULE.strokeWidth,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -79,20 +90,27 @@ export const geojsonMapper: MapLayerRegistryItem<GeoJSONMapperConfig> = {
|
||||
|
||||
// Geojson source url
|
||||
registerOptionsUI: (builder) => {
|
||||
builder.addSelect({
|
||||
path: 'config.src',
|
||||
name: 'GeoJSON URL',
|
||||
settings: {
|
||||
options: [
|
||||
{ label: 'public/maps/countries.geojson', value: 'public/maps/countries.geojson' },
|
||||
{ label: 'public/maps/usa-states.geojson', value: 'public/maps/usa-states.geojson' },
|
||||
],
|
||||
allowCustomValue: true,
|
||||
},
|
||||
defaultValue: defaultOptions.src,
|
||||
});
|
||||
builder
|
||||
.addSelect({
|
||||
path: 'config.src',
|
||||
name: 'GeoJSON URL',
|
||||
settings: {
|
||||
options: [
|
||||
{ label: 'public/maps/countries.geojson', value: 'public/maps/countries.geojson' },
|
||||
{ label: 'public/maps/usa-states.geojson', value: 'public/maps/usa-states.geojson' },
|
||||
],
|
||||
allowCustomValue: true,
|
||||
},
|
||||
defaultValue: defaultOptions.src,
|
||||
})
|
||||
.addCustomEditor({
|
||||
id: 'config.styles',
|
||||
path: 'config.styles',
|
||||
name: 'Style Rules',
|
||||
editor: GeomapStyleRulesEditor,
|
||||
settings: {},
|
||||
defaultValue: [],
|
||||
});
|
||||
},
|
||||
|
||||
// fill in the default values
|
||||
defaultOptions,
|
||||
};
|
||||
|
||||
@@ -53,14 +53,25 @@ export const plugin = new PanelPlugin<GeomapPanelOptions>(GeomapPanel)
|
||||
);
|
||||
}
|
||||
|
||||
builder.addNestedOptions(
|
||||
getLayerEditor({
|
||||
category: ['Data layer'],
|
||||
path: 'layers[0]', // only one for now
|
||||
basemaps: false,
|
||||
current: context.options?.layers?.[0],
|
||||
})
|
||||
);
|
||||
let layerCount = context.options?.layers?.length;
|
||||
if (layerCount == null || layerCount < 1) {
|
||||
layerCount = 1;
|
||||
}
|
||||
|
||||
for (let i = 0; i < layerCount; i++) {
|
||||
let name = 'Data layer';
|
||||
if (i > 0) {
|
||||
name += ` (${i + 1})`;
|
||||
}
|
||||
builder.addNestedOptions(
|
||||
getLayerEditor({
|
||||
category: [name],
|
||||
path: `layers[${i}]`, // only one for now
|
||||
basemaps: false,
|
||||
current: context.options?.layers?.[i],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// The controls section
|
||||
category = ['Map controls'];
|
||||
|
||||
@@ -502,6 +502,12 @@ export function getAppRoutes(): RouteDescriptor[] {
|
||||
() => import(/* webpackChunkName: "BenchmarksPage"*/ 'app/features/sandbox/BenchmarksPage')
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/sandbox/test',
|
||||
component: SafeDynamicImport(
|
||||
() => import(/* webpackChunkName: "TestStuffPage"*/ 'app/features/sandbox/TestStuffPage')
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/dashboards/f/:uid/:slug/library-panels',
|
||||
component: SafeDynamicImport(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user