Merge remote-tracking branch 'origin/main' into grafana-git-ui-sync
This commit is contained in:
commit
e1f075bdde
99 files changed
+1622
-353
No files matched your search
+71
-41
@@ -18,7 +18,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
|
||||
@@ -69,7 +69,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- go install github.com/bazelbuild/buildtools/buildifier@latest
|
||||
@@ -112,7 +112,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -170,7 +170,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -309,7 +309,7 @@ steps:
|
||||
path: /github-app
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -339,6 +339,16 @@ steps:
|
||||
- yarn-install
|
||||
image: node:22-bookworm
|
||||
name: verify-i18n
|
||||
- commands:
|
||||
- yarn generate-apis
|
||||
- "\n file_diff=$(git diff ':!conf')\n if [ -n \"$file_diff\"
|
||||
]; then\n echo $file_diff\n echo \"\nAPI client
|
||||
generation has not been committed. Please run 'yarn generate-apis', commit the
|
||||
changes and push again.\"\n exit 1\n fi\n "
|
||||
depends_on:
|
||||
- yarn-install
|
||||
image: node:22-bookworm
|
||||
name: verify-api-clients
|
||||
trigger:
|
||||
event:
|
||||
- pull_request
|
||||
@@ -417,7 +427,7 @@ steps:
|
||||
path: /github-app
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- '# It is required that code generated from Thema/CUE be committed and in sync
|
||||
@@ -506,7 +516,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
|
||||
@@ -630,7 +640,7 @@ steps:
|
||||
path: /github-app
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
@@ -699,7 +709,7 @@ steps:
|
||||
-a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu
|
||||
-a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7
|
||||
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.5 --yarn-cache=$$YARN_CACHE_FOLDER
|
||||
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.3
|
||||
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.5
|
||||
--tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{
|
||||
.version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD
|
||||
> packages.txt
|
||||
@@ -756,7 +766,7 @@ steps:
|
||||
GF_APP_MODE: development
|
||||
GF_SERVER_HTTP_PORT: "3001"
|
||||
GF_SERVER_ROUTER_LOGGING: "1"
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: grafana-server
|
||||
- commands:
|
||||
- ./bin/build e2e-tests --port 3001 --suite dashboards-suite
|
||||
@@ -1100,7 +1110,7 @@ steps:
|
||||
name: compile-build-cmd
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- '# It is required that code generated from Thema/CUE be committed and in sync
|
||||
@@ -1266,7 +1276,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -1623,7 +1633,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -1694,7 +1704,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -1752,7 +1762,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -1782,6 +1792,16 @@ steps:
|
||||
- yarn-install
|
||||
image: node:22-bookworm
|
||||
name: verify-i18n
|
||||
- commands:
|
||||
- yarn generate-apis
|
||||
- "\n file_diff=$(git diff ':!conf')\n if [ -n \"$file_diff\"
|
||||
]; then\n echo $file_diff\n echo \"\nAPI client
|
||||
generation has not been committed. Please run 'yarn generate-apis', commit the
|
||||
changes and push again.\"\n exit 1\n fi\n "
|
||||
depends_on:
|
||||
- yarn-install
|
||||
image: node:22-bookworm
|
||||
name: verify-api-clients
|
||||
trigger:
|
||||
branch: main
|
||||
event:
|
||||
@@ -1818,7 +1838,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- '# It is required that code generated from Thema/CUE be committed and in sync
|
||||
@@ -1898,7 +1918,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
|
||||
@@ -1964,7 +1984,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -2039,7 +2059,7 @@ steps:
|
||||
path: /github-app
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
@@ -2107,7 +2127,7 @@ steps:
|
||||
-a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu
|
||||
-a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7
|
||||
-a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.5 --yarn-cache=$$YARN_CACHE_FOLDER
|
||||
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.3
|
||||
--build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.20.5
|
||||
--tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{
|
||||
.version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD
|
||||
> packages.txt
|
||||
@@ -2168,7 +2188,7 @@ steps:
|
||||
GF_APP_MODE: development
|
||||
GF_SERVER_HTTP_PORT: "3001"
|
||||
GF_SERVER_ROUTER_LOGGING: "1"
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: grafana-server
|
||||
- commands:
|
||||
- ./bin/build e2e-tests --port 3001 --suite dashboards-suite
|
||||
@@ -2585,7 +2605,7 @@ steps:
|
||||
name: compile-build-cmd
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- '# It is required that code generated from Thema/CUE be committed and in sync
|
||||
@@ -2830,7 +2850,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -2886,7 +2906,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -2916,6 +2936,16 @@ steps:
|
||||
- yarn-install
|
||||
image: node:22-bookworm
|
||||
name: verify-i18n
|
||||
- commands:
|
||||
- yarn generate-apis
|
||||
- "\n file_diff=$(git diff ':!conf')\n if [ -n \"$file_diff\"
|
||||
]; then\n echo $file_diff\n echo \"\nAPI client
|
||||
generation has not been committed. Please run 'yarn generate-apis', commit the
|
||||
changes and push again.\"\n exit 1\n fi\n "
|
||||
depends_on:
|
||||
- yarn-install
|
||||
image: node:22-bookworm
|
||||
name: verify-api-clients
|
||||
trigger:
|
||||
branch:
|
||||
- instant
|
||||
@@ -2950,7 +2980,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- '# It is required that code generated from Thema/CUE be committed and in sync
|
||||
@@ -3028,7 +3058,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- go build -o ./bin/build -ldflags '-extldflags -static' ./pkg/build/cmd
|
||||
@@ -3133,7 +3163,7 @@ steps:
|
||||
name: compile-build-cmd
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- '# It is required that code generated from Thema/CUE be committed and in sync
|
||||
@@ -3336,7 +3366,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
@@ -3468,7 +3498,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- mkdir -p bin
|
||||
@@ -3958,7 +3988,7 @@ steps:
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
ALPINE_BASE: alpine:3.20.3
|
||||
ALPINE_BASE: alpine:3.20.5
|
||||
CDN_DESTINATION:
|
||||
from_secret: rgm_cdn_destination
|
||||
DESTINATION:
|
||||
@@ -4033,7 +4063,7 @@ steps:
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
ALPINE_BASE: alpine:3.20.3
|
||||
ALPINE_BASE: alpine:3.20.5
|
||||
CDN_DESTINATION:
|
||||
from_secret: rgm_cdn_destination
|
||||
DESTINATION:
|
||||
@@ -4195,7 +4225,7 @@ steps:
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
ALPINE_BASE: alpine:3.20.3
|
||||
ALPINE_BASE: alpine:3.20.5
|
||||
CDN_DESTINATION:
|
||||
from_secret: rgm_cdn_destination
|
||||
DESTINATION:
|
||||
@@ -4297,7 +4327,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- yarn install --immutable || yarn install --immutable
|
||||
@@ -4351,7 +4381,7 @@ services: []
|
||||
steps:
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- '# It is required that code generated from Thema/CUE be committed and in sync
|
||||
@@ -4432,7 +4462,7 @@ steps:
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
ALPINE_BASE: alpine:3.20.3
|
||||
ALPINE_BASE: alpine:3.20.5
|
||||
CDN_DESTINATION:
|
||||
from_secret: rgm_cdn_destination
|
||||
DESTINATION:
|
||||
@@ -4576,7 +4606,7 @@ steps:
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
ALPINE_BASE: alpine:3.20.3
|
||||
ALPINE_BASE: alpine:3.20.5
|
||||
CDN_DESTINATION:
|
||||
from_secret: rgm_cdn_destination
|
||||
DESTINATION:
|
||||
@@ -4704,7 +4734,7 @@ steps:
|
||||
environment:
|
||||
_EXPERIMENTAL_DAGGER_CLOUD_TOKEN:
|
||||
from_secret: dagger_token
|
||||
ALPINE_BASE: alpine:3.20.3
|
||||
ALPINE_BASE: alpine:3.20.5
|
||||
CDN_DESTINATION:
|
||||
from_secret: rgm_cdn_destination
|
||||
DESTINATION:
|
||||
@@ -4843,7 +4873,7 @@ steps:
|
||||
name: grabpl
|
||||
- commands:
|
||||
- echo $DRONE_RUNNER_NAME
|
||||
image: alpine:3.20.3
|
||||
image: alpine:3.20.5
|
||||
name: identify-runner
|
||||
- commands:
|
||||
- '# It is required that code generated from Thema/CUE be committed and in sync
|
||||
@@ -5267,7 +5297,7 @@ steps:
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22-bookworm
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM google/cloud-sdk:431.0.0
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/grafana-ci-deploy:1.3.3
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine:3.20.3
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine:3.20.5
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM ubuntu:22.04
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM byrnedo/alpine-curl:0.1.8
|
||||
- trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM plugins/slack
|
||||
@@ -5305,7 +5335,7 @@ steps:
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL node:22-bookworm
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL grafana/grafana-ci-deploy:1.3.3
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL alpine:3.20.3
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL alpine:3.20.5
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL ubuntu:22.04
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL byrnedo/alpine-curl:0.1.8
|
||||
- trivy --exit-code 1 --severity HIGH,CRITICAL plugins/slack
|
||||
@@ -5569,6 +5599,6 @@ kind: secret
|
||||
name: gcr_credentials
|
||||
---
|
||||
kind: signature
|
||||
hmac: 154c1f072294cef09ba4f579eed30b1e29bc5c263897c862f4574432d321502c
|
||||
hmac: abd48dfdec08a719b414fba06b06dfc97560df31d81751cd833b5fdf5b7f9937
|
||||
|
||||
...
|
||||
@@ -15,9 +15,9 @@ title: Migrate from Grafana OSS/Enterprise to Grafana Cloud
|
||||
|
||||
When you decide to migrate from your self-managed Grafana instance to Grafana Cloud, you can benefit from the convenience of a managed observability platform, additional cloud-only features, and robust security. There are a couple of key approaches to help you transition to Grafana Cloud.
|
||||
|
||||
| Migration type | Tools used | Availability | Migratable resources |
|
||||
| :------------- | :-------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Manual | <ul><li>Command line utilities</li><li>The Grafana HTTP API</li></ul> | Generally available in all versions of Grafana OSS/Enterprise | The entire Grafana instance |
|
||||
| Automated | The Grafana Cloud Migration Assistant | Available in public preview from Grafana v11.2 using the `OnPremToCloudMigration` feature toggle. This toggle is enabled by default in Grafana v11.5 and later. | <ul><li>Dashboards</li><li>Folders</li>Data sources</li><li>App Plugins</li>Panel Plugins</li><li>Library Panels</li><li>Grafana Alerting resources</li></ul> |
|
||||
| Migration type | Tools used | Availability | Migratable resources |
|
||||
| :------------- | :-------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Manual | <ul><li>Command line utilities</li><li>The Grafana HTTP API</li></ul> | Generally available in all versions of Grafana OSS/Enterprise | The entire Grafana instance |
|
||||
| Automated | The Grafana Cloud Migration Assistant | Available in public preview from Grafana v11.2 using the `onPremToCloudMigrations` feature toggle. This toggle is enabled by default in Grafana v11.5 and later. | <ul><li>Dashboards</li><li>Folders</li><li>Data sources</li><li>App Plugins</li><li>Panel Plugins</li><li>Library Panels</li><li>Grafana Alerting resources</li></ul> |
|
||||
|
||||
Our detailed [migration guide](https://www.grafana.com/docs/grafana-cloud/account-management/migration-guide/manually-migrate-to-grafana-cloud/) explains the key steps and scripts to manually migrate your resources to Grafana Cloud, covering a comprehensive set of resources in your Grafana instance. Alternatively, the [Grafana Cloud Migration Assistant](https://www.grafana.com/docs/grafana-cloud/account-management/migration-guide/cloud-migration-assistant/), available in public preview in Grafana v11.2 and later, automates the migration process across a broad range of Grafana resources. You can use the migration assistant to migrate a large proportion of your Grafana resources and then, if needed, leverage the migration guide to migrate the rest.
|
||||
@@ -58,7 +58,7 @@ In Grafana Enterprise, the server administrator has access to the migration assi
|
||||
|
||||
### Grant access in Grafana Enterprise
|
||||
|
||||
{{< admonition type="impotrtant">}}
|
||||
{{< admonition type="important">}}
|
||||
You must [configure RBAC](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/configure-rbac/) before you can grant other administrators access to the Grafana Migration Assistant.
|
||||
{{< /admonition >}}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ In [Configure no data and error handling](ref:no-data-and-error-handling), you c
|
||||
|
||||
Note that `No Data` and `Error` states are supported only for Grafana-managed alert rules.
|
||||
|
||||
{{< figure src="/media/docs/alerting/alert-rule-configure-no-data-and-error.png" alt="A screenshot of the `Configure no data and error handling` option in Grafana Alerting." max-width="500px" >}}
|
||||
{{< figure src="/media/docs/alerting/alert-rule-configure-no-data-and-error-v2.png" alt="A screenshot of the `Configure no data and error handling` option in Grafana Alerting." max-width="500px" >}}
|
||||
|
||||
{{< docs/shared lookup="alerts/table-configure-no-data-and-error.md" source="grafana" version="<GRAFANA_VERSION>" >}}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@test-plugins/extensions-test-app",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack -c ./webpack.config.ts --env production",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@test-plugins/grafana-e2etest-datasource",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "webpack -c ./webpack.config.ts --env production",
|
||||
@@ -11,7 +11,7 @@
|
||||
"author": "Grafana",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "workspace:*",
|
||||
"@types/lodash": "4.17.7",
|
||||
"@types/node": "22.10.2",
|
||||
"@types/prismjs": "1.26.4",
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||
"npmClient": "yarn",
|
||||
"version": "11.5.0-pre"
|
||||
"version": "11.6.0-pre"
|
||||
}
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production nx exec --verbose -- webpack --config scripts/webpack/webpack.prod.js --progress",
|
||||
@@ -62,7 +62,7 @@
|
||||
"plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'",
|
||||
"plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100",
|
||||
"generate-icons": "nx run grafana-icons:generate",
|
||||
"process-specs": "npx tsx scripts/process-specs.ts",
|
||||
"process-specs": "node --experimental-strip-types scripts/process-specs.ts",
|
||||
"generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts"
|
||||
},
|
||||
"grafana": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "7.0.1",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@types/d3-interpolate": "^3.0.0",
|
||||
"@types/string-hash": "1.1.3",
|
||||
"d3-interpolate": "3.0.1",
|
||||
|
||||
@@ -251,6 +251,7 @@ export interface FeatureToggles {
|
||||
queryLibraryDashboards?: boolean;
|
||||
grafanaAdvisor?: boolean;
|
||||
elasticsearchImprovedParsing?: boolean;
|
||||
exploreMetricsUseExternalAppPlugin?: boolean;
|
||||
datasourceConnectionsTab?: boolean;
|
||||
fetchRulesUsingPost?: boolean;
|
||||
alertingAlertmanagerExtraDedupStage?: boolean;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@grafana/eslint-plugin",
|
||||
"description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"main": "./index.cjs",
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/flamegraph",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"description": "Grafana flamegraph visualization component",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -44,8 +44,8 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"@leeoniya/ufuzzy": "1.0.18",
|
||||
"d3": "^7.8.5",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@grafana/saga-icons",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"private": true,
|
||||
"description": "Icons for Grafana",
|
||||
"author": "Grafana Labs",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"name": "@grafana/o11y-ds-frontend",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"description": "Library to manage traces in Grafana.",
|
||||
"sideEffects": false,
|
||||
"repository": {
|
||||
@@ -18,12 +18,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/plugin-ui": "0.10.1",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"react-select": "5.10.0",
|
||||
"react-use": "17.6.0",
|
||||
"rxjs": "7.8.1",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@grafana/plugin-configs",
|
||||
"description": "Shared dependencies and files for core plugins",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "AGPL-3.0-only",
|
||||
"name": "@grafana/prometheus",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"description": "Grafana Prometheus Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
@@ -38,13 +38,13 @@
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@floating-ui/react": "0.27.3",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/faro-web-sdk": "1.12.3",
|
||||
"@grafana/llm": "0.12.0",
|
||||
"@grafana/plugin-ui": "0.10.1",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"@hello-pangea/dnd": "17.0.0",
|
||||
"@leeoniya/ufuzzy": "1.0.18",
|
||||
"@lezer/common": "1.2.3",
|
||||
@@ -77,7 +77,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@emotion/eslint-plugin": "11.12.0",
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/tsconfig": "^2.0.0",
|
||||
"@rollup/plugin-image": "3.0.3",
|
||||
"@rollup/plugin-node-resolve": "16.0.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -37,11 +37,11 @@
|
||||
"postpack": "mv package.json.bak package.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/faro-web-sdk": "^1.3.6",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"history": "4.10.1",
|
||||
"lodash": "4.17.21",
|
||||
"react-use": "17.6.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/schema",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"description": "Grafana Schema Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options {
|
||||
limit: number;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip, common.OptionsWithTextFormatting {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options extends common.OptionsWithLegend, common.SingleStatBaseOptions {
|
||||
displayMode: common.BarGaugeDisplayMode;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export enum VizDisplayMode {
|
||||
Candles = 'candles',
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as ui from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export enum HorizontalConstraint {
|
||||
Center = 'center',
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface MetricStat {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options {
|
||||
selectedSeries: number;
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export type UpdateConfig = {
|
||||
render: boolean,
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested);
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options extends common.SingleStatBaseOptions {
|
||||
minVizHeight: number;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as ui from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options {
|
||||
basemap: ui.MapLayerOptions;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as ui from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
/**
|
||||
* Controls the color mode of the heatmap
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options {
|
||||
dedupStrategy: common.LogsDedupStrategy;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export enum QueryEditorMode {
|
||||
Builder = 'builder',
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface ArcOption {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
/**
|
||||
* Select the pie chart display style.
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options extends common.SingleStatBaseOptions {
|
||||
colorMode: common.BigValueColorMode;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as ui from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as ui from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as ui from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
//
|
||||
// Run 'make gen-cue' from repository root to regenerate.
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export enum TextMode {
|
||||
Code = 'code',
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export interface Options extends common.OptionsWithTimezones {
|
||||
legend: common.VizLegendOptions;
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
/**
|
||||
* Identical to timeseries... except it does not have timezone settings
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
|
||||
import * as common from '@grafana/schema';
|
||||
|
||||
export const pluginVersion = "11.5.0-pre";
|
||||
export const pluginVersion = "11.6.0-pre";
|
||||
|
||||
export enum PointShape {
|
||||
Circle = 'circle',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": true,
|
||||
"name": "@grafana/sql",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/grafana/grafana.git",
|
||||
@@ -15,11 +15,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/plugin-ui": "0.10.1",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"@react-awesome-query-builder/ui": "6.6.4",
|
||||
"immutable": "5.0.3",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -50,10 +50,10 @@
|
||||
"@emotion/react": "11.14.0",
|
||||
"@emotion/serialize": "1.3.3",
|
||||
"@floating-ui/react": "0.27.3",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/faro-web-sdk": "^1.3.6",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@hello-pangea/dnd": "17.0.0",
|
||||
"@leeoniya/ufuzzy": "1.0.18",
|
||||
"@monaco-editor/react": "4.6.0",
|
||||
|
||||
@@ -2,6 +2,7 @@ package ossaccesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
@@ -106,6 +107,17 @@ func ProvideFolderPermissions(
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
// if the folder is not found, this may be on the create path,
|
||||
// where the write path to legacy will then go through the read
|
||||
// path and try to read from both legacy & unified before it exists on both
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) && errors.Is(err, dashboards.ErrFolderNotFound) {
|
||||
_, err = folderService.GetLegacy(ctx, &folder.GetFolderQuery{
|
||||
UID: &resourceID,
|
||||
OrgID: orgID,
|
||||
SignedInUser: ident,
|
||||
})
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -912,7 +912,7 @@ var (
|
||||
Stage: FeatureStageGeneralAvailability,
|
||||
Expression: "true", // enabled by default
|
||||
FrontendOnly: true,
|
||||
Owner: grafanaDashboardsSquad,
|
||||
Owner: grafanaObservabilityMetricsSquad,
|
||||
},
|
||||
{
|
||||
Name: "alertingSimplifiedRouting",
|
||||
@@ -1742,6 +1742,15 @@ var (
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: awsDatasourcesSquad,
|
||||
},
|
||||
{
|
||||
Name: "exploreMetricsUseExternalAppPlugin",
|
||||
Description: "Use the externalized Metrics Drilldown (formerly known as Explore Metrics) app plugin",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaObservabilityMetricsSquad,
|
||||
FrontendOnly: true,
|
||||
RequiresRestart: true,
|
||||
HideFromDocs: true,
|
||||
},
|
||||
{
|
||||
Name: "datasourceConnectionsTab",
|
||||
Description: "Shows defined connections for a data source in the plugins detail page",
|
||||
|
||||
@@ -120,7 +120,7 @@ pdfTables,preview,@grafana/sharing-squad,false,false,false
|
||||
ssoSettingsApi,GA,@grafana/identity-access-team,false,false,false
|
||||
canvasPanelPanZoom,preview,@grafana/dataviz-squad,false,false,true
|
||||
logsInfiniteScrolling,GA,@grafana/observability-logs,false,false,true
|
||||
exploreMetrics,GA,@grafana/dashboards-squad,false,false,true
|
||||
exploreMetrics,GA,@grafana/observability-metrics,false,false,true
|
||||
alertingSimplifiedRouting,GA,@grafana/alerting-squad,false,false,false
|
||||
logRowsPopoverMenu,GA,@grafana/observability-logs,false,false,true
|
||||
pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||
@@ -232,6 +232,7 @@ ABTestFeatureToggleB,experimental,@grafana/sharing-squad,false,false,false
|
||||
queryLibraryDashboards,experimental,@grafana/grafana-frontend-platform,false,false,false
|
||||
grafanaAdvisor,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||
elasticsearchImprovedParsing,experimental,@grafana/aws-datasources,false,false,false
|
||||
exploreMetricsUseExternalAppPlugin,experimental,@grafana/observability-metrics,false,true,true
|
||||
datasourceConnectionsTab,experimental,@grafana/plugins-platform-backend,false,false,true
|
||||
fetchRulesUsingPost,experimental,@grafana/alerting-squad,false,false,false
|
||||
alertingAlertmanagerExtraDedupStage,experimental,@grafana/alerting-squad,false,true,false
|
||||
|
||||
|
@@ -939,6 +939,10 @@ const (
|
||||
// Enables less memory intensive Elasticsearch result parsing
|
||||
FlagElasticsearchImprovedParsing = "elasticsearchImprovedParsing"
|
||||
|
||||
// FlagExploreMetricsUseExternalAppPlugin
|
||||
// Use the externalized Metrics Drilldown (formerly known as Explore Metrics) app plugin
|
||||
FlagExploreMetricsUseExternalAppPlugin = "exploreMetricsUseExternalAppPlugin"
|
||||
|
||||
// FlagDatasourceConnectionsTab
|
||||
// Shows defined connections for a data source in the plugins detail page
|
||||
FlagDatasourceConnectionsTab = "datasourceConnectionsTab"
|
||||
|
||||
@@ -1529,16 +1529,16 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "exploreMetrics",
|
||||
"resourceVersion": "1720021873452",
|
||||
"resourceVersion": "1737658563230",
|
||||
"creationTimestamp": "2024-04-09T18:15:18Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC"
|
||||
"grafana.app/updatedTimestamp": "2025-01-23 18:56:03.23086 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Enables the new Explore Metrics core app",
|
||||
"stage": "GA",
|
||||
"codeowner": "@grafana/dashboards-squad",
|
||||
"codeowner": "@grafana/observability-metrics",
|
||||
"frontend": true,
|
||||
"expression": "true"
|
||||
}
|
||||
@@ -1557,6 +1557,24 @@
|
||||
"hideFromDocs": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "exploreMetricsUseExternalAppPlugin",
|
||||
"resourceVersion": "1738596266973",
|
||||
"creationTimestamp": "2025-01-21T23:24:50Z",
|
||||
"annotations": {
|
||||
"grafana.app/updatedTimestamp": "2025-02-03 15:24:26.973231 +0000 UTC"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"description": "Use the externalized Metrics Drilldown (formerly known as Explore Metrics) app plugin",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/observability-metrics",
|
||||
"frontend": true,
|
||||
"requiresRestart": true,
|
||||
"hideFromDocs": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "expressionParser",
|
||||
|
||||
@@ -36,11 +36,13 @@ import (
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/search"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// interface to allow for testing
|
||||
type folderK8sHandler interface {
|
||||
getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool)
|
||||
getDashboardClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool)
|
||||
getNamespace(orgID int64) string
|
||||
getSearcher(ctx context.Context) resource.ResourceClient
|
||||
}
|
||||
@@ -688,7 +690,7 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
|
||||
|
||||
// if dashboard restore is on we don't delete public dashboards, the hard delete will take care of it later
|
||||
if !s.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) {
|
||||
// We need a list of dashboard uids inside the folder to delete related public dashboards
|
||||
// We need a list of dashboard uids inside the folder to delete related dashboards & public dashboards
|
||||
var dashboardUIDs []string
|
||||
// we cannot use the dashboard service directly due to circular dependencies,
|
||||
// so either use the search client if the feature is enabled or use the dashboard store
|
||||
@@ -722,10 +724,17 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err)
|
||||
}
|
||||
|
||||
dashboardUIDs = make([]string, len(hits.Hits))
|
||||
k8sDeleteClient, created := s.k8sclient.getDashboardClient(ctx, cmd.OrgID)
|
||||
if !created {
|
||||
return folder.ErrInternal.Errorf("failed to create client to get dashboards")
|
||||
}
|
||||
for i, dashboard := range hits.Hits {
|
||||
dashboardUIDs[i] = dashboard.Name
|
||||
err = k8sDeleteClient.Delete(ctx, dashboard.Name, metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dashes, err := s.dashboardStore.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{SignedInUser: cmd.SignedInUser, FolderUIDs: folders, OrgId: cmd.OrgID})
|
||||
@@ -735,6 +744,13 @@ func (s *Service) deleteFromApiServer(ctx context.Context, cmd *folder.DeleteFol
|
||||
dashboardUIDs = make([]string, len(dashes))
|
||||
for i, dashboard := range dashes {
|
||||
dashboardUIDs[i] = dashboard.UID
|
||||
err = s.dashboardStore.DeleteDashboard(ctx, &dashboards.DeleteDashboardCommand{
|
||||
UID: dashboard.UID,
|
||||
OrgID: cmd.OrgID,
|
||||
})
|
||||
if err != nil {
|
||||
return folder.ErrInternal.Errorf("failed to delete child dashboard: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Delete all public dashboards in the folders
|
||||
@@ -986,6 +1002,20 @@ func (fk8s *foldk8sHandler) getClient(ctx context.Context, orgID int64) (dynamic
|
||||
return dyn.Resource(fk8s.gvr).Namespace(fk8s.getNamespace(orgID)), true
|
||||
}
|
||||
|
||||
func (fk8s *foldk8sHandler) getDashboardClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool) {
|
||||
cfg := fk8s.restConfigProvider(ctx)
|
||||
if cfg == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
dyn, err := dynamic.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return dyn.Resource(dashboardv0.DashboardResourceInfo.GroupVersionResource()).Namespace(fk8s.getNamespace(orgID)), true
|
||||
}
|
||||
|
||||
func (fk8s *foldk8sHandler) getNamespace(orgID int64) string {
|
||||
return fk8s.namespacer(orgID)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/dynamic"
|
||||
clientrest "k8s.io/client-go/rest"
|
||||
|
||||
@@ -682,6 +683,46 @@ func (r resourceClientMock) Search(ctx context.Context, in *resource.ResourceSea
|
||||
}, nil
|
||||
}
|
||||
|
||||
if len(in.Options.Fields) > 0 &&
|
||||
in.Options.Fields[0].Key == resource.SEARCH_FIELD_FOLDER &&
|
||||
in.Options.Fields[0].Operator == "in" &&
|
||||
len(in.Options.Fields[0].Values) > 0 {
|
||||
rows := []*resource.ResourceTableRow{}
|
||||
for i, row := range in.Options.Fields[0].Values {
|
||||
rows = append(rows, &resource.ResourceTableRow{
|
||||
Key: &resource.ResourceKey{
|
||||
Name: row,
|
||||
Resource: "folders",
|
||||
},
|
||||
Cells: [][]byte{
|
||||
[]byte(fmt.Sprintf("%d", i)), // set legacy id as the row id
|
||||
[]byte(fmt.Sprintf("folder%d", i)), // set title as folder + row id
|
||||
[]byte(""),
|
||||
},
|
||||
})
|
||||
}
|
||||
return &resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{
|
||||
Columns: []*resource.ResourceTableColumnDefinition{
|
||||
{
|
||||
Name: "_id",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "title",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
{
|
||||
Name: "folder",
|
||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||
},
|
||||
},
|
||||
Rows: rows,
|
||||
},
|
||||
TotalHits: int64(len(rows)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// not found
|
||||
return &resource.ResourceSearchResponse{
|
||||
Results: &resource.ResourceTable{},
|
||||
@@ -716,6 +757,11 @@ func (m *mockFoldersK8sCli) getClient(ctx context.Context, orgID int64) (dynamic
|
||||
return args.Get(0).(dynamic.ResourceInterface), args.Bool(1)
|
||||
}
|
||||
|
||||
func (m *mockFoldersK8sCli) getDashboardClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool) {
|
||||
args := m.Called(ctx, orgID)
|
||||
return args.Get(0).(dynamic.ResourceInterface), args.Bool(1)
|
||||
}
|
||||
|
||||
func (m *mockFoldersK8sCli) getNamespace(orgID int64) string {
|
||||
if orgID == 1 {
|
||||
return "default"
|
||||
@@ -834,3 +880,105 @@ func TestSearchFoldersFromApiServer(t *testing.T) {
|
||||
require.Equal(t, expectedResult, result)
|
||||
})
|
||||
}
|
||||
|
||||
type mockDashboardCli struct {
|
||||
mock.Mock
|
||||
dynamic.ResourceInterface
|
||||
}
|
||||
|
||||
func (c *mockDashboardCli) Delete(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error {
|
||||
args := c.Called(ctx, name, options)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestDeleteFoldersFromApiServer(t *testing.T) {
|
||||
fakeK8sClient := new(mockFoldersK8sCli)
|
||||
fakeFolderStore := folder.NewFakeStore()
|
||||
dashboardStore := dashboards.NewFakeDashboardStore(t)
|
||||
publicDashboardFakeService := publicdashboards.NewFakePublicDashboardServiceWrapper(t)
|
||||
service := Service{
|
||||
k8sclient: fakeK8sClient,
|
||||
unifiedStore: fakeFolderStore,
|
||||
dashboardStore: dashboardStore,
|
||||
publicDashboardService: publicDashboardFakeService,
|
||||
registry: make(map[string]folder.RegistryService),
|
||||
features: featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2),
|
||||
}
|
||||
fakeK8sClient.On("getSearcher", mock.Anything).Return(fakeK8sClient)
|
||||
user := &user.SignedInUser{OrgID: 1}
|
||||
ctx := identity.WithRequester(context.Background(), user)
|
||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{
|
||||
CanSaveValue: true,
|
||||
CanViewValue: true,
|
||||
})
|
||||
db, cfg := sqlstore.InitTestDB(t)
|
||||
|
||||
alertingStore := ngstore.DBstore{
|
||||
SQLStore: db,
|
||||
Cfg: cfg.UnifiedAlerting,
|
||||
Logger: log.New("test-alerting-store"),
|
||||
AccessControl: actest.FakeAccessControl{ExpectedEvaluate: true},
|
||||
}
|
||||
require.NoError(t, service.RegisterService(alertingStore))
|
||||
|
||||
t.Run("Should delete folder", func(t *testing.T) {
|
||||
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{}, nil).Once()
|
||||
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
|
||||
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
|
||||
UID: "uid",
|
||||
OrgID: 1,
|
||||
SignedInUser: user,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Should delete dashboards and public dashboards within the folder", func(t *testing.T) {
|
||||
dashboardStore.On("FindDashboards", mock.Anything, mock.Anything).Return([]dashboards.DashboardSearchProjection{
|
||||
{
|
||||
UID: "test",
|
||||
OrgID: 1,
|
||||
},
|
||||
{
|
||||
UID: "test2",
|
||||
OrgID: 1,
|
||||
},
|
||||
}, nil).Once()
|
||||
dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{
|
||||
UID: "test",
|
||||
OrgID: 1,
|
||||
}).Return(nil).Once()
|
||||
dashboardStore.On("DeleteDashboard", mock.Anything, &dashboards.DeleteDashboardCommand{
|
||||
UID: "test2",
|
||||
OrgID: 1,
|
||||
}).Return(nil).Once()
|
||||
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"test", "test2"}).Return(nil).Once()
|
||||
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
|
||||
UID: "uid",
|
||||
OrgID: 1,
|
||||
SignedInUser: user,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
dashboardStore.AssertExpectations(t)
|
||||
publicDashboardFakeService.AssertExpectations(t)
|
||||
})
|
||||
|
||||
// enable k8s ff for dashboards, retest
|
||||
service.features = featuremgmt.WithFeatures(featuremgmt.FlagKubernetesFoldersServiceV2, featuremgmt.FlagKubernetesCliDashboards)
|
||||
|
||||
t.Run("Should delete dashboards and public dashboards within the folder through k8s if the ff is enabled", func(t *testing.T) {
|
||||
dashboardK8sCli := mockDashboardCli{}
|
||||
dashboardK8sCli.On("Delete", mock.Anything, "uid1", mock.Anything, mock.Anything).Return(nil).Once()
|
||||
fakeK8sClient.On("getDashboardClient", mock.Anything, mock.Anything).Return(&dashboardK8sCli, true)
|
||||
fakeK8sClient.On("getSearcher", mock.Anything).Return(fakeK8sClient)
|
||||
publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, int64(1), []string{"uid1"}).Return(nil).Once()
|
||||
err := service.deleteFromApiServer(ctx, &folder.DeleteFolderCommand{
|
||||
UID: "uid1",
|
||||
OrgID: 1,
|
||||
SignedInUser: user,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
dashboardStore.AssertExpectations(t)
|
||||
publicDashboardFakeService.AssertExpectations(t)
|
||||
dashboardK8sCli.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
@@ -286,9 +286,9 @@ func (s *ServiceImpl) readNavigationSettings() {
|
||||
"grafana-k8s-app": {SectionID: navtree.NavIDInfrastructure, SortWeight: 1, Text: "Kubernetes"},
|
||||
"grafana-dbo11y-app": {SectionID: navtree.NavIDInfrastructure, SortWeight: 2, Text: "Databases"},
|
||||
"grafana-app-observability-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightApplication, Text: "Application", Icon: "graph-bar"},
|
||||
"grafana-lokiexplore-app": {SectionID: navtree.NavIDExplore, SortWeight: 1, Text: "Logs"},
|
||||
"grafana-exploretraces-app": {SectionID: navtree.NavIDExplore, SortWeight: 2, Text: "Traces"},
|
||||
"grafana-pyroscope-app": {SectionID: navtree.NavIDExplore, SortWeight: 3, Text: "Profiles"},
|
||||
"grafana-lokiexplore-app": {SectionID: navtree.NavIDExplore, SortWeight: 2, Text: "Logs"},
|
||||
"grafana-exploretraces-app": {SectionID: navtree.NavIDExplore, SortWeight: 3, Text: "Traces"},
|
||||
"grafana-pyroscope-app": {SectionID: navtree.NavIDExplore, SortWeight: 4, Text: "Profiles"},
|
||||
"grafana-kowalski-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightFrontend, Text: "Frontend", Icon: "frontend-observability"},
|
||||
"grafana-synthetic-monitoring-app": {SectionID: navtree.NavIDTestingAndSynthetics, SortWeight: 2, Text: "Synthetics"},
|
||||
"grafana-irm-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 1, Text: "IRM"},
|
||||
@@ -309,6 +309,10 @@ func (s *ServiceImpl) readNavigationSettings() {
|
||||
"grafana-csp-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightCloudServiceProviders, Icon: "cloud"},
|
||||
}
|
||||
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagExploreMetricsUseExternalAppPlugin) {
|
||||
s.navigationAppConfig["grafana-metricsdrilldown-app"] = NavigationAppConfig{SectionID: navtree.NavIDExplore, SortWeight: 1, Text: "Metrics"}
|
||||
}
|
||||
|
||||
s.navigationAppPathConfig = map[string]NavigationAppConfig{
|
||||
"/a/grafana-auth-app": {SectionID: navtree.NavIDCfgAccess, SortWeight: 2},
|
||||
}
|
||||
|
||||
@@ -562,7 +562,7 @@ func (s *ServiceImpl) buildDataConnectionsNavLink(c *contextmodel.ReqContext) *n
|
||||
|
||||
func (s *ServiceImpl) buildExploreNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink {
|
||||
exploreChildNavs := []*navtree.NavLink{}
|
||||
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagExploreMetrics) {
|
||||
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagExploreMetrics) && !s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagExploreMetricsUseExternalAppPlugin) {
|
||||
exploreChildNavs = append(exploreChildNavs, &navtree.NavLink{
|
||||
Text: "Metrics",
|
||||
SubTitle: "Queryless exploration of your metrics",
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -330,6 +331,30 @@ func (srv RulerSrv) RouteGetRuleByUID(c *contextmodel.ReqContext, ruleUID string
|
||||
return response.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (srv RulerSrv) RouteGetRuleVersionsByUID(c *contextmodel.ReqContext, ruleUID string) response.Response {
|
||||
ctx := c.Req.Context()
|
||||
// make sure the user has access to the current version of the rule. Also, check if it exists
|
||||
_, err := srv.getAuthorizedRuleByUid(ctx, c, ruleUID)
|
||||
if err != nil {
|
||||
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
|
||||
return response.Empty(http.StatusNotFound)
|
||||
}
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get rule by UID", err)
|
||||
}
|
||||
|
||||
rules, err := srv.store.GetAlertRuleVersions(ctx, ngmodels.AlertRuleKey{OrgID: c.OrgID, UID: ruleUID})
|
||||
if err != nil {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get rule history", err)
|
||||
}
|
||||
sort.Slice(rules, func(i, j int) bool { return rules[i].ID > rules[j].ID })
|
||||
result := make(apimodels.GettableRuleVersions, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
// do not provide provenance status because we do not have historical changes for it
|
||||
result = append(result, toGettableExtendedRuleNode(*rule, map[string]ngmodels.Provenance{}, srv.resolveUserIdToNameFn(ctx)))
|
||||
}
|
||||
return response.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
func (srv RulerSrv) RoutePostNameRulesConfig(c *contextmodel.ReqContext, ruleGroupConfig apimodels.PostableRuleGroupConfig, namespaceUID string) response.Response {
|
||||
namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), namespaceUID, c.SignedInUser.GetOrgID(), c.SignedInUser)
|
||||
if err != nil {
|
||||
|
||||
@@ -448,6 +448,109 @@ func TestRouteGetRuleByUID(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteGetRuleHistoryByUID(t *testing.T) {
|
||||
orgID := rand.Int63()
|
||||
f := randFolder()
|
||||
groupKey := models.GenerateGroupKey(orgID)
|
||||
groupKey.NamespaceUID = f.UID
|
||||
gen := models.RuleGen.With(models.RuleGen.WithGroupKey(groupKey), models.RuleGen.WithUniqueID())
|
||||
|
||||
t.Run("rule history is successfully fetched with the correct UID", func(t *testing.T) {
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], f)
|
||||
|
||||
rule := gen.GenerateRef()
|
||||
history := gen.With(gen.WithUID(rule.UID)).GenerateManyRef(3)
|
||||
// simulate order of the history
|
||||
rule.ID = 100
|
||||
for i, alertRule := range history {
|
||||
alertRule.ID = rule.ID - int64(i) - 1
|
||||
}
|
||||
|
||||
ruleStore.PutRule(context.Background(), rule)
|
||||
ruleStore.History[rule.GetKey()] = append(ruleStore.History[rule.GetKey()], history...)
|
||||
|
||||
perms := createPermissionsForRules([]*models.AlertRule{rule}, orgID)
|
||||
req := createRequestContextWithPerms(orgID, perms, nil)
|
||||
|
||||
svc := createService(ruleStore)
|
||||
response := svc.RouteGetRuleVersionsByUID(req, rule.UID)
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
var result apimodels.GettableRuleVersions
|
||||
require.NoError(t, json.Unmarshal(response.Body(), &result))
|
||||
require.NotNil(t, result)
|
||||
|
||||
require.Len(t, result, len(history)+1) // history + current version
|
||||
|
||||
t.Run("should be in correct order", func(t *testing.T) {
|
||||
expectedHistory := append([]*models.AlertRule{rule}, history...)
|
||||
for i, rul := range expectedHistory {
|
||||
assert.Equal(t, rul.UID, result[i].GrafanaManagedAlert.UID)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("NotFound when rule does not exist", func(t *testing.T) {
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], f)
|
||||
ruleKey := models.AlertRuleKey{
|
||||
OrgID: orgID,
|
||||
UID: "test",
|
||||
}
|
||||
history := gen.With(gen.WithKey(ruleKey)).GenerateManyRef(3)
|
||||
ruleStore.History[ruleKey] = append(ruleStore.History[ruleKey], history...) // even if history is full of records
|
||||
|
||||
perms := createPermissionsForRules(history, orgID)
|
||||
req := createRequestContextWithPerms(orgID, perms, nil)
|
||||
response := createService(ruleStore).RouteGetRuleVersionsByUID(req, ruleKey.UID)
|
||||
|
||||
require.Equal(t, http.StatusNotFound, response.Status())
|
||||
})
|
||||
|
||||
t.Run("Empty result when rule history is empty", func(t *testing.T) {
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], f)
|
||||
ruleKey := models.AlertRuleKey{
|
||||
OrgID: orgID,
|
||||
UID: "test",
|
||||
}
|
||||
rule := gen.With(gen.WithKey(ruleKey)).GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), rule)
|
||||
ruleStore.History[ruleKey] = nil
|
||||
|
||||
perms := createPermissionsForRules([]*models.AlertRule{rule}, orgID)
|
||||
req := createRequestContextWithPerms(orgID, perms, nil)
|
||||
response := createService(ruleStore).RouteGetRuleVersionsByUID(req, ruleKey.UID)
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
var result apimodels.GettableRuleVersions
|
||||
require.NoError(t, json.Unmarshal(response.Body(), &result))
|
||||
require.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("Unauthorized if user does not have access to the current rule", func(t *testing.T) {
|
||||
ruleStore := fakes.NewRuleStore(t)
|
||||
anotherFolder := randFolder()
|
||||
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], f, anotherFolder)
|
||||
ruleKey := models.AlertRuleKey{
|
||||
OrgID: orgID,
|
||||
UID: "test",
|
||||
}
|
||||
rule := gen.With(gen.WithKey(ruleKey), gen.WithNamespaceUID(anotherFolder.UID)).GenerateRef()
|
||||
ruleStore.PutRule(context.Background(), rule)
|
||||
history := gen.With(gen.WithKey(ruleKey)).GenerateManyRef(3)
|
||||
ruleStore.History[ruleKey] = history
|
||||
|
||||
perms := createPermissionsForRules(history, orgID) // grant permissions to all records in history but not the rule itself
|
||||
req := createRequestContextWithPerms(orgID, perms, nil)
|
||||
response := createService(ruleStore).RouteGetRuleVersionsByUID(req, ruleKey.UID)
|
||||
|
||||
require.Equal(t, http.StatusForbidden, response.Status())
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteGetRulesConfig(t *testing.T) {
|
||||
gen := models.RuleGen
|
||||
t.Run("fine-grained access is enabled", func(t *testing.T) {
|
||||
|
||||
@@ -41,7 +41,8 @@ func (api *API) authorize(method, path string) web.Handler {
|
||||
case http.MethodGet + "/api/ruler/grafana/api/v1/rules",
|
||||
http.MethodGet + "/api/ruler/grafana/api/v1/export/rules":
|
||||
eval = ac.EvalPermission(ac.ActionAlertingRuleRead)
|
||||
case http.MethodGet + "/api/ruler/grafana/api/v1/rule/{RuleUID}":
|
||||
case http.MethodGet + "/api/ruler/grafana/api/v1/rule/{RuleUID}",
|
||||
http.MethodGet + "/api/ruler/grafana/api/v1/rule/{RuleUID}/versions":
|
||||
eval = ac.EvalAll(
|
||||
ac.EvalPermission(ac.ActionAlertingRuleRead),
|
||||
ac.EvalPermission(dashboards.ActionFoldersRead),
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestAuthorize(t *testing.T) {
|
||||
}
|
||||
paths[p] = methods
|
||||
}
|
||||
require.Len(t, paths, 59)
|
||||
require.Len(t, paths, 60)
|
||||
|
||||
ac := acmock.New()
|
||||
api := &API{AccessControl: ac, FeatureManager: featuremgmt.WithFeatures()}
|
||||
|
||||
@@ -124,3 +124,7 @@ func (f *RulerApiHandler) getService(ctx *contextmodel.ReqContext) (*LotexRuler,
|
||||
}
|
||||
return f.LotexRuler, nil
|
||||
}
|
||||
|
||||
func (f *RulerApiHandler) handleRouteGetRuleVersionsByUID(ctx *contextmodel.ReqContext, ruleUID string) response.Response {
|
||||
return f.GrafanaRuler.RouteGetRuleVersionsByUID(ctx, ruleUID)
|
||||
}
|
||||
@@ -29,6 +29,7 @@ type RulerApi interface {
|
||||
RouteGetNamespaceGrafanaRulesConfig(*contextmodel.ReqContext) response.Response
|
||||
RouteGetNamespaceRulesConfig(*contextmodel.ReqContext) response.Response
|
||||
RouteGetRuleByUID(*contextmodel.ReqContext) response.Response
|
||||
RouteGetRuleVersionsByUID(*contextmodel.ReqContext) response.Response
|
||||
RouteGetRulegGroupConfig(*contextmodel.ReqContext) response.Response
|
||||
RouteGetRulesConfig(*contextmodel.ReqContext) response.Response
|
||||
RouteGetRulesForExport(*contextmodel.ReqContext) response.Response
|
||||
@@ -86,6 +87,11 @@ func (f *RulerApiHandler) RouteGetRuleByUID(ctx *contextmodel.ReqContext) respon
|
||||
ruleUIDParam := web.Params(ctx.Req)[":RuleUID"]
|
||||
return f.handleRouteGetRuleByUID(ctx, ruleUIDParam)
|
||||
}
|
||||
func (f *RulerApiHandler) RouteGetRuleVersionsByUID(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
ruleUIDParam := web.Params(ctx.Req)[":RuleUID"]
|
||||
return f.handleRouteGetRuleVersionsByUID(ctx, ruleUIDParam)
|
||||
}
|
||||
func (f *RulerApiHandler) RouteGetRulegGroupConfig(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
datasourceUIDParam := web.Params(ctx.Req)[":DatasourceUID"]
|
||||
@@ -243,6 +249,18 @@ func (api *API) RegisterRulerApiEndpoints(srv RulerApi, m *metrics.API) {
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/ruler/grafana/api/v1/rule/{RuleUID}/versions"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||
api.authorize(http.MethodGet, "/api/ruler/grafana/api/v1/rule/{RuleUID}/versions"),
|
||||
metrics.Instrument(
|
||||
http.MethodGet,
|
||||
"/api/ruler/grafana/api/v1/rule/{RuleUID}/versions",
|
||||
api.Hooks.Wrap(srv.RouteGetRuleVersionsByUID),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/ruler/{DatasourceUID}/api/v1/rules/{Namespace}/{Groupname}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
|
||||
@@ -28,6 +28,6 @@ type RuleStore interface {
|
||||
|
||||
// IncreaseVersionForAllRulesInNamespaces Increases version for all rules that have specified namespace uids
|
||||
IncreaseVersionForAllRulesInNamespaces(ctx context.Context, orgID int64, namespaceUIDs []string) ([]ngmodels.AlertRuleKeyWithVersion, error)
|
||||
|
||||
GetAlertRuleVersions(ctx context.Context, key ngmodels.AlertRuleKey) ([]*ngmodels.AlertRule, error)
|
||||
accesscontrol.RuleUIDToNamespaceStore
|
||||
}
|
||||
@@ -459,6 +459,9 @@
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -498,10 +501,15 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type",
|
||||
@@ -1166,6 +1174,14 @@
|
||||
"CounterResetHint": {
|
||||
"$ref": "#/definitions/CounterResetHint"
|
||||
},
|
||||
"CustomValues": {
|
||||
"description": "Holds the custom (usually upper) bounds for bucket definitions, otherwise nil.\nThis slice is interned, to be treated as immutable and copied by reference.\nThese numbers should be strictly increasing. This field is only used when the\nschema is for custom buckets, and the ZeroThreshold, ZeroCount, NegativeSpans\nand NegativeBuckets fields are not used in that case.",
|
||||
"items": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"PositiveBuckets": {
|
||||
"description": "Observation counts in buckets. Each represents an absolute count and\nmust be zero or positive.",
|
||||
"items": {
|
||||
@@ -1182,7 +1198,7 @@
|
||||
"type": "array"
|
||||
},
|
||||
"Schema": {
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8. They are all for\nbase-2 bucket schemas, where 1 is a bucket boundary in each case, and\nthen each power of two is divided into 2^n logarithmic buckets. Or\nin other words, each bucket boundary is the previous boundary times\n2^(2^-n).",
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8 for exponential buckets.\nThey are all for base-2 bucket schemas, where 1 is a bucket boundary in\neach case, and then each power of two is divided into 2^n logarithmic buckets.\nOr in other words, each bucket boundary is the previous boundary times\n2^(2^-n). Another valid schema number is -53 for custom buckets, defined by\nthe CustomValues field.",
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -1628,6 +1644,9 @@
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_by": {
|
||||
"$ref": "#/definitions/UserInfo"
|
||||
},
|
||||
"version": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
@@ -1712,6 +1731,12 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GettableRuleVersions": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/GettableExtendedRuleNode"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GettableStatus": {
|
||||
"properties": {
|
||||
"cluster": {
|
||||
@@ -3572,6 +3597,9 @@
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3593,10 +3621,15 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type"
|
||||
@@ -3636,6 +3669,9 @@
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"interval": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
@@ -3665,6 +3701,7 @@
|
||||
"required": [
|
||||
"name",
|
||||
"file",
|
||||
"folderUid",
|
||||
"rules",
|
||||
"interval"
|
||||
],
|
||||
@@ -3767,6 +3804,10 @@
|
||||
"Sample": {
|
||||
"description": "Sample is a single sample belonging to a metric. It represents either a float\nsample or a histogram sample. If H is nil, it is a float sample. Otherwise,\nit is a histogram sample.",
|
||||
"properties": {
|
||||
"DropName": {
|
||||
"description": "DropName is used to indicate whether the __name__ label should be dropped\nas part of the query evaluation.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"F": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
@@ -4382,7 +4423,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nThe Host field contains the host and port subcomponents of the URL.\nWhen the port is present, it is separated from the host with a colon.\nWhen the host is an IPv6 address, it must be enclosed in square brackets:\n\"[fe80::1]:80\". The [net.JoinHostPort] function combines a host and port\ninto a string suitable for the Host field, adding square brackets to\nthe host when necessary.\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use the [URL.EscapedPath] method, which preserves\nthe original encoding of Path.\n\nThe RawPath field is an optional field which is only set when the default\nencoding of Path is different from the escaped path. See the EscapedPath method\nfor more details.\n\nURL's String method uses the EscapedPath method to obtain the path.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@@ -4418,7 +4458,7 @@
|
||||
"$ref": "#/definitions/Userinfo"
|
||||
}
|
||||
},
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"type": "object"
|
||||
},
|
||||
"UpdateRuleGroupResponse": {
|
||||
@@ -4447,6 +4487,18 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"UserInfo": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "UserInfo represents user-related information, including a unique identifier and a name.",
|
||||
"type": "object"
|
||||
},
|
||||
"Userinfo": {
|
||||
"description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a [URL]. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.",
|
||||
"type": "object"
|
||||
@@ -4648,6 +4700,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup",
|
||||
"type": "object"
|
||||
@@ -4934,6 +4987,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence",
|
||||
"type": "object"
|
||||
|
||||
@@ -20,6 +20,18 @@ import (
|
||||
// 403: ForbiddenError
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route Get /ruler/grafana/api/v1/rule/{RuleUID}/versions ruler RouteGetRuleVersionsByUID
|
||||
//
|
||||
// Get rule versions by UID
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 202: GettableRuleVersions
|
||||
// 403: ForbiddenError
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route Get /ruler/grafana/api/v1/rules ruler RouteGetGrafanaRulesConfig
|
||||
//
|
||||
// List rule groups
|
||||
@@ -218,7 +230,7 @@ type PathGetRulesParams struct {
|
||||
PanelID int64
|
||||
}
|
||||
|
||||
// swagger:parameters RouteGetRuleByUID
|
||||
// swagger:parameters RouteGetRuleByUID RouteGetRuleVersionsByUID
|
||||
type PathGetRuleByUIDParams struct {
|
||||
// in: path
|
||||
RuleUID string
|
||||
@@ -290,6 +302,9 @@ func (c *PostableRuleGroupConfig) validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type GettableRuleVersions []GettableExtendedRuleNode
|
||||
|
||||
// swagger:model
|
||||
type GettableRuleGroupConfig struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
|
||||
@@ -459,6 +459,9 @@
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -498,10 +501,15 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type",
|
||||
@@ -1166,6 +1174,14 @@
|
||||
"CounterResetHint": {
|
||||
"$ref": "#/definitions/CounterResetHint"
|
||||
},
|
||||
"CustomValues": {
|
||||
"description": "Holds the custom (usually upper) bounds for bucket definitions, otherwise nil.\nThis slice is interned, to be treated as immutable and copied by reference.\nThese numbers should be strictly increasing. This field is only used when the\nschema is for custom buckets, and the ZeroThreshold, ZeroCount, NegativeSpans\nand NegativeBuckets fields are not used in that case.",
|
||||
"items": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"PositiveBuckets": {
|
||||
"description": "Observation counts in buckets. Each represents an absolute count and\nmust be zero or positive.",
|
||||
"items": {
|
||||
@@ -1182,7 +1198,7 @@
|
||||
"type": "array"
|
||||
},
|
||||
"Schema": {
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8. They are all for\nbase-2 bucket schemas, where 1 is a bucket boundary in each case, and\nthen each power of two is divided into 2^n logarithmic buckets. Or\nin other words, each bucket boundary is the previous boundary times\n2^(2^-n).",
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8 for exponential buckets.\nThey are all for base-2 bucket schemas, where 1 is a bucket boundary in\neach case, and then each power of two is divided into 2^n logarithmic buckets.\nOr in other words, each bucket boundary is the previous boundary times\n2^(2^-n). Another valid schema number is -53 for custom buckets, defined by\nthe CustomValues field.",
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -1628,6 +1644,9 @@
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_by": {
|
||||
"$ref": "#/definitions/UserInfo"
|
||||
},
|
||||
"version": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
@@ -1712,6 +1731,12 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GettableRuleVersions": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/GettableExtendedRuleNode"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GettableStatus": {
|
||||
"properties": {
|
||||
"cluster": {
|
||||
@@ -3572,6 +3597,9 @@
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3593,10 +3621,15 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type"
|
||||
@@ -3636,6 +3669,9 @@
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"interval": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
@@ -3665,6 +3701,7 @@
|
||||
"required": [
|
||||
"name",
|
||||
"file",
|
||||
"folderUid",
|
||||
"rules",
|
||||
"interval"
|
||||
],
|
||||
@@ -3767,6 +3804,10 @@
|
||||
"Sample": {
|
||||
"description": "Sample is a single sample belonging to a metric. It represents either a float\nsample or a histogram sample. If H is nil, it is a float sample. Otherwise,\nit is a histogram sample.",
|
||||
"properties": {
|
||||
"DropName": {
|
||||
"description": "DropName is used to indicate whether the __name__ label should be dropped\nas part of the query evaluation.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"F": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
@@ -4446,6 +4487,18 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"UserInfo": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "UserInfo represents user-related information, including a unique identifier and a name.",
|
||||
"type": "object"
|
||||
},
|
||||
"Userinfo": {
|
||||
"description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a [URL]. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.",
|
||||
"type": "object"
|
||||
@@ -4647,6 +4700,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup",
|
||||
"type": "object"
|
||||
@@ -4808,6 +4862,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert",
|
||||
"type": "object"
|
||||
@@ -4932,6 +4987,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence",
|
||||
"type": "object"
|
||||
@@ -6515,6 +6571,43 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ruler/grafana/api/v1/rule/{RuleUID}/versions": {
|
||||
"get": {
|
||||
"description": "Get rule versions by UID",
|
||||
"operationId": "RouteGetRuleVersionsByUID",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "RuleUID",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "GettableRuleVersions",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/GettableRuleVersions"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"ruler"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ruler/grafana/api/v1/rules": {
|
||||
"get": {
|
||||
"description": "List rule groups",
|
||||
|
||||
@@ -1343,6 +1343,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ruler/grafana/api/v1/rule/{RuleUID}/versions": {
|
||||
"get": {
|
||||
"description": "Get rule versions by UID",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"ruler"
|
||||
],
|
||||
"operationId": "RouteGetRuleVersionsByUID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "RuleUID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "GettableRuleVersions",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/GettableRuleVersions"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ruler/grafana/api/v1/rules": {
|
||||
"get": {
|
||||
"description": "List rule groups",
|
||||
@@ -4088,7 +4125,9 @@
|
||||
"description": "adapted from cortex",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type",
|
||||
@@ -4118,6 +4157,9 @@
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -4157,6 +4199,9 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4818,6 +4863,14 @@
|
||||
"CounterResetHint": {
|
||||
"$ref": "#/definitions/CounterResetHint"
|
||||
},
|
||||
"CustomValues": {
|
||||
"description": "Holds the custom (usually upper) bounds for bucket definitions, otherwise nil.\nThis slice is interned, to be treated as immutable and copied by reference.\nThese numbers should be strictly increasing. This field is only used when the\nschema is for custom buckets, and the ZeroThreshold, ZeroCount, NegativeSpans\nand NegativeBuckets fields are not used in that case.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
"PositiveBuckets": {
|
||||
"description": "Observation counts in buckets. Each represents an absolute count and\nmust be zero or positive.",
|
||||
"type": "array",
|
||||
@@ -4834,7 +4887,7 @@
|
||||
}
|
||||
},
|
||||
"Schema": {
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8. They are all for\nbase-2 bucket schemas, where 1 is a bucket boundary in each case, and\nthen each power of two is divided into 2^n logarithmic buckets. Or\nin other words, each bucket boundary is the previous boundary times\n2^(2^-n).",
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8 for exponential buckets.\nThey are all for base-2 bucket schemas, where 1 is a bucket boundary in\neach case, and then each power of two is divided into 2^n logarithmic buckets.\nOr in other words, each bucket boundary is the previous boundary times\n2^(2^-n). Another valid schema number is -53 for custom buckets, defined by\nthe CustomValues field.",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
@@ -5279,6 +5332,9 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated_by": {
|
||||
"$ref": "#/definitions/UserInfo"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
@@ -5362,6 +5418,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"GettableRuleVersions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/GettableExtendedRuleNode"
|
||||
}
|
||||
},
|
||||
"GettableStatus": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -7220,7 +7282,9 @@
|
||||
"description": "adapted from cortex",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type"
|
||||
@@ -7230,6 +7294,9 @@
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -7251,6 +7318,9 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7283,6 +7353,7 @@
|
||||
"required": [
|
||||
"name",
|
||||
"file",
|
||||
"folderUid",
|
||||
"rules",
|
||||
"interval"
|
||||
],
|
||||
@@ -7294,6 +7365,9 @@
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"interval": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
@@ -7419,6 +7493,10 @@
|
||||
"description": "Sample is a single sample belonging to a metric. It represents either a float\nsample or a histogram sample. If H is nil, it is a float sample. Otherwise,\nit is a histogram sample.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"DropName": {
|
||||
"description": "DropName is used to indicate whether the __name__ label should be dropped\nas part of the query evaluation.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"F": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
@@ -8097,6 +8175,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserInfo": {
|
||||
"type": "object",
|
||||
"title": "UserInfo represents user-related information, including a unique identifier and a name.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Userinfo": {
|
||||
"description": "The Userinfo type is an immutable encapsulation of username and\npassword details for a [URL]. An existing Userinfo value is guaranteed\nto have a username set (potentially empty, as allowed by RFC 2396),\nand optionally a password.",
|
||||
"type": "object"
|
||||
@@ -8298,6 +8388,7 @@
|
||||
}
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
@@ -8459,6 +8550,7 @@
|
||||
}
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
@@ -8583,6 +8675,7 @@
|
||||
}
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
|
||||
@@ -550,6 +550,19 @@ func (a *AlertRuleMutators) WithUpdatedBy(uid *UserUID) AlertRuleMutator {
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRuleMutators) WithUID(uid string) AlertRuleMutator {
|
||||
return func(r *AlertRule) {
|
||||
r.UID = uid
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AlertRuleMutators) WithKey(key AlertRuleKey) AlertRuleMutator {
|
||||
return func(r *AlertRule) {
|
||||
r.UID = key.UID
|
||||
r.OrgID = key.OrgID
|
||||
}
|
||||
}
|
||||
|
||||
func (g *AlertRuleGenerator) GenerateLabels(min, max int, prefix string) data.Labels {
|
||||
count := max
|
||||
if min > max {
|
||||
|
||||
@@ -118,6 +118,36 @@ func (st DBstore) GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAler
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (st DBstore) GetAlertRuleVersions(ctx context.Context, key ngmodels.AlertRuleKey) ([]*ngmodels.AlertRule, error) {
|
||||
alertRules := make([]*ngmodels.AlertRule, 0)
|
||||
err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error {
|
||||
rows, err := sess.Table(new(alertRuleVersion)).Where("rule_org_id = ? AND rule_uid = ?", key.OrgID, key.UID).Desc("id").Rows(new(alertRuleVersion))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Deserialize each rule separately in case any of them contain invalid JSON.
|
||||
for rows.Next() {
|
||||
rule := new(alertRuleVersion)
|
||||
err = rows.Scan(rule)
|
||||
if err != nil {
|
||||
st.Logger.Error("Invalid rule version found in DB store, ignoring it", "func", "GetAlertRuleVersions", "error", err)
|
||||
continue
|
||||
}
|
||||
converted, err := alertRuleToModelsAlertRule(alertRuleVersionToAlertRule(*rule), st.Logger)
|
||||
if err != nil {
|
||||
st.Logger.Error("Invalid rule found in DB store, cannot convert, ignoring it", "func", "GetAlertRuleVersions", "error", err, "version_id", rule.ID)
|
||||
continue
|
||||
}
|
||||
alertRules = append(alertRules, &converted)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return alertRules, nil
|
||||
}
|
||||
|
||||
// GetRuleByID retrieves models.AlertRule by ID.
|
||||
// It returns models.ErrAlertRuleNotFound if no alert rule is found for the provided ID.
|
||||
func (st DBstore) GetRuleByID(ctx context.Context, query ngmodels.GetAlertRuleByIDQuery) (result *ngmodels.AlertRule, err error) {
|
||||
|
||||
@@ -204,3 +204,34 @@ func alertRuleToAlertRuleVersion(rule alertRule) alertRuleVersion {
|
||||
Metadata: rule.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
func alertRuleVersionToAlertRule(version alertRuleVersion) alertRule {
|
||||
return alertRule{
|
||||
ID: version.ID,
|
||||
OrgID: version.RuleOrgID,
|
||||
Title: version.Title,
|
||||
Condition: version.Condition,
|
||||
Data: version.Data,
|
||||
Updated: version.Created,
|
||||
UpdatedBy: version.CreatedBy,
|
||||
IntervalSeconds: version.IntervalSeconds,
|
||||
Version: version.Version,
|
||||
UID: version.RuleUID,
|
||||
NamespaceUID: version.RuleNamespaceUID,
|
||||
// Versions do not store Dashboard\Panel as separate column.
|
||||
// However, these fields are part of annotations and information in these fields is redundant
|
||||
DashboardUID: nil,
|
||||
PanelID: nil,
|
||||
RuleGroup: version.RuleGroup,
|
||||
RuleGroupIndex: version.RuleGroupIndex,
|
||||
Record: version.Record,
|
||||
NoDataState: version.NoDataState,
|
||||
ExecErrState: version.ExecErrState,
|
||||
For: version.For,
|
||||
Annotations: version.Annotations,
|
||||
Labels: version.Labels,
|
||||
IsPaused: version.IsPaused,
|
||||
NotificationSettings: version.NotificationSettings,
|
||||
Metadata: version.Metadata,
|
||||
}
|
||||
}
|
||||
@@ -43,3 +43,20 @@ func TestAlertRuleToModelsAlertRule(t *testing.T) {
|
||||
require.Equal(t, ngmodels.ErrorErrState, converted.ExecErrState)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAlertRuleVersionToAlertRule(t *testing.T) {
|
||||
g := ngmodels.RuleGen
|
||||
|
||||
t.Run("make sure no data is lost between conversions", func(t *testing.T) {
|
||||
for _, rule := range g.GenerateMany(100) {
|
||||
// ignore fields
|
||||
rule.DashboardUID = nil
|
||||
rule.PanelID = nil
|
||||
|
||||
r, err := alertRuleFromModelsAlertRule(rule)
|
||||
require.NoError(t, err)
|
||||
r2 := alertRuleVersionToAlertRule(alertRuleToAlertRuleVersion(r))
|
||||
require.Equal(t, r, r2)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -22,6 +22,7 @@ type RuleStore struct {
|
||||
mtx sync.Mutex
|
||||
// OrgID -> RuleGroup -> Namespace -> Rules
|
||||
Rules map[int64][]*models.AlertRule
|
||||
History map[models.AlertRuleKey][]*models.AlertRule
|
||||
Hook func(cmd any) error // use Hook if you need to intercept some query and return an error
|
||||
RecordedOps []any
|
||||
Folders map[int64][]*folder.Folder
|
||||
@@ -40,6 +41,7 @@ func NewRuleStore(t *testing.T) *RuleStore {
|
||||
return nil
|
||||
},
|
||||
Folders: map[int64][]*folder.Folder{},
|
||||
History: map[models.AlertRuleKey][]*models.AlertRule{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +52,8 @@ func (f *RuleStore) PutRule(_ context.Context, rules ...*models.AlertRule) {
|
||||
mainloop:
|
||||
for _, r := range rules {
|
||||
rgs := f.Rules[r.OrgID]
|
||||
cp := models.CopyRule(r)
|
||||
f.History[r.GetKey()] = append(f.History[r.GetKey()], cp)
|
||||
for idx, rulePtr := range rgs {
|
||||
if rulePtr.UID == r.UID {
|
||||
rgs[idx] = r
|
||||
@@ -131,11 +135,7 @@ func (f *RuleStore) GetAlertRuleByUID(_ context.Context, q *models.GetAlertRuleB
|
||||
if err := f.Hook(*q); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rules, ok := f.Rules[q.OrgID]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rules := f.Rules[q.OrgID]
|
||||
for _, rule := range rules {
|
||||
if rule.UID == q.UID {
|
||||
return rule, nil
|
||||
@@ -372,3 +372,22 @@ func (f *RuleStore) GetNamespacesByRuleUID(ctx context.Context, orgID int64, uid
|
||||
|
||||
return namespacesMap, nil
|
||||
}
|
||||
|
||||
func (f *RuleStore) GetAlertRuleVersions(_ context.Context, key models.AlertRuleKey) ([]*models.AlertRule, error) {
|
||||
f.mtx.Lock()
|
||||
defer f.mtx.Unlock()
|
||||
|
||||
q := GenericRecordedQuery{
|
||||
Name: "GetAlertRuleVersions",
|
||||
Params: []any{key},
|
||||
}
|
||||
defer func() {
|
||||
f.RecordedOps = append(f.RecordedOps, q)
|
||||
}()
|
||||
|
||||
if err := f.Hook(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f.History[key], nil
|
||||
}
|
||||
@@ -241,6 +241,18 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
|
||||
t.Run("Get versions of any rule", func(t *testing.T) {
|
||||
for _, groups := range allRules { // random rule from each folder
|
||||
group := groups[rand.Intn(len(groups))]
|
||||
rule := group.Rules[rand.Intn(len(group.Rules))]
|
||||
versions, status, raw := apiClient.GetRuleVersionsWithStatus(t, rule.GrafanaManagedAlert.UID)
|
||||
if assert.Equalf(t, http.StatusOK, status, "Expected status 200, got %d: %s", status, raw) {
|
||||
assert.NotEmpty(t, versions)
|
||||
assert.Equal(t, rule, versions[0]) // the first version in the collection should always be the current
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when permissions for folder2 removed", func(t *testing.T) {
|
||||
@@ -310,6 +322,12 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
|
||||
require.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
|
||||
t.Run("Versions of rule", func(t *testing.T) {
|
||||
uid := allRules["folder2"][0].Rules[0].GrafanaManagedAlert.UID
|
||||
_, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusForbidden, status, "Expected status 403, got %d: %s", status, raw)
|
||||
})
|
||||
|
||||
t.Run("when all permissions are revoked", func(t *testing.T) {
|
||||
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1")
|
||||
apiClient.ReloadCachedPermissions(t)
|
||||
@@ -4315,6 +4333,95 @@ func TestIntegrationRuleUpdateAllDatabases(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationRuleVersions(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
// Setup Grafana and its Database
|
||||
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
EnableQuota: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
|
||||
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleEditor),
|
||||
Password: "password",
|
||||
Login: "grafana",
|
||||
})
|
||||
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
||||
|
||||
// Create the namespace we'll save our alerts to.
|
||||
apiClient.CreateFolder(t, "folder1", "folder1")
|
||||
|
||||
postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json"))
|
||||
require.NoError(t, err)
|
||||
var group1 apimodels.PostableRuleGroupConfig
|
||||
require.NoError(t, json.Unmarshal(postGroupRaw, &group1))
|
||||
|
||||
// Create rule under folder1
|
||||
response := apiClient.PostRulesGroup(t, "folder1", &group1)
|
||||
|
||||
require.NotEmptyf(t, response.Created, "Expected created to be set")
|
||||
uid := response.Created[0]
|
||||
|
||||
ruleV1 := apiClient.GetRuleByUID(t, uid)
|
||||
|
||||
t.Run("should return 1 version right after creation", func(t *testing.T) {
|
||||
versions, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusOK, status, "Expected status 200, got %d: %s", status, raw)
|
||||
require.Lenf(t, versions, 1, "Expected 1 version, got %d", len(versions))
|
||||
assert.Equal(t, ruleV1, versions[0])
|
||||
})
|
||||
|
||||
group1Gettable := apiClient.GetRulesGroup(t, "folder1", group1.Name)
|
||||
group1 = convertGettableRuleGroupToPostable(group1Gettable.GettableRuleGroupConfig)
|
||||
group1.Rules[0].Annotations[util.GenerateShortUID()] = util.GenerateShortUID()
|
||||
|
||||
_ = apiClient.PostRulesGroup(t, "folder1", &group1)
|
||||
|
||||
ruleV2 := apiClient.GetRuleByUID(t, uid)
|
||||
|
||||
t.Run("should return previous versions after update", func(t *testing.T) {
|
||||
versions, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusOK, status, "Expected status 200, got %d: %s", status, raw)
|
||||
require.Lenf(t, versions, 2, "Expected 2 versions, got %d", len(versions))
|
||||
|
||||
pathsToIgnore := []string{
|
||||
"GrafanaManagedAlert.ID", // In versions ID has different value
|
||||
}
|
||||
// compare expected and actual and ignore the dynamic fields
|
||||
diff := cmp.Diff(apimodels.GettableRuleVersions{ruleV2, ruleV1}, versions, cmp.FilterPath(func(path cmp.Path) bool {
|
||||
for _, s := range pathsToIgnore {
|
||||
if strings.Contains(path.String(), s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()))
|
||||
assert.Empty(t, diff)
|
||||
})
|
||||
|
||||
_ = apiClient.PostRulesGroup(t, "folder1", &group1) // Noop update
|
||||
|
||||
t.Run("should not add new version if rule was not changed", func(t *testing.T) {
|
||||
versions, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusOK, status, "Expected status 200, got %d: %s", status, raw)
|
||||
require.Lenf(t, versions, 2, "Expected 2 versions, got %d", len(versions))
|
||||
})
|
||||
|
||||
apiClient.DeleteRulesGroup(t, "folder1", group1.Name)
|
||||
|
||||
t.Run("should NotFound after rule was deleted", func(t *testing.T) {
|
||||
_, status, raw := apiClient.GetRuleVersionsWithStatus(t, uid)
|
||||
require.Equalf(t, http.StatusNotFound, status, "Expected status 404, got %d: %s", status, raw)
|
||||
})
|
||||
}
|
||||
|
||||
func newTestingRuleConfig(t *testing.T) apimodels.PostableRuleGroupConfig {
|
||||
interval, err := model.ParseDuration("1m")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -475,6 +475,13 @@ func (a apiClient) PostRulesGroupWithStatus(t *testing.T, folder string, group *
|
||||
return m, resp.StatusCode, string(b)
|
||||
}
|
||||
|
||||
func (a apiClient) PostRulesGroup(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig) apimodels.UpdateRuleGroupResponse {
|
||||
t.Helper()
|
||||
m, status, raw := a.PostRulesGroupWithStatus(t, folder, group)
|
||||
requireStatusCode(t, http.StatusAccepted, status, raw)
|
||||
return m
|
||||
}
|
||||
|
||||
func (a apiClient) PostRulesExportWithStatus(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig, params *apimodels.ExportQueryParams) (int, string) {
|
||||
t.Helper()
|
||||
buf := bytes.Buffer{}
|
||||
@@ -1048,6 +1055,22 @@ func (a apiClient) GetActiveAlertsWithStatus(t *testing.T) (apimodels.AlertGroup
|
||||
return sendRequest[apimodels.AlertGroups](t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func (a apiClient) GetRuleVersionsWithStatus(t *testing.T, ruleUID string) (apimodels.GettableRuleVersions, int, string) {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rule/%s/versions", a.url, ruleUID), nil)
|
||||
require.NoError(t, err)
|
||||
return sendRequest[apimodels.GettableRuleVersions](t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func (a apiClient) GetRuleByUID(t *testing.T, ruleUID string) apimodels.GettableExtendedRuleNode {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rule/%s", a.url, ruleUID), nil)
|
||||
require.NoError(t, err)
|
||||
rule, status, raw := sendRequest[apimodels.GettableExtendedRuleNode](t, req, http.StatusOK)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
return rule
|
||||
}
|
||||
|
||||
func sendRequest[T any](t *testing.T, req *http.Request, successStatusCode int) (T, int, string) {
|
||||
t.Helper()
|
||||
client := &http.Client{}
|
||||
|
||||
+56
-1
@@ -12861,7 +12861,9 @@
|
||||
"description": "adapted from cortex",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type",
|
||||
@@ -12891,6 +12893,9 @@
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -12930,6 +12935,9 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -15392,6 +15400,14 @@
|
||||
"CounterResetHint": {
|
||||
"$ref": "#/definitions/CounterResetHint"
|
||||
},
|
||||
"CustomValues": {
|
||||
"description": "Holds the custom (usually upper) bounds for bucket definitions, otherwise nil.\nThis slice is interned, to be treated as immutable and copied by reference.\nThese numbers should be strictly increasing. This field is only used when the\nschema is for custom buckets, and the ZeroThreshold, ZeroCount, NegativeSpans\nand NegativeBuckets fields are not used in that case.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
}
|
||||
},
|
||||
"PositiveBuckets": {
|
||||
"description": "Observation counts in buckets. Each represents an absolute count and\nmust be zero or positive.",
|
||||
"type": "array",
|
||||
@@ -15408,7 +15424,7 @@
|
||||
}
|
||||
},
|
||||
"Schema": {
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8. They are all for\nbase-2 bucket schemas, where 1 is a bucket boundary in each case, and\nthen each power of two is divided into 2^n logarithmic buckets. Or\nin other words, each bucket boundary is the previous boundary times\n2^(2^-n).",
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8 for exponential buckets.\nThey are all for base-2 bucket schemas, where 1 is a bucket boundary in\neach case, and then each power of two is divided into 2^n logarithmic buckets.\nOr in other words, each bucket boundary is the previous boundary times\n2^(2^-n). Another valid schema number is -53 for custom buckets, defined by\nthe CustomValues field.",
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
@@ -16048,6 +16064,9 @@
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updated_by": {
|
||||
"$ref": "#/definitions/UserInfo"
|
||||
},
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
@@ -16131,6 +16150,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"GettableRuleVersions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/GettableExtendedRuleNode"
|
||||
}
|
||||
},
|
||||
"GettableStatus": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -19810,7 +19835,9 @@
|
||||
"description": "adapted from cortex",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type"
|
||||
@@ -19820,6 +19847,9 @@
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -19841,6 +19871,9 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -19873,6 +19906,7 @@
|
||||
"required": [
|
||||
"name",
|
||||
"file",
|
||||
"folderUid",
|
||||
"rules",
|
||||
"interval"
|
||||
],
|
||||
@@ -19884,6 +19918,9 @@
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"interval": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
@@ -20009,6 +20046,10 @@
|
||||
"description": "Sample is a single sample belonging to a metric. It represents either a float\nsample or a histogram sample. If H is nil, it is a float sample. Otherwise,\nit is a histogram sample.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"DropName": {
|
||||
"description": "DropName is used to indicate whether the __name__ label should be dropped\nas part of the query evaluation.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"F": {
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
@@ -22032,6 +22073,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserInfo": {
|
||||
"type": "object",
|
||||
"title": "UserInfo represents user-related information, including a unique identifier and a name.",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserLookupDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -22437,6 +22490,7 @@
|
||||
}
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
@@ -22766,6 +22820,7 @@
|
||||
}
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
|
||||
@@ -4,26 +4,28 @@ import { getRootSectionForNode } from 'app/core/selectors/navModel';
|
||||
import AppRootPage from 'app/features/plugins/components/AppRootPage';
|
||||
import { getState } from 'app/store/store';
|
||||
|
||||
const isPluginNavModelItem = (model: NavModelItem): model is PluginNavModelItem => 'pluginId' in model && 'id' in model;
|
||||
const isStandalonePluginPage = (id: string) => id.startsWith('standalone-plugin-page-/');
|
||||
|
||||
function getPathForNavItem(navItem: PluginNavModelItem) {
|
||||
const pluginNavSection = getRootSectionForNode(navItem);
|
||||
const appPluginUrl = `/a/${navItem.pluginId}`;
|
||||
const path = isStandalonePluginPage(navItem.id) ? navItem.url || appPluginUrl : appPluginUrl; // Only standalone pages can use core URLs, otherwise we fall back to "/a/:pluginId"
|
||||
const isSensitive = isStandalonePluginPage(navItem.id) && !navItem.url?.startsWith('/a/'); // Have case-sensitive URLs only for standalone pages that have custom URLs
|
||||
|
||||
return {
|
||||
path: `${path}/*`,
|
||||
sensitive: isSensitive,
|
||||
component: () => <AppRootPage pluginId={navItem.pluginId} pluginNavSection={pluginNavSection} />,
|
||||
};
|
||||
}
|
||||
|
||||
export function getAppPluginRoutes(): RouteDescriptor[] {
|
||||
const state = getState();
|
||||
const { navIndex } = state;
|
||||
const isStandalonePluginPage = (id: string) => id.startsWith('standalone-plugin-page-/');
|
||||
const isPluginNavModelItem = (model: NavModelItem): model is PluginNavModelItem =>
|
||||
'pluginId' in model && 'id' in model;
|
||||
const explicitAppPluginRoutes = Object.values(navIndex)
|
||||
.filter<PluginNavModelItem>(isPluginNavModelItem)
|
||||
.map((navItem) => {
|
||||
const pluginNavSection = getRootSectionForNode(navItem);
|
||||
const appPluginUrl = `/a/${navItem.pluginId}`;
|
||||
const path = isStandalonePluginPage(navItem.id) ? navItem.url || appPluginUrl : appPluginUrl; // Only standalone pages can use core URLs, otherwise we fall back to "/a/:pluginId"
|
||||
const isSensitive = isStandalonePluginPage(navItem.id) && !navItem.url?.startsWith('/a/'); // Have case-sensitive URLs only for standalone pages that have custom URLs
|
||||
|
||||
return {
|
||||
path: `${path}/*`,
|
||||
sensitive: isSensitive,
|
||||
component: () => <AppRootPage pluginId={navItem.pluginId} pluginNavSection={pluginNavSection} />,
|
||||
};
|
||||
});
|
||||
.map(getPathForNavItem);
|
||||
|
||||
return [
|
||||
...explicitAppPluginRoutes,
|
||||
@@ -36,6 +38,23 @@ export function getAppPluginRoutes(): RouteDescriptor[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function getRouteForAppPlugin(pluginId: string): RouteDescriptor {
|
||||
const state = getState();
|
||||
const { navIndex } = state;
|
||||
const navItem = Object.values(navIndex)
|
||||
.filter<PluginNavModelItem>(isPluginNavModelItem)
|
||||
.find((navItem) => navItem.pluginId === pluginId);
|
||||
|
||||
if (!navItem) {
|
||||
return {
|
||||
path: '/a/:pluginId/*',
|
||||
component: () => <AppRootPage pluginNavSection={navIndex.home} />,
|
||||
};
|
||||
}
|
||||
|
||||
return getPathForNavItem(navItem);
|
||||
}
|
||||
|
||||
interface PluginNavModelItem extends Omit<NavModelItem, 'pluginId' | 'id'> {
|
||||
pluginId: string;
|
||||
id: string;
|
||||
|
||||
@@ -1,6 +1,143 @@
|
||||
import { toDashboardResults, SearchHit, SearchAPIResponse } from './unified';
|
||||
import { BackendSrv } from '@grafana/runtime';
|
||||
|
||||
import { GrafanaSearcher, SearchQuery } from './types';
|
||||
import { toDashboardResults, SearchHit, SearchAPIResponse, UnifiedSearcher } from './unified';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const mockResults: SearchAPIResponse = {
|
||||
hits: [],
|
||||
totalHits: 0,
|
||||
};
|
||||
|
||||
const mockFolders: SearchAPIResponse = {
|
||||
hits: [],
|
||||
totalHits: 0,
|
||||
};
|
||||
|
||||
const mockFallbackSearcher = {
|
||||
search: jest.fn(),
|
||||
} as unknown as GrafanaSearcher;
|
||||
|
||||
const getResponse = (uri: string) => {
|
||||
if (uri.endsWith('?type=folders')) {
|
||||
return Promise.resolve(mockFolders);
|
||||
}
|
||||
return Promise.resolve(mockResults);
|
||||
};
|
||||
|
||||
const mockSearcher = {
|
||||
search: (uri: string) => getResponse(uri),
|
||||
};
|
||||
|
||||
jest.mock('@grafana/runtime', () => {
|
||||
const originalRuntime = jest.requireActual('@grafana/runtime');
|
||||
return {
|
||||
...originalRuntime,
|
||||
getBackendSrv: () =>
|
||||
({
|
||||
get: (uri: string) => mockSearcher.search(uri),
|
||||
}) as unknown as BackendSrv,
|
||||
};
|
||||
});
|
||||
|
||||
describe('Unified Storage Searcher', () => {
|
||||
it('should perform search with basic query', async () => {
|
||||
mockFolders.hits = [
|
||||
{
|
||||
name: 'folder1',
|
||||
title: 'Folder 1',
|
||||
resource: 'folders',
|
||||
} as SearchHit,
|
||||
];
|
||||
mockResults.hits = [
|
||||
{
|
||||
name: 'dashboard1',
|
||||
title: 'Dashboard 1',
|
||||
resource: 'dashboards',
|
||||
folder: 'folder1',
|
||||
} as SearchHit,
|
||||
];
|
||||
|
||||
const query: SearchQuery = {
|
||||
query: 'test',
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
const searcher = new UnifiedSearcher(mockFallbackSearcher);
|
||||
|
||||
const response = await searcher.search(query);
|
||||
|
||||
expect(response.view.length).toBe(1);
|
||||
expect(response.view.get(0).title).toBe('Dashboard 1');
|
||||
|
||||
const df = response.view.dataFrame;
|
||||
const locationInfo = df.meta?.custom?.locationInfo;
|
||||
expect(locationInfo).toBeDefined();
|
||||
expect(locationInfo?.folder1.name).toBe('Folder 1');
|
||||
});
|
||||
|
||||
it('should perform search and sync folders with missing folder', async () => {
|
||||
const mockFolders = {
|
||||
hits: [
|
||||
{
|
||||
name: 'folder2',
|
||||
title: 'Folder 2',
|
||||
resource: 'folders',
|
||||
} as SearchHit,
|
||||
],
|
||||
totalHits: 1,
|
||||
};
|
||||
|
||||
const mockResults = {
|
||||
hits: [
|
||||
{
|
||||
name: 'db1',
|
||||
title: 'DB 1',
|
||||
resource: 'dashboards',
|
||||
folder: 'folder1',
|
||||
} as SearchHit,
|
||||
{
|
||||
name: 'db2',
|
||||
title: 'DB 2',
|
||||
resource: 'dashboards',
|
||||
folder: 'folder2',
|
||||
} as SearchHit,
|
||||
],
|
||||
totalHits: 2,
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(mockSearcher, 'search')
|
||||
.mockResolvedValueOnce(mockFolders)
|
||||
.mockResolvedValueOnce(mockResults)
|
||||
.mockResolvedValueOnce(mockFolders);
|
||||
|
||||
const consoleWarn = jest.fn();
|
||||
jest.spyOn(console, 'warn').mockImplementationOnce(consoleWarn);
|
||||
|
||||
const query: SearchQuery = {
|
||||
query: 'test',
|
||||
limit: 50,
|
||||
};
|
||||
|
||||
const searcher = new UnifiedSearcher(mockFallbackSearcher);
|
||||
|
||||
const response = await searcher.search(query);
|
||||
|
||||
expect(response.view.length).toBe(1);
|
||||
expect(response.view.get(0).title).toBe('DB 2');
|
||||
|
||||
const df = response.view.dataFrame;
|
||||
const locationInfo = df.meta?.custom?.locationInfo;
|
||||
expect(locationInfo).toBeDefined();
|
||||
expect(locationInfo?.folder2.name).toBe('Folder 2');
|
||||
expect(consoleWarn).toHaveBeenCalled();
|
||||
expect(mockSearcher.search).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('can create dashboard search results and set meta sortBy so column is added for sprinkles sort field', () => {
|
||||
const mockHits: SearchHit[] = [
|
||||
{
|
||||
|
||||
@@ -112,7 +112,7 @@ export class UnifiedSearcher implements GrafanaSearcher {
|
||||
|
||||
async doSearchQuery(query: SearchQuery): Promise<QueryResponse> {
|
||||
const uri = await this.newRequest(query);
|
||||
const rsp = await getBackendSrv().get<SearchAPIResponse>(uri);
|
||||
const rsp = await this.fetchResponse(uri);
|
||||
|
||||
const first = toDashboardResults(rsp, query.sort ?? '');
|
||||
if (first.name === loadingFrameName) {
|
||||
@@ -120,12 +120,6 @@ export class UnifiedSearcher implements GrafanaSearcher {
|
||||
}
|
||||
|
||||
const meta = first.meta?.custom || ({} as SearchResultMeta);
|
||||
const locationInfo = await this.locationInfo;
|
||||
const hasMissing = rsp.hits.some((hit) => !locationInfo[hit.folder]);
|
||||
if (hasMissing) {
|
||||
// sync the location info ( folders )
|
||||
this.locationInfo = loadLocationInfo();
|
||||
}
|
||||
meta.locationInfo = await this.locationInfo;
|
||||
|
||||
// Set the field name to a better display name
|
||||
@@ -141,14 +135,13 @@ export class UnifiedSearcher implements GrafanaSearcher {
|
||||
let loadMax = 0;
|
||||
let pending: Promise<void> | undefined = undefined;
|
||||
const getNextPage = async () => {
|
||||
// TODO: implement this correctly
|
||||
while (loadMax > view.dataFrame.length) {
|
||||
const offset = view.dataFrame.length;
|
||||
if (offset >= meta.count) {
|
||||
return;
|
||||
}
|
||||
const nextPageUrl = `${uri}&offset=${offset}`;
|
||||
const resp = await getBackendSrv().get<SearchAPIResponse>(nextPageUrl);
|
||||
const resp = await this.fetchResponse(nextPageUrl);
|
||||
const frame = toDashboardResults(resp, query.sort ?? '');
|
||||
if (!frame) {
|
||||
console.log('no results', frame);
|
||||
@@ -198,6 +191,40 @@ export class UnifiedSearcher implements GrafanaSearcher {
|
||||
};
|
||||
}
|
||||
|
||||
async fetchResponse(uri: string) {
|
||||
const rsp = await getBackendSrv().get<SearchAPIResponse>(uri);
|
||||
const isFolderCacheStale = await this.isFolderCacheStale(rsp.hits);
|
||||
if (!isFolderCacheStale) {
|
||||
return rsp;
|
||||
}
|
||||
// sync the location info ( folders )
|
||||
this.locationInfo = loadLocationInfo();
|
||||
// recheck for missing folders
|
||||
const hasMissing = await this.isFolderCacheStale(rsp.hits);
|
||||
if (!hasMissing) {
|
||||
return rsp;
|
||||
}
|
||||
// we still have results here with folders we can't find
|
||||
// filter the results since we probably don't have access to that folder
|
||||
const locationInfo = await this.locationInfo;
|
||||
const hits = rsp.hits.filter((hit) => {
|
||||
if (hit.folder === undefined || locationInfo[hit.folder] !== undefined) {
|
||||
return true;
|
||||
}
|
||||
console.warn('Dropping search hit with missing folder', hit);
|
||||
return false;
|
||||
});
|
||||
const totalHits = rsp.totalHits - (rsp.hits.length - hits.length);
|
||||
return { ...rsp, hits, totalHits };
|
||||
}
|
||||
|
||||
async isFolderCacheStale(hits: SearchHit[]): Promise<boolean> {
|
||||
const locationInfo = await this.locationInfo;
|
||||
return hits.some((hit) => {
|
||||
return hit.folder !== undefined && locationInfo[hit.folder] === undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private async newRequest(query: SearchQuery): Promise<string> {
|
||||
query = await replaceCurrentFolderQuery(query);
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
"name": "@grafana-plugins/grafana-azure-monitor-datasource",
|
||||
"description": "Grafana data source for Azure Monitor",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/plugin-ui": "0.10.1",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"@kusto/monaco-kusto": "^10.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"i18next": "^24.0.0",
|
||||
@@ -25,8 +25,8 @@
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/plugin-configs": "11.6.0-pre",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.2.0",
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
"name": "@grafana-plugins/stackdriver",
|
||||
"description": "Grafana data source for Google Cloud Monitoring",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/google-sdk": "0.1.2",
|
||||
"@grafana/plugin-ui": "0.10.1",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"debounce-promise": "3.1.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"i18next": "^24.0.0",
|
||||
@@ -26,8 +26,8 @@
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/plugin-configs": "11.6.0-pre",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.2.0",
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
"name": "@grafana-plugins/grafana-postgresql-datasource",
|
||||
"description": "PostgreSQL data source plugin",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/plugin-ui": "0.10.1",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/sql": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/sql": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"lodash": "4.17.21",
|
||||
"react": "18.3.1",
|
||||
"rxjs": "7.8.1",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/plugin-configs": "11.6.0-pre",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/react": "16.2.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"name": "@grafana-plugins/grafana-pyroscope-datasource",
|
||||
"description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"lodash": "4.17.21",
|
||||
"monaco-editor": "0.34.1",
|
||||
@@ -20,7 +20,7 @@
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "11.6.0-pre",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.2.0",
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"name": "@grafana-plugins/grafana-testdata-datasource",
|
||||
"description": "Generates test data in different forms",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"d3-random": "^3.0.1",
|
||||
"lodash": "4.17.21",
|
||||
"micro-memoize": "^4.1.2",
|
||||
@@ -21,8 +21,8 @@
|
||||
"uuid": "11.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/plugin-configs": "11.6.0-pre",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/react": "16.2.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
|
||||
@@ -55,6 +55,10 @@ describe('graphiteDatasource', () => {
|
||||
|
||||
describe('convertResponseToDataFrames', () => {
|
||||
it('should transform regular result', () => {
|
||||
const refIDMap = {
|
||||
refIDA: 'A',
|
||||
refIDB: 'B',
|
||||
};
|
||||
const result = ctx.ds.convertResponseToDataFrames(
|
||||
createFetchResponse({
|
||||
meta: {
|
||||
@@ -65,7 +69,7 @@ describe('graphiteDatasource', () => {
|
||||
},
|
||||
series: [
|
||||
{
|
||||
target: 'seriesA',
|
||||
target: 'seriesA refIDA',
|
||||
datapoints: [
|
||||
[100, 200],
|
||||
[101, 201],
|
||||
@@ -85,7 +89,7 @@ describe('graphiteDatasource', () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
target: 'seriesB',
|
||||
target: 'seriesB refIDB',
|
||||
meta: [
|
||||
{
|
||||
'aggnum-norm': 1,
|
||||
@@ -105,7 +109,8 @@ describe('graphiteDatasource', () => {
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
}),
|
||||
refIDMap
|
||||
);
|
||||
|
||||
expect(result.data.length).toBe(2);
|
||||
@@ -113,24 +118,28 @@ describe('graphiteDatasource', () => {
|
||||
expect(getFrameDisplayName(result.data[1])).toBe('seriesB');
|
||||
expect(result.data[0].length).toBe(2);
|
||||
expect(result.data[0].meta.notices.length).toBe(1);
|
||||
expect(result.data[0].refId).toBe('A');
|
||||
expect(result.data[0].meta.notices[0].text).toBe('Data is rolled up, aggregated over 2h using Average function');
|
||||
expect(result.data[1].meta.notices).toBeUndefined();
|
||||
expect(result.data[1].refId).toBe('B');
|
||||
});
|
||||
});
|
||||
|
||||
describe('When querying graphite with one target using query editor target spec', () => {
|
||||
const query = {
|
||||
panelId: 3,
|
||||
dashboardId: 5,
|
||||
range: { from: dateTime('2022-04-01T00:00:00'), to: dateTime('2022-07-01T00:00:00') },
|
||||
targets: [{ target: 'prod1.count' }, { target: 'prod2.count' }],
|
||||
maxDataPoints: 500,
|
||||
};
|
||||
|
||||
let response: unknown;
|
||||
let requestOptions: BackendSrvRequest;
|
||||
|
||||
beforeEach(() => {
|
||||
const query = {
|
||||
panelId: 3,
|
||||
dashboardId: 5,
|
||||
range: { from: dateTime('2022-04-01T00:00:00'), to: dateTime('2022-07-01T00:00:00') },
|
||||
targets: [
|
||||
{ target: 'prod1.count', refId: 'A' },
|
||||
{ target: 'prod2.count', refId: 'B' },
|
||||
],
|
||||
maxDataPoints: 500,
|
||||
};
|
||||
fetchMock.mockImplementation((options) => {
|
||||
requestOptions = options;
|
||||
return of(
|
||||
@@ -164,8 +173,8 @@ describe('graphiteDatasource', () => {
|
||||
|
||||
it('should query correctly', () => {
|
||||
const params = requestOptions.data.split('&');
|
||||
expect(params).toContain('target=prod1.count');
|
||||
expect(params).toContain('target=prod2.count');
|
||||
expect(params).toContain(`target=${encodeURIComponent(`aliasSub(prod1.count, "(^.*$)", "\\1 A")`)}`);
|
||||
expect(params).toContain(`target=${encodeURIComponent(`aliasSub(prod2.count, "(^.*$)", "\\1 B")`)}`);
|
||||
expect(params).toContain('from=1648789200');
|
||||
expect(params).toContain('until=1656655200');
|
||||
});
|
||||
|
||||
@@ -209,6 +209,17 @@ export class GraphiteDatasource
|
||||
return merge(...streams);
|
||||
}
|
||||
|
||||
// Use this object to map the original refID of the query to our sanitised one
|
||||
const refIds: { [key: string]: string } = {};
|
||||
for (const target of options.targets) {
|
||||
// Sanitise the refID otherwise the Graphite query will fail
|
||||
const formattedRefId = target.refId.replaceAll(' ', '_');
|
||||
refIds[formattedRefId] = target.refId;
|
||||
// Use aliasSub to include the refID in the response series name. This allows us to set the refID on the frame.
|
||||
const updatedTarget = `aliasSub(${target.target}, "(^.*$)", "\\1 ${formattedRefId}")`;
|
||||
target.target = updatedTarget;
|
||||
}
|
||||
|
||||
// handle the queries here
|
||||
const graphOptions = {
|
||||
from: this.translateTime(options.range.from, false, options.timezone),
|
||||
@@ -243,7 +254,7 @@ export class GraphiteDatasource
|
||||
httpOptions.requestId = this.name + '.panelId.' + options.panelId;
|
||||
}
|
||||
|
||||
return this.doGraphiteRequest(httpOptions).pipe(map(this.convertResponseToDataFrames));
|
||||
return this.doGraphiteRequest(httpOptions).pipe(map((result) => this.convertResponseToDataFrames(result, refIds)));
|
||||
}
|
||||
|
||||
addTracingHeaders(
|
||||
@@ -267,7 +278,7 @@ export class GraphiteDatasource
|
||||
}
|
||||
}
|
||||
|
||||
convertResponseToDataFrames = (result: FetchResponse): DataQueryResponse => {
|
||||
convertResponseToDataFrames = (result: FetchResponse, refIdMap: { [key: string]: string }): DataQueryResponse => {
|
||||
const data: DataFrame[] = [];
|
||||
if (!result || !result.data) {
|
||||
return { data };
|
||||
@@ -282,6 +293,9 @@ export class GraphiteDatasource
|
||||
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
const s = series[i];
|
||||
// Retrieve the original refID of the query
|
||||
const [target, refId] = s.target.split(' ');
|
||||
s.target = target;
|
||||
|
||||
// Disables Grafana own series naming
|
||||
s.title = s.target;
|
||||
@@ -291,6 +305,8 @@ export class GraphiteDatasource
|
||||
}
|
||||
|
||||
const frame = toDataFrame(s);
|
||||
// Set the refID value on the frame
|
||||
frame.refId = refIdMap[refId];
|
||||
|
||||
// Metrictank metadata
|
||||
if (s.meta) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@grafana-plugins/jaeger",
|
||||
"description": "Jaeger plugin for Grafana",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "workspace:*",
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
"name": "@grafana-plugins/mssql",
|
||||
"description": "MSSQL data source plugin",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/plugin-ui": "0.10.1",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/sql": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/sql": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"lodash": "4.17.21",
|
||||
"react": "18.3.1",
|
||||
"rxjs": "7.8.1",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/plugin-configs": "11.6.0-pre",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/react": "16.2.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
|
||||
@@ -2,22 +2,22 @@
|
||||
"name": "@grafana-plugins/mysql",
|
||||
"description": "MySQL data source plugin",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/plugin-ui": "0.10.1",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/sql": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/sql": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"lodash": "4.17.21",
|
||||
"react": "18.3.1",
|
||||
"rxjs": "7.8.1",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/e2e-selectors": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/e2e-selectors": "11.6.0-pre",
|
||||
"@grafana/plugin-configs": "11.6.0-pre",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/react": "16.2.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"name": "@grafana-plugins/parca",
|
||||
"description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "11.5.0-pre",
|
||||
"@grafana/runtime": "11.5.0-pre",
|
||||
"@grafana/schema": "11.5.0-pre",
|
||||
"@grafana/ui": "11.5.0-pre",
|
||||
"@grafana/data": "11.6.0-pre",
|
||||
"@grafana/runtime": "11.6.0-pre",
|
||||
"@grafana/schema": "11.6.0-pre",
|
||||
"@grafana/ui": "11.6.0-pre",
|
||||
"lodash": "4.17.21",
|
||||
"monaco-editor": "0.34.1",
|
||||
"react": "18.3.1",
|
||||
@@ -18,7 +18,7 @@
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "11.6.0-pre",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/react": "16.2.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@grafana-plugins/tempo",
|
||||
"description": "Grafana plugin for the Tempo data source.",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "workspace:*",
|
||||
@@ -39,7 +39,7 @@
|
||||
"uuid": "11.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@grafana/plugin-configs": "11.5.0-pre",
|
||||
"@grafana/plugin-configs": "11.6.0-pre",
|
||||
"@testing-library/dom": "10.4.0",
|
||||
"@testing-library/jest-dom": "6.6.3",
|
||||
"@testing-library/react": "16.2.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@grafana-plugins/zipkin",
|
||||
"description": "Zipkin plugin for Grafana",
|
||||
"private": true,
|
||||
"version": "11.5.0-pre",
|
||||
"version": "11.6.0-pre",
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.13.5",
|
||||
"@grafana/data": "workspace:*",
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getRoutes as getDataConnectionsRoutes } from 'app/features/connections/
|
||||
import { DATASOURCES_ROUTES } from 'app/features/datasources/constants';
|
||||
import { ConfigureIRM } from 'app/features/gops/configuration-tracker/components/ConfigureIRM';
|
||||
import { getRoutes as getPluginCatalogRoutes } from 'app/features/plugins/admin/routes';
|
||||
import { getAppPluginRoutes } from 'app/features/plugins/routes';
|
||||
import { getAppPluginRoutes, getRouteForAppPlugin } from 'app/features/plugins/routes';
|
||||
import { getProfileRoutes } from 'app/features/profile/routes';
|
||||
import { AccessControlAction, DashboardRoutes } from 'app/types';
|
||||
|
||||
@@ -516,11 +516,17 @@ export function getAppRoutes(): RouteDescriptor[] {
|
||||
},
|
||||
config.featureToggles.exploreMetrics && {
|
||||
path: '/explore/metrics/*',
|
||||
chromeless: false,
|
||||
roles: () => contextSrv.evaluatePermission([AccessControlAction.DataSourcesExplore]),
|
||||
component: SafeDynamicImport(
|
||||
() => import(/* webpackChunkName: "DataTrailsPage"*/ 'app/features/trails/DataTrailsPage')
|
||||
),
|
||||
...(config.featureToggles.exploreMetricsUseExternalAppPlugin
|
||||
? {
|
||||
component: getRouteForAppPlugin('grafana-metricsdrilldown-app').component,
|
||||
}
|
||||
: {
|
||||
chromeless: false,
|
||||
component: SafeDynamicImport(
|
||||
() => import(/* webpackChunkName: "DataTrailsPage"*/ 'app/features/trails/DataTrailsPage')
|
||||
),
|
||||
}),
|
||||
},
|
||||
{
|
||||
path: '/bookmarks',
|
||||
|
||||
+56
-1
@@ -2956,6 +2956,9 @@
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -2995,10 +2998,15 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type",
|
||||
@@ -5465,6 +5473,14 @@
|
||||
"CounterResetHint": {
|
||||
"$ref": "#/components/schemas/CounterResetHint"
|
||||
},
|
||||
"CustomValues": {
|
||||
"description": "Holds the custom (usually upper) bounds for bucket definitions, otherwise nil.\nThis slice is interned, to be treated as immutable and copied by reference.\nThese numbers should be strictly increasing. This field is only used when the\nschema is for custom buckets, and the ZeroThreshold, ZeroCount, NegativeSpans\nand NegativeBuckets fields are not used in that case.",
|
||||
"items": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"PositiveBuckets": {
|
||||
"description": "Observation counts in buckets. Each represents an absolute count and\nmust be zero or positive.",
|
||||
"items": {
|
||||
@@ -5481,7 +5497,7 @@
|
||||
"type": "array"
|
||||
},
|
||||
"Schema": {
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8. They are all for\nbase-2 bucket schemas, where 1 is a bucket boundary in each case, and\nthen each power of two is divided into 2^n logarithmic buckets. Or\nin other words, each bucket boundary is the previous boundary times\n2^(2^-n).",
|
||||
"description": "Currently valid schema numbers are -4 \u003c= n \u003c= 8 for exponential buckets.\nThey are all for base-2 bucket schemas, where 1 is a bucket boundary in\neach case, and then each power of two is divided into 2^n logarithmic buckets.\nOr in other words, each bucket boundary is the previous boundary times\n2^(2^-n). Another valid schema number is -53 for custom buckets, defined by\nthe CustomValues field.",
|
||||
"format": "int32",
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -6122,6 +6138,9 @@
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"updated_by": {
|
||||
"$ref": "#/components/schemas/UserInfo"
|
||||
},
|
||||
"version": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
@@ -6206,6 +6225,12 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"GettableRuleVersions": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/GettableExtendedRuleNode"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"GettableStatus": {
|
||||
"properties": {
|
||||
"cluster": {
|
||||
@@ -9888,6 +9913,9 @@
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"health": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -9909,10 +9937,15 @@
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"uid",
|
||||
"name",
|
||||
"folderUid",
|
||||
"query",
|
||||
"health",
|
||||
"type"
|
||||
@@ -9952,6 +9985,9 @@
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"folderUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"interval": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
@@ -9981,6 +10017,7 @@
|
||||
"required": [
|
||||
"name",
|
||||
"file",
|
||||
"folderUid",
|
||||
"rules",
|
||||
"interval"
|
||||
],
|
||||
@@ -10083,6 +10120,10 @@
|
||||
"Sample": {
|
||||
"description": "Sample is a single sample belonging to a metric. It represents either a float\nsample or a histogram sample. If H is nil, it is a float sample. Otherwise,\nit is a histogram sample.",
|
||||
"properties": {
|
||||
"DropName": {
|
||||
"description": "DropName is used to indicate whether the __name__ label should be dropped\nas part of the query evaluation.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"F": {
|
||||
"format": "double",
|
||||
"type": "number"
|
||||
@@ -12106,6 +12147,18 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"UserInfo": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "UserInfo represents user-related information, including a unique identifier and a name.",
|
||||
"type": "object"
|
||||
},
|
||||
"UserLookupDTO": {
|
||||
"properties": {
|
||||
"avatarUrl": {
|
||||
@@ -12511,6 +12564,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/alertGroup"
|
||||
},
|
||||
@@ -12838,6 +12892,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/gettableSilence"
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ load(
|
||||
"enterprise_setup_step",
|
||||
"identify_runner_step",
|
||||
"lint_frontend_step",
|
||||
"verify_api_clients_step",
|
||||
"verify_i18n_step",
|
||||
"yarn_install_step",
|
||||
)
|
||||
@@ -35,6 +36,7 @@ def lint_frontend_pipeline(trigger, ver_mode):
|
||||
init_steps = []
|
||||
lint_step = lint_frontend_step()
|
||||
i18n_step = verify_i18n_step()
|
||||
api_clients_step = verify_api_clients_step()
|
||||
|
||||
volumes = []
|
||||
|
||||
@@ -54,6 +56,7 @@ def lint_frontend_pipeline(trigger, ver_mode):
|
||||
test_steps = [
|
||||
lint_step,
|
||||
i18n_step,
|
||||
api_clients_step,
|
||||
]
|
||||
|
||||
return pipeline(
|
||||
|
||||
@@ -676,6 +676,28 @@ def verify_i18n_step():
|
||||
],
|
||||
}
|
||||
|
||||
def verify_api_clients_step():
|
||||
uncommited_error_message = "\nAPI client generation has not been committed. Please run 'yarn generate-apis', commit the changes and push again."
|
||||
return {
|
||||
"name": "verify-api-clients",
|
||||
"image": images["node_deb"],
|
||||
"depends_on": [
|
||||
"yarn-install",
|
||||
],
|
||||
"commands": [
|
||||
"yarn generate-apis",
|
||||
# Verify that client generation has been run and committed
|
||||
'''
|
||||
file_diff=$(git diff ':!conf')
|
||||
if [ -n "$file_diff" ]; then
|
||||
echo $file_diff
|
||||
echo "{}"
|
||||
exit 1
|
||||
fi
|
||||
'''.format(uncommited_error_message),
|
||||
],
|
||||
}
|
||||
|
||||
def test_a11y_frontend_step(ver_mode, port = 3001):
|
||||
"""Runs automated accessiblity tests against the frontend.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ images = {
|
||||
"node_deb": "node:{}-bookworm".format(nodejs_version[:2]),
|
||||
"cloudsdk": "google/cloud-sdk:431.0.0",
|
||||
"publish": "grafana/grafana-ci-deploy:1.3.3",
|
||||
"alpine": "alpine:3.20.3",
|
||||
"alpine": "alpine:3.20.5",
|
||||
"ubuntu": "ubuntu:22.04",
|
||||
"curl": "byrnedo/alpine-curl:0.1.8",
|
||||
"plugins_slack": "plugins/slack",
|
||||
|
||||
+44
-27
@@ -129,33 +129,50 @@ function simplifySchemaName(schemaName: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const sourceDir = path.resolve(__dirname, '../pkg/tests/apis/openapi_snapshots');
|
||||
const outputDir = path.resolve(__dirname, '../data/openapi');
|
||||
|
||||
// Create the output directory if it doesn't exist
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(sourceDir).filter((file: string) => file.endsWith('.json'));
|
||||
|
||||
for (const file of files) {
|
||||
const inputPath = path.join(sourceDir, file);
|
||||
const outputPath = path.join(outputDir, file);
|
||||
|
||||
console.log(`Processing file "${file}"...`);
|
||||
|
||||
const fileContent = fs.readFileSync(inputPath, 'utf-8');
|
||||
|
||||
let inputSpec;
|
||||
try {
|
||||
inputSpec = JSON.parse(fileContent);
|
||||
} catch (err) {
|
||||
console.error(`Invalid JSON file "${file}". Skipping this file.`);
|
||||
continue;
|
||||
/**
|
||||
* Process all files in a source directory and write results to output directory
|
||||
*/
|
||||
function processDirectory(sourceDir: string, outputDir: string) {
|
||||
// Skip if source directory doesn't exist
|
||||
if (!fs.existsSync(sourceDir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const outputSpec = processOpenAPISpec(inputSpec);
|
||||
fs.writeFileSync(outputPath, JSON.stringify(outputSpec, null, 2), 'utf-8');
|
||||
console.log(`Processing completed for file "${file}".`);
|
||||
// Create the output directory if it doesn't exist
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(sourceDir).filter((file: string) => file.endsWith('.json'));
|
||||
|
||||
for (const file of files) {
|
||||
const inputPath = path.join(sourceDir, file);
|
||||
const outputPath = path.join(outputDir, file);
|
||||
|
||||
console.log(`Processing file "${file}"...`);
|
||||
|
||||
const fileContent = fs.readFileSync(inputPath, 'utf-8');
|
||||
|
||||
let inputSpec;
|
||||
try {
|
||||
inputSpec = JSON.parse(fileContent);
|
||||
} catch (err) {
|
||||
console.error(`Invalid JSON file "${file}". Skipping this file.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const outputSpec = processOpenAPISpec(inputSpec);
|
||||
fs.writeFileSync(outputPath, JSON.stringify(outputSpec, null, 2), 'utf-8');
|
||||
console.log(`Processing completed for file "${file}".`);
|
||||
}
|
||||
}
|
||||
|
||||
const sourceDirs = [
|
||||
path.resolve(import.meta.dirname, '../pkg/tests/apis/openapi_snapshots'),
|
||||
path.resolve(import.meta.dirname, '../pkg/extensions/apiserver/tests/openapi_snapshots'),
|
||||
];
|
||||
const outputDir = path.resolve(import.meta.dirname, '../data/openapi');
|
||||
|
||||
for (const sourceDir of sourceDirs) {
|
||||
processDirectory(sourceDir, outputDir);
|
||||
}
|
||||
@@ -2704,13 +2704,13 @@ __metadata:
|
||||
resolution: "@grafana-plugins/grafana-azure-monitor-datasource@workspace:public/app/plugins/datasource/azuremonitor"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-ui": "npm:0.10.1"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@kusto/monaco-kusto": "npm:^10.0.0"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/jest-dom": "npm:6.6.3"
|
||||
@@ -2748,13 +2748,13 @@ __metadata:
|
||||
resolution: "@grafana-plugins/grafana-postgresql-datasource@workspace:public/app/plugins/datasource/grafana-postgresql-datasource"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-ui": "npm:0.10.1"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/sql": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/sql": "npm:11.6.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/react": "npm:16.2.0"
|
||||
"@testing-library/user-event": "npm:14.6.1"
|
||||
@@ -2779,11 +2779,11 @@ __metadata:
|
||||
resolution: "@grafana-plugins/grafana-pyroscope-datasource@workspace:public/app/plugins/datasource/grafana-pyroscope-datasource"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.6.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/jest-dom": "npm:6.6.3"
|
||||
"@testing-library/react": "npm:16.2.0"
|
||||
@@ -2819,12 +2819,12 @@ __metadata:
|
||||
resolution: "@grafana-plugins/grafana-testdata-datasource@workspace:public/app/plugins/datasource/grafana-testdata-datasource"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.6.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/react": "npm:16.2.0"
|
||||
"@testing-library/user-event": "npm:14.6.1"
|
||||
@@ -2900,13 +2900,13 @@ __metadata:
|
||||
resolution: "@grafana-plugins/mssql@workspace:public/app/plugins/datasource/mssql"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-ui": "npm:0.10.1"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/sql": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/sql": "npm:11.6.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/react": "npm:16.2.0"
|
||||
"@testing-library/user-event": "npm:14.6.1"
|
||||
@@ -2931,13 +2931,13 @@ __metadata:
|
||||
resolution: "@grafana-plugins/mysql@workspace:public/app/plugins/datasource/mysql"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-ui": "npm:0.10.1"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/sql": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/sql": "npm:11.6.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/react": "npm:16.2.0"
|
||||
"@testing-library/user-event": "npm:14.6.1"
|
||||
@@ -2962,11 +2962,11 @@ __metadata:
|
||||
resolution: "@grafana-plugins/parca@workspace:public/app/plugins/datasource/parca"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.6.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/react": "npm:16.2.0"
|
||||
"@testing-library/user-event": "npm:14.6.1"
|
||||
@@ -2994,14 +2994,14 @@ __metadata:
|
||||
resolution: "@grafana-plugins/stackdriver@workspace:public/app/plugins/datasource/cloud-monitoring"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/google-sdk": "npm:0.1.2"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-ui": "npm:0.10.1"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/jest-dom": "npm:6.6.3"
|
||||
"@testing-library/react": "npm:16.2.0"
|
||||
@@ -3046,7 +3046,7 @@ __metadata:
|
||||
"@grafana/lezer-traceql": "npm:0.0.20"
|
||||
"@grafana/monaco-logql": "npm:^0.0.8"
|
||||
"@grafana/o11y-ds-frontend": "workspace:*"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-ui": "npm:0.10.1"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/schema": "workspace:*"
|
||||
@@ -3194,12 +3194,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/data@npm:11.5.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
|
||||
"@grafana/data@npm:11.6.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/data@workspace:packages/grafana-data"
|
||||
dependencies:
|
||||
"@braintree/sanitize-url": "npm:7.0.1"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
"@rollup/plugin-node-resolve": "npm:16.0.0"
|
||||
"@types/d3-interpolate": "npm:^3.0.0"
|
||||
@@ -3257,7 +3257,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/e2e-selectors@npm:11.5.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
|
||||
"@grafana/e2e-selectors@npm:11.6.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors"
|
||||
dependencies:
|
||||
@@ -3360,9 +3360,9 @@ __metadata:
|
||||
"@babel/preset-env": "npm:7.26.7"
|
||||
"@babel/preset-react": "npm:7.26.3"
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@leeoniya/ufuzzy": "npm:1.0.18"
|
||||
"@rollup/plugin-node-resolve": "npm:16.0.0"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
@@ -3468,13 +3468,13 @@ __metadata:
|
||||
resolution: "@grafana/o11y-ds-frontend@workspace:packages/grafana-o11y-ds-frontend"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-ui": "npm:0.10.1"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/jest-dom": "npm:^6.1.2"
|
||||
"@testing-library/react": "npm:16.2.0"
|
||||
@@ -3498,7 +3498,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/plugin-configs@npm:11.5.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs":
|
||||
"@grafana/plugin-configs@npm:11.6.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/plugin-configs@workspace:packages/grafana-plugin-configs"
|
||||
dependencies:
|
||||
@@ -3599,15 +3599,15 @@ __metadata:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@emotion/eslint-plugin": "npm:11.12.0"
|
||||
"@floating-ui/react": "npm:0.27.3"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/faro-web-sdk": "npm:1.12.3"
|
||||
"@grafana/llm": "npm:0.12.0"
|
||||
"@grafana/plugin-ui": "npm:0.10.1"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@hello-pangea/dnd": "npm:17.0.0"
|
||||
"@leeoniya/ufuzzy": "npm:1.0.18"
|
||||
"@lezer/common": "npm:1.2.3"
|
||||
@@ -3702,16 +3702,16 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/runtime@npm:11.5.0-pre, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime":
|
||||
"@grafana/runtime@npm:11.6.0-pre, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/runtime@workspace:packages/grafana-runtime"
|
||||
dependencies:
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/faro-web-sdk": "npm:^1.3.6"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@rollup/plugin-node-resolve": "npm:16.0.0"
|
||||
"@rollup/plugin-terser": "npm:0.4.4"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
@@ -3851,7 +3851,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/schema@npm:11.5.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
|
||||
"@grafana/schema@npm:11.6.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/schema@workspace:packages/grafana-schema"
|
||||
dependencies:
|
||||
@@ -3869,17 +3869,17 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/sql@npm:11.5.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql":
|
||||
"@grafana/sql@npm:11.6.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/sql@workspace:packages/grafana-sql"
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.13.5"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/plugin-ui": "npm:0.10.1"
|
||||
"@grafana/runtime": "npm:11.5.0-pre"
|
||||
"@grafana/runtime": "npm:11.6.0-pre"
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
"@grafana/ui": "npm:11.5.0-pre"
|
||||
"@grafana/ui": "npm:11.6.0-pre"
|
||||
"@react-awesome-query-builder/ui": "npm:6.6.4"
|
||||
"@testing-library/dom": "npm:10.4.0"
|
||||
"@testing-library/jest-dom": "npm:^6.1.2"
|
||||
@@ -3997,7 +3997,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/ui@npm:11.5.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
|
||||
"@grafana/ui@npm:11.6.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@grafana/ui@workspace:packages/grafana-ui"
|
||||
dependencies:
|
||||
@@ -4007,10 +4007,10 @@ __metadata:
|
||||
"@emotion/serialize": "npm:1.3.3"
|
||||
"@faker-js/faker": "npm:^9.0.0"
|
||||
"@floating-ui/react": "npm:0.27.3"
|
||||
"@grafana/data": "npm:11.5.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.5.0-pre"
|
||||
"@grafana/data": "npm:11.6.0-pre"
|
||||
"@grafana/e2e-selectors": "npm:11.6.0-pre"
|
||||
"@grafana/faro-web-sdk": "npm:^1.3.6"
|
||||
"@grafana/schema": "npm:11.5.0-pre"
|
||||
"@grafana/schema": "npm:11.6.0-pre"
|
||||
"@grafana/tsconfig": "npm:^2.0.0"
|
||||
"@hello-pangea/dnd": "npm:17.0.0"
|
||||
"@leeoniya/ufuzzy": "npm:1.0.18"
|
||||
@@ -8712,7 +8712,7 @@ __metadata:
|
||||
dependencies:
|
||||
"@emotion/css": "npm:11.11.2"
|
||||
"@grafana/data": "workspace:*"
|
||||
"@grafana/plugin-configs": "npm:11.5.0-pre"
|
||||
"@grafana/plugin-configs": "workspace:*"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/ui": "workspace:*"
|
||||
|
||||
Reference in new issue
Block a user