Merge branch 'table-ng' into adela/table-ng_datalinks_support

pull/100769/head
Adela Almasan 4 months ago
commit 50de82d636
  1. 601
      .betterer.results
  2. 3
      .github/CODEOWNERS
  3. 48
      .github/actions/setup-enterprise/action.yml
  4. 69
      .github/workflows/pr-backend-coverage.yml
  5. 132
      .github/workflows/pr-backend-unit-tests.yml
  6. 40
      .github/workflows/storybook-verification.yml
  7. 14
      Makefile
  8. 8
      apps/alerting/notifications/go.sum
  9. 1
      apps/investigations/go.mod
  10. 4
      apps/investigations/go.sum
  11. 1
      apps/playlist/go.mod
  12. 4
      apps/playlist/go.sum
  13. 10
      conf/defaults.ini
  14. 10
      conf/sample.ini
  15. 2
      docs/sources/administration/provisioning/index.md
  16. 2
      docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md
  17. 6
      docs/sources/alerting/monitor-status/view-alert-rules.md
  18. 4
      docs/sources/setup-grafana/configure-grafana/_index.md
  19. 1
      docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md
  20. 6
      e2e/cypress/support/e2e.js
  21. 6
      e2e/run-suite
  22. 4
      go.mod
  23. 8
      go.sum
  24. 4
      go.work.sum
  25. 4
      package.json
  26. 10
      packages/README.md
  27. 1
      packages/grafana-data/src/types/config.ts
  28. 1
      packages/grafana-data/src/types/featureToggles.gen.ts
  29. 4
      packages/grafana-plugin-configs/tsconfig.json
  30. 2
      packages/grafana-prometheus/src/datasource.ts
  31. 2
      packages/grafana-runtime/src/config.ts
  32. 16
      packages/grafana-ui/package.json
  33. 56
      packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx
  34. 4
      packages/grafana-ui/src/components/Table/TableRT/RowsList.tsx
  35. 105
      packages/grafana-ui/src/internal/index.ts
  36. 4
      pkg/aggregator/go.mod
  37. 8
      pkg/aggregator/go.sum
  38. 5
      pkg/api/accesscontrol.go
  39. 1
      pkg/api/dtos/frontend_settings.go
  40. 2
      pkg/api/frontendsettings.go
  41. 16
      pkg/apimachinery/utils/meta.go
  42. 14
      pkg/apis/dashboard/migration/conversion/conversion_test.go
  43. 3
      pkg/apis/folder/v0alpha1/register.go
  44. 21
      pkg/apis/provisioning/v0alpha1/types.go
  45. 32
      pkg/apis/provisioning/v0alpha1/zz_generated.deepcopy.go
  46. 74
      pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go
  47. 3
      pkg/apis/provisioning/v0alpha1/zz_generated.openapi_violation_exceptions.list
  48. 2
      pkg/apiserver/go.mod
  49. 4
      pkg/apiserver/go.sum
  50. 2
      pkg/build/go.mod
  51. 4
      pkg/build/go.sum
  52. 4
      pkg/build/wire/go.mod
  53. 8
      pkg/build/wire/go.sum
  54. 6
      pkg/codegen/go.mod
  55. 12
      pkg/codegen/go.sum
  56. 4
      pkg/codegen/jenny_go_spec.go
  57. 53
      pkg/expr/sql/parser_allow.go
  58. 85
      pkg/expr/sql/parser_allow_test.go
  59. 9
      pkg/generated/applyconfiguration/provisioning/v0alpha1/resourcecount.go
  60. 2
      pkg/infra/remotecache/redis_storage.go
  61. 3
      pkg/infra/remotecache/redis_storage_test.go
  62. 1020
      pkg/kinds/dashboard/dashboard_spec_gen.go
  63. 56
      pkg/kinds/librarypanel/librarypanel_spec_gen.go
  64. 54
      pkg/kinds/preferences/preferences_spec_gen.go
  65. 6
      pkg/plugins/codegen/go.mod
  66. 12
      pkg/plugins/codegen/go.sum
  67. 3
      pkg/plugins/manager/loader/loader_test.go
  68. 49
      pkg/promlib/converter/prom.go
  69. 45
      pkg/promlib/converter/prom_bench_test.go
  70. 2
      pkg/promlib/converter/prom_test.go
  71. 12
      pkg/promlib/converter/testdata/prom-matrix-histogram-no-labels-frame.jsonc
  72. 672
      pkg/promlib/converter/testdata/prom-matrix-histogram-partitioned-frame.jsonc
  73. 35407
      pkg/promlib/converter/testdata/prom-query-range-big-frame.jsonc
  74. 62468
      pkg/promlib/converter/testdata/prom-query-range-big.json
  75. 1777
      pkg/promlib/converter/testdata/prom-query-range-frame.jsonc
  76. 1
      pkg/promlib/converter/testdata/prom-query-range.json
  77. 12
      pkg/promlib/converter/testdata/prom-vector-histogram-no-labels-frame.jsonc
  78. 4
      pkg/promlib/go.mod
  79. 8
      pkg/promlib/go.sum
  80. 2
      pkg/registry/apis/dashboard/register.go
  81. 2
      pkg/registry/apis/dashboard/register_test.go
  82. 3
      pkg/server/wire.go
  83. 2
      pkg/services/auth/authimpl/auth_token.go
  84. 6
      pkg/services/authn/authnimpl/usage_stats.go
  85. 3
      pkg/services/authn/authnimpl/usage_stats_test.go
  86. 6
      pkg/services/authn/grpcutils/grpc_authenticator.go
  87. 22
      pkg/services/cleanup/cleanup.go
  88. 14
      pkg/services/dashboards/database/database.go
  89. 4
      pkg/services/dashboards/database/migrations/folder_uid_migrator.go
  90. 2
      pkg/services/datasources/service/store.go
  91. 7
      pkg/services/featuremgmt/registry.go
  92. 1
      pkg/services/featuremgmt/toggles_gen.csv
  93. 4
      pkg/services/featuremgmt/toggles_gen.go
  94. 537
      pkg/services/featuremgmt/toggles_gen.json
  95. 7
      pkg/services/folder/folderimpl/folder.go
  96. 2
      pkg/services/guardian/accesscontrol_guardian.go
  97. 1
      pkg/services/guardian/accesscontrol_guardian_test.go
  98. 23
      pkg/services/ngalert/models/alert_rule.go
  99. 79
      pkg/services/ngalert/models/alert_rule_test.go
  100. 11
      pkg/services/ngalert/models/testing.go
  101. Some files were not shown because too many files have changed in this diff Show More

File diff suppressed because it is too large Load Diff

@ -756,6 +756,7 @@ embed.go @grafana/grafana-as-code
/.github/pr-checks.json @tolzhabayev
/.github/pr-commands.json @tolzhabayev
/.github/renovate.json5 @grafana/frontend-ops
/.github/actions/setup-enterprise/action.yml @grafana/grafana-backend-group
/.github/actions/test-coverage-processor/action.yml @grafana/grafana-backend-group
/.github/actions/setup-grafana-bench/ @Proximyst
/.github/workflows/add-to-whats-new.yml @grafana/docs-tooling
@ -792,12 +793,14 @@ embed.go @grafana/grafana-as-code
/.github/workflows/pr-commands.yml @tolzhabayev
/.github/workflows/pr-patch-check.yml @grafana/grafana-developer-enablement-squad
/.github/workflows/pr-backend-unit-tests.yml @grafana/grafana-backend-group
/.github/workflows/pr-backend-coverage.yml @grafana/grafana-backend-group
/.github/workflows/sync-mirror.yml @grafana/grafana-developer-enablement-squad
/.github/workflows/publish-technical-documentation-next.yml @grafana/docs-tooling
/.github/workflows/publish-technical-documentation-release.yml @grafana/docs-tooling
/.github/workflows/remove-milestone.yml @grafana/grafana-developer-enablement-squad
/.github/workflows/scripts/json-file-to-job-output.js @grafana/plugins-platform-frontend
/.github/workflows/stale.yml @grafana/grafana-developer-enablement-squad
/.github/workflows/storybook-verification.yml @grafana/grafana-frontend-platform
/.github/workflows/update-changelog.yml @grafana/grafana-developer-enablement-squad
/.github/workflows/update-make-docs.yml @grafana/docs-tooling
/.github/workflows/scripts/kinds/verify-kinds.go @grafana/platform-monitoring

@ -0,0 +1,48 @@
name: 'Setup Grafana Enterprise'
description: 'Clones and sets up Grafana Enterprise repository for testing'
inputs:
github-app-name:
description: 'Name of the GitHub App in Vault'
required: false
default: 'grafana-ci-bot'
runs:
using: "composite"
steps:
- name: Retrieve GitHub App secrets
id: get-secrets
uses: grafana/shared-workflows/actions/get-vault-secrets@get-vault-secrets-v1.0.1
with:
repo_secrets: |
APP_ID=${{ inputs.github-app-name }}:app-id
APP_INSTALLATION_ID=${{ inputs.github-app-name }}:app-installation-id
PRIVATE_KEY=${{ inputs.github-app-name }}:private-key
- name: Generate GitHub App token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ env.APP_ID }}
private-key: ${{ env.PRIVATE_KEY }}
repositories: "grafana-enterprise"
owner: "grafana"
- name: Setup Enterprise
shell: bash
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
git clone https://x-access-token:${GH_TOKEN}@github.com/grafana/grafana-enterprise.git ../grafana-enterprise;
cd ../grafana-enterprise
if git checkout ${GITHUB_HEAD_REF}; then
echo "checked out ${GITHUB_HEAD_REF}"
elif git checkout ${GITHUB_BASE_REF}; then
echo "checked out ${GITHUB_BASE_REF}"
else
git checkout main
fi
./build.sh

@ -0,0 +1,69 @@
name: Coverage
on:
workflow_dispatch:
push:
branches:
- main
paths-ignore:
- 'docs/**'
- '**/*.md'
permissions:
contents: read
id-token: write
env:
EDITION: 'oss'
WIRE_TAGS: 'oss'
jobs:
main:
name: Backend Unit Tests
runs-on: ubuntu-latest-8-cores
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential shared-mime-info
go install github.com/mfridman/tparse@c1754a1f484ac5cd422697b0fec635177ddc8507 # v0.17.0
- name: Generate Go code
run: make gen-go
- name: Run unit tests
run: COVER_OPTS="-coverprofile=be-unit.cov -coverpkg=github.com/grafana/grafana/..." GO_TEST_OUTPUT="/tmp/unit.log" make test-go-unit-cov
- name: Process and upload coverage
uses: ./.github/actions/test-coverage-processor
with:
test-type: 'be-unit'
# Needs to be named 'unit.cov' based on the Makefile command `make test-go-unit`
coverage-file: 'unit.cov'
codecov-token: ${{ secrets.CODECOV_TOKEN }}
codecov-flag: 'be-unit'
codecov-name: 'be-unit'
- name: Install Grafana Bench
# We can't allow forks here, as we need secret access.
if: ${{ github.event_name != 'pull_request' }}
uses: ./.github/actions/setup-grafana-bench
- name: Process output for Bench
if: ${{ github.event_name != 'pull_request' }}
run: |
grafana-bench report \
--trigger pr-backend-unit-tests-oss \
--report-input go \
--report-output log \
--grafana-version "$(git rev-parse HEAD)" \
--suite-name grafana-oss-unit-tests \
/tmp/unit.log
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

@ -1,78 +1,104 @@
name: Backend Unit Tests
on:
workflow_dispatch:
push:
branches:
- main
paths-ignore:
- 'docs/**'
- '**/*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '**/*.md'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
id-token: write
env:
EDITION: 'oss'
WIRE_TAGS: 'oss'
jobs:
backend-testing-coverage:
name: Backend Testing & Coverage
runs-on: ubuntu-latest
grafana:
# Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`,
# the `pr-backend-unit-tests-enterprise` workflow will run instead
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
name: Grafana
runs-on: ubuntu-latest-8-cores
continue-on-error: true
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential shared-mime-info
go install github.com/mfridman/tparse@c1754a1f484ac5cd422697b0fec635177ddc8507 # v0.17.0
- name: Restore GOCACHE
uses: actions/cache/restore@v4
with:
key: go-test-cache-${{ github.ref_name }}
restore-keys: |
go-test-cache-${{ github.base_ref }}
go-test-cache-main
path: /home/runner/.cache/go-build
- name: Generate Go code
run: make gen-go
- name: Run unit tests
run: COVER_OPTS="-coverprofile=be-unit.cov -coverpkg=github.com/grafana/grafana/..." GO_TEST_OUTPUT="/tmp/unit.log" make test-go-unit
- name: Process and upload coverage
uses: ./.github/actions/test-coverage-processor
run: make test-go-unit
- name: "Generate token"
id: generate_token
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
with:
test-type: 'be-unit'
# Needs to be named 'unit.cov' based on the Makefile command `make test-go-unit`
coverage-file: 'unit.cov'
codecov-token: ${{ secrets.CODECOV_TOKEN }}
codecov-flag: 'be-unit'
codecov-name: 'be-unit'
- name: Install Grafana Bench
# We can't allow forks here, as we need secret access.
if: ${{ github.event_name != 'pull_request' }}
uses: ./.github/actions/setup-grafana-bench
- name: Process output for Bench
if: ${{ github.event_name != 'pull_request' }}
run: |
grafana-bench report \
--trigger pr-backend-unit-tests-oss \
--report-input go \
--report-output log \
--grafana-version "$(git rev-parse HEAD)" \
--suite-name grafana-oss-unit-tests \
/tmp/unit.log
app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
- name: Clear GOCACHE
run: gh cache delete go-test-cache-${{ github.ref_name }}
continue-on-error: true
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
- name: Save GOCACHE
uses: actions/cache/save@v4
with:
key: go-test-cache-${{ github.ref_name }}
path: /home/runner/.cache/go-build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
grafana-enterprise:
# Run this workflow for non-PR events (like pushes to `main` or `release-*`) OR for internal PRs (PRs not from forks)
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
name: Grafana Enterprise
runs-on: ubuntu-latest-8-cores
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Restore GOCACHE
uses: actions/cache/restore@v4
with:
key: go-test-cache-${{ github.ref_name }}-enterprise
restore-keys: |
go-test-cache-${{ github.base_ref }}-enterprise
go-test-cache-main-enterprise
path: /home/runner/.cache/go-build
- name: Setup Enterprise
uses: ./.github/actions/setup-enterprise
with:
github-app-name: 'grafana-ci-bot'
- name: Generate Go code
run: make gen-go
- name: Run unit tests
run: make test-go-unit
- name: "Generate token"
id: generate_token
uses: tibdex/github-app-token@b62528385c34dbc9f38e5f4225ac829252d1ea92
with:
app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }}
private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }}
- name: Clear GOCACHE
run: gh cache delete go-test-cache-${{ github.ref_name }}-enterprise
continue-on-error: true
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
- name: Save GOCACHE
uses: actions/cache/save@v4
with:
key: go-test-cache-${{ github.ref_name }}-enterprise
path: /home/runner/.cache/go-build

@ -0,0 +1,40 @@
name: Verify Storybook
on:
pull_request:
paths:
- 'packages/grafana-ui/**'
- '.github/workflows/storybook-verification.yml'
- '!docs/**'
- '!*.md'
jobs:
verify-storybook:
name: Verify Storybook
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: 'package.json'
cache: 'yarn'
- name: Install dependencies
run: yarn install --immutable
- name: Run Storybook and E2E tests
uses: cypress-io/github-action@v6
with:
browser: chrome
start: yarn storybook --quiet
wait-on: 'http://localhost:9001'
wait-on-timeout: 60
command: yarn e2e:storybook
install: false
env:
HOST: localhost
PORT: 9001

@ -18,6 +18,9 @@ GO_BUILD_FLAGS += $(if $(GO_BUILD_DEV),-dev)
GO_BUILD_FLAGS += $(if $(GO_BUILD_TAGS),-build-tags=$(GO_BUILD_TAGS))
GO_BUILD_FLAGS += $(GO_RACE_FLAG)
GO_TEST_OUTPUT := $(shell [ -n "$(GO_TEST_OUTPUT)" ] && echo '-json | tee $(GO_TEST_OUTPUT) | tparse -all')
GO_UNIT_COVERAGE ?= true
GO_UNIT_COVER_PROFILE ?= unit.cov
GO_INTEGRATION_COVER_PROFILE ?= integration.cov
GIT_BASE = remotes/origin/main
# GNU xargs has flag -r, and BSD xargs (e.g. MacOS) has that behaviour by default
@ -246,8 +249,13 @@ test-go: test-go-unit test-go-integration
.PHONY: test-go-unit
test-go-unit: ## Run unit tests for backend with flags.
@echo "test backend unit tests"
$(GO) test $(GO_RACE_FLAG) -short -covermode=atomic -coverprofile=unit.cov -timeout=30m $(GO_TEST_FILES) $(GO_TEST_OUTPUT)
@echo "backend unit tests"
$(GO) test $(GO_RACE_FLAG) -v -short -timeout=30m $(GO_TEST_FILES) $(GO_TEST_OUTPUT)
.PHONY: test-go-unit-cov
test-go-unit-cov: ## Run unit tests for backend with flags and coverage
@echo "backend unit tests with coverage"
$(GO) test $(GO_RACE_FLAG) -v -short $(if $(filter true,$(GO_UNIT_COVERAGE)),-covermode=atomic -coverprofile=$(GO_UNIT_COVER_PROFILE) $(if $(GO_UNIT_TEST_COVERPKG),-coverpkg=$(GO_UNIT_TEST_COVERPKG)),) -timeout=30m $(GO_TEST_FILES) $(GO_TEST_OUTPUT)
.PHONY: test-go-unit-pretty
test-go-unit-pretty: check-tparse
@ -260,7 +268,7 @@ test-go-unit-pretty: check-tparse
.PHONY: test-go-integration
test-go-integration: ## Run integration tests for backend with flags.
@echo "test backend integration tests"
$(GO) test $(GO_RACE_FLAG) -count=1 -run "^TestIntegration" -covermode=atomic -coverprofile=integration.cov -timeout=5m $(GO_INTEGRATION_TESTS) $(GO_TEST_OUTPUT)
$(GO) test $(GO_RACE_FLAG) -count=1 -run "^TestIntegration" -covermode=atomic -coverprofile=$(GO_INTEGRATION_COVER_PROFILE) -timeout=5m $(GO_INTEGRATION_TESTS) $(GO_TEST_OUTPUT)
.PHONY: test-go-integration-alertmanager
test-go-integration-alertmanager: ## Run integration tests for the remote alertmanager (config taken from the mimir_backend block).

@ -214,8 +214,8 @@ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -252,8 +252,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -72,6 +72,7 @@ require (
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.30.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect

@ -181,8 +181,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -73,6 +73,7 @@ require (
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.30.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect

@ -181,8 +181,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -198,7 +198,7 @@ type = database
# cache connectionstring options
# database: will use Grafana primary database.
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'.
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,username=grafana,password=grafanaRocks,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'.
# memcache: 127.0.0.1:11211
connstr =
@ -1406,6 +1406,14 @@ resolved_alert_retention = 15m
# 0 value means no limit
rule_version_record_limit = 0
# The retention period for deleted alerting rules.
# Determines how long deleted rules are retained before being permanently removed.
# The retention duration must be specified using a time format with unit suffixes
# such as ms, s, m, h, d (e.g., 30d for 30 days).
# Default: 30d
# 0 value means that rules are deleted permanently immediately.
deleted_rule_retention = 30d
[unified_alerting.screenshots]
# Enable screenshots in notifications. You must have either installed the Grafana image rendering
# plugin, or set up Grafana to use a remote rendering service.

@ -197,7 +197,7 @@
# cache connectionstring options
# database: will use Grafana primary database.
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'.
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,username=grafana,password=grafanaRocks,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'.
# memcache: 127.0.0.1:11211
;connstr =
@ -1389,6 +1389,14 @@
# 0 value means no limit
;rule_version_record_limit= 0
# The retention period for deleted alerting rules.
# Determines how long deleted rules are retained before being permanently removed.
# The retention duration must be specified using a time format with unit suffixes
# such as ms, s, m, h, d (e.g., 30d for 30 days).
# Default: 30d
# 0 value means that rules are deleted permanently immediately.
;deleted_rule_retention = 30d
[unified_alerting.screenshots]
# Enable screenshots in notifications. You must have either installed the Grafana image rendering
# plugin, or set up Grafana to use a remote rendering service.

@ -233,7 +233,7 @@ Data sources tagged with _HTTP\*_ communicate using the HTTP protocol, which inc
| keepCookies | array | _HTTP\*_ | Cookies that needs to be passed along while communicating with data sources |
| prometheusVersion | string | Prometheus | The version of the Prometheus data source, such as `2.37.0`, `2.24.0` |
| prometheusType | string | Prometheus | Prometheus database type. Options are `Prometheus`, `Cortex`, `Mimir` or`Thanos`. |
| cacheLevel | string | Prometheus | Determines the duration of the browser cache. Valid values include: `Low`, `Medium`, `High`, and `None`. This field is configurable when you enable the `prometheusResourceBrowserCache` feature flag. |
| cacheLevel | string | Prometheus | Determines the duration of the browser cache. Valid values include: `Low`, `Medium`, `High`, and `None`. |
| incrementalQuerying | string | Prometheus | Experimental: Turn on incremental querying to enhance dashboard reload performance with slow data sources |
| incrementalQueryOverlapWindow | string | Prometheus | Experimental: Configure incremental query overlap window. Requires a valid duration string, i.e. `180s` or `15m` Default value is `10m` (10 minutes). |
| disableRecordingRules | boolean | Prometheus | Experimental: Turn off Prometheus recording rules |

@ -55,7 +55,7 @@ Alert instances are grouped together if they have the same exact label values fo
For example, given the `Group by` option set to the `team` label:
- `alertname:foo, team=frontend`, and `alertname:bar, team=frontend` are in one group.
- `alertname:foo, team=backend`, and `alertname:qux, team=backend` are in another group.
- `alertname:foo, team=frontend`, and `alertname:qux, team=backend` are in another group.
### Group by alert rule or labels

@ -59,13 +59,13 @@ Select a group to expand it and view the list of alert rules within that group.
For details on how rule states and alert instance states are displayed, refer to [View alert state](ref:view-alert-state).
## View and compare alert rules versions.
## View, compare and restore alert rules versions.
To view previous alert rules for an alert, complete the following steps.
To view or restore previous versions for an alert rule, complete the following steps.
1. Navigate to **Alerts & IRM -> Alerting -> Alert rules**.
1. Select an alert rule and click **View**.
1. Click the **Versions** tab.
The page displays a list of the previous rule versions.
On the Alert rule's Versions page you can view and compare the previous rule versions.
On the Alert rule's Versions page you can view, compare and restore the previous rule versions.

@ -491,11 +491,13 @@ Leave empty when using `database` and Grafana uses the primary database.
##### `redis`
Example connection string: `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`
Example connection string: `addr=127.0.0.1:6379,pool_size=100,db=0,username=grafana,password=grafanaRocks,ssl=false`
- `addr` is the host `:` port of the Redis server.
- `pool_size` (optional) is the number of underlying connections that can be made to Redis.
- `db` (optional) is the number identifier of the Redis database you want to use.
- `username` (optional) is the connection identifier to authenticate the current connection.
- `password` (optional) is the connection secret to authenticate the current connection.
- `ssl` (optional) is if SSL should be used to connect to Redis server. The value may be `true`, `false`, or `insecure`. Setting the value to `insecure` skips verification of the certificate chain and hostname when making the connection.
##### `memcache`

@ -215,7 +215,6 @@ Experimental features might be changed or removed without prior notice.
| `enableSCIM` | Enables SCIM support for user and group management |
| `crashDetection` | Enables browser crash detection reporting to Faro. |
| `jaegerBackendMigration` | Enables querying the Jaeger data source without the proxy |
| `useV2DashboardsAPI` | Use the v2 kubernetes API in the frontend for dashboards |
| `unifiedHistory` | Displays the navigation history so the user can navigate back to previous pages |
| `investigationsBackend` | Enable the investigations backend API |
| `k8SFolderCounts` | Enable folder's api server counts |

@ -51,8 +51,8 @@ beforeEach(() => {
cy.setLocalStorage('grafana.featureToggles', 'dashboardScene=false');
}
if (Cypress.env('useV2DashboardsAPI')) {
cy.logToConsole('enabling v2 dashboards API in localstorage');
cy.setLocalStorage('grafana.featureToggles', 'useV2DashboardsAPI=true');
if (Cypress.env('kubernetesDashboards')) {
cy.logToConsole('enabling kubernetes dashboards API in localstorage');
cy.setLocalStorage('grafana.featureToggles', 'kubernetesDashboards=true');
}
});

@ -28,7 +28,7 @@ declare -A env=(
testFilesForSingleSuite="*.spec.ts"
rootForEnterpriseSuite="./e2e/extensions-suite"
rootForOldArch="./e2e/old-arch"
rootForDashboardsSchemaV2="./e2e/dashboards-suite"
rootForKubernetesDashboards="./e2e/dashboards-suite"
declare -A cypressConfig=(
[screenshotsFolder]=./e2e/"${args[0]}"/screenshots
@ -113,8 +113,8 @@ case "$1" in
env[DISABLE_SCENES]=true
;;
"dashboards-schema-v2")
env[useV2DashboardsAPI]=true
cypressConfig[specPattern]=$rootForDashboardsSchemaV2/$testFilesForSingleSuite
env[kubernetesDashboards]=true
cypressConfig[specPattern]=$rootForKubernetesDashboards/$testFilesForSingleSuite
cypressConfig[video]=false
case "$2" in
"debug")

@ -171,13 +171,13 @@ require (
gocloud.dev v0.40.0 // @grafana/grafana-app-platform-squad
golang.org/x/crypto v0.35.0 // @grafana/grafana-backend-group
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // @grafana/alerting-backend
golang.org/x/mod v0.22.0 // indirect; @grafana/grafana-backend-group
golang.org/x/mod v0.23.0 // indirect; @grafana/grafana-backend-group
golang.org/x/net v0.36.0 // @grafana/oss-big-tent @grafana/partner-datasources
golang.org/x/oauth2 v0.27.0 // @grafana/identity-access-team
golang.org/x/sync v0.11.0 // @grafana/alerting-backend
golang.org/x/text v0.22.0 // @grafana/grafana-backend-group
golang.org/x/time v0.9.0 // @grafana/grafana-backend-group
golang.org/x/tools v0.29.0 // indirect; @grafana/grafana-as-code
golang.org/x/tools v0.30.0 // indirect; @grafana/grafana-as-code
gonum.org/v1/gonum v0.15.1 // @grafana/oss-big-tent
google.golang.org/api v0.220.0 // @grafana/grafana-backend-group
google.golang.org/grpc v1.70.0 // @grafana/plugins-platform-backend

@ -2655,8 +2655,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -3052,8 +3052,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -692,7 +692,6 @@ github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiG
github.com/couchbase/moss v0.2.0 h1:VCYrMzFwEryyhRSeI+/b3tRBSeTpi/8gn5Kf6dxqn+o=
github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
@ -923,6 +922,7 @@ github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0/go.mod h1:7t5XR+2IA8P
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
@ -1197,7 +1197,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
@ -1448,6 +1447,7 @@ golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=

@ -73,7 +73,7 @@
"devDependencies": {
"@babel/core": "7.26.9",
"@babel/preset-env": "7.26.9",
"@babel/runtime": "7.26.9",
"@babel/runtime": "7.26.10",
"@betterer/betterer": "5.4.0",
"@betterer/cli": "5.4.0",
"@cypress/webpack-preprocessor": "6.0.2",
@ -270,7 +270,7 @@
"@grafana/flamegraph": "workspace:*",
"@grafana/google-sdk": "0.1.2",
"@grafana/lezer-logql": "0.2.7",
"@grafana/llm": "0.12.0",
"@grafana/llm": "0.13.2",
"@grafana/monaco-logql": "^0.0.8",
"@grafana/o11y-ds-frontend": "workspace:*",
"@grafana/plugin-ui": "0.10.1",

@ -1,6 +1,14 @@
# Grafana frontend packages
This document contains information about Grafana frontend package versioning and releases.
## Exporting code conventions
`@grafana/ui` makes use of `exports` in package.json to define three entrypoints that Grafana core and Grafana plugins can access. Before exposing anything in this package please consider the table below.
| Entrypoint Name | Import Path | Description | Available to Grafana | Available to plugins |
| --------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | -------------------- |
| `./` | `@grafana/ui` | The public API entrypoint. If the code is stable and you want to share it everywhere, this is the place to export it. | ✅ | ✅ |
| `./unstable` | `@grafana/ui/unstable` | The public API entrypoint for all experimental code. If you want to iterate and test code from Grafana and plugins, this is the place to export it. | ✅ | ✅ |
| `./internal` | `@grafana/ui/internal` | The private API entrypoint for internal code shared with Grafana. If you need to import code in Grafana but don't want to expose it to plugins, this is the place to export it. | ✅ | ❌ |
## Versioning

@ -195,7 +195,6 @@ export interface GrafanaConfig {
passwordHint: string;
loginError?: string;
viewersCanEdit: boolean;
editorsCanAdmin: boolean;
disableSanitizeHtml: boolean;
trustedTypesDefaultPolicyEnabled: boolean;
cspReportOnlyEnabled: boolean;

@ -226,7 +226,6 @@ export interface FeatureToggles {
alertingUIOptimizeReducer?: boolean;
azureMonitorEnableUserAuth?: boolean;
alertingNotificationsStepMode?: boolean;
useV2DashboardsAPI?: boolean;
feedbackButton?: boolean;
unifiedStorageSearchUI?: boolean;
elasticsearchCrossClusterSearch?: boolean;

@ -3,12 +3,14 @@
"jsx": "react-jsx",
"alwaysStrict": true,
"declaration": false,
"resolveJsonModule": true
"resolveJsonModule": true,
"moduleResolution": "bundler"
},
"ts-node": {
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"moduleResolution": "Node",
"esModuleInterop": true
},
"transpileOnly": true

@ -691,7 +691,7 @@ export class PrometheusDatasource
// By implementing getTagKeys and getTagValues we add ad-hoc filters functionality
async getTagValues(options: DataSourceGetTagValuesOptions<PromQuery>) {
const requestId = `[${this.uid}][${options.key}]`;
if (config.featureToggles.promQLScope) {
if (config.featureToggles.promQLScope && (options?.scopes?.length ?? 0) > 0) {
return (
await this.languageProvider.fetchSuggestions(
options.timeRange,

@ -103,7 +103,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
passwordHint = '';
loginError: string | undefined = undefined;
viewersCanEdit = false;
editorsCanAdmin = false;
disableSanitizeHtml = false;
trustedTypesDefaultPolicyEnabled = false;
cspReportOnlyEnabled = false;
@ -229,7 +228,6 @@ export class GrafanaBootConfig implements GrafanaConfig {
env: 'production',
},
viewersCanEdit: false,
editorsCanAdmin: false,
disableSanitizeHtml: false,
};

@ -18,6 +18,22 @@
},
"main": "src/index.ts",
"types": "src/index.ts",
"module": "src/index.ts",
"exports": {
"./package.json": "./package.json",
".": {
"import": "./src/index.ts",
"require": "./src/index.ts"
},
"./internal": {
"import": "./src/internal/index.ts",
"require": "./src/internal/index.ts"
},
"./unstable": {
"import": "./src/unstable.ts",
"require": "./src/unstable.ts"
}
},
"publishConfig": {
"main": "./dist/cjs/index.cjs",
"module": "./dist/esm/index.mjs",

@ -1,5 +1,5 @@
import { css, cx } from '@emotion/css';
import { CSSProperties, PointerEvent, ReactElement, ReactNode, useId, useRef, useState } from 'react';
import { CSSProperties, ReactElement, ReactNode, useId, useRef, useState } from 'react';
import * as React from 'react';
import { useMeasure, useToggle } from 'react-use';
@ -143,7 +143,6 @@ export function PanelChrome({
onFocus,
onMouseMove,
onMouseEnter,
onDragStart,
showMenuAlways = false,
}: PanelChromeProps) {
const theme = useTheme2();
@ -151,7 +150,7 @@ export function PanelChrome({
const panelContentId = useId();
const panelTitleId = useId().replace(/:/g, '_');
const { isSelected, onSelect, isSelectable } = useElementSelection(selectionId);
const pointerDownEvt = useRef<PointerEvent | null>(null);
const pointerDownPos = useRef<{ screenX: number; screenY: number }>({ screenX: 0, screenY: 0 });
const hasHeader = !hoverHeader;
@ -196,6 +195,33 @@ export function PanelChrome({
const testid = typeof title === 'string' ? selectors.components.Panels.Panel.title(title) : 'Panel';
// Handle drag & selection events
// Mainly the tricky bit of differentiating between dragging and selecting
const onPointerUp = (evt: React.PointerEvent) => {
evt.stopPropagation();
const distance = Math.sqrt(
Math.pow(pointerDownPos.current.screenX - evt.screenX, 2) +
Math.pow(pointerDownPos.current.screenY - evt.screenY, 2)
);
// If we are dragging some distance or clicking on elements that should cancel dragging (panel menu, etc)
if (
distance > 10 ||
(dragClassCancel && evt.target instanceof HTMLElement && evt.target.closest(`.${dragClassCancel}`))
) {
return;
}
onSelect?.(evt);
};
const onPointerDown = (evt: React.PointerEvent) => {
evt.stopPropagation();
pointerDownPos.current = { screenX: evt.screenX, screenY: evt.screenY };
};
const headerContent = (
<>
{/* Non collapsible title */}
@ -321,30 +347,10 @@ export function PanelChrome({
className={cx(styles.headerContainer, dragClass)}
style={headerStyles}
data-testid="header-container"
onPointerDown={(evt) => {
evt.stopPropagation();
pointerDownEvt.current = evt;
}}
onPointerMove={() => {
if (pointerDownEvt.current) {
onDragStart?.(pointerDownEvt.current);
pointerDownEvt.current = null;
}
}}
onPointerDown={onPointerDown}
onMouseEnter={isSelectable ? onHeaderEnter : undefined}
onMouseLeave={isSelectable ? onHeaderLeave : undefined}
onPointerUp={(evt) => {
evt.stopPropagation();
if (
pointerDownEvt.current &&
dragClassCancel &&
evt.target instanceof HTMLElement &&
!evt.target.closest(`.${dragClassCancel}`)
) {
onSelect?.(pointerDownEvt.current);
pointerDownEvt.current = null;
}
}}
onPointerUp={onPointerUp}
>
{statusMessage && (
<div className={dragClassCancel}>

@ -351,7 +351,9 @@ export const RowsList = (props: RowsListProps) => {
rowStyled={rowBg !== undefined}
rowExpanded={rowExpanded}
textWrapped={textWrapFinal !== undefined}
height={Number(style.height)}
// VariableSizeList overrides calculated in buildCellContainerStyle height of the cell,
// so we need to subtract 1 to respect the row border
height={Number(style.height) - 1}
getActions={getActions}
replaceVariables={replaceVariables}
setInspectCell={setInspectCell}

@ -0,0 +1,105 @@
/**
* This file is used to share internal grafana/ui code with Grafana core.
* Note that these exports are also used within Enterprise.
*
* Through the exports declared in package.json we can import this code in core Grafana and the grafana/ui
* package will continue to be able to access all code when it's published to npm as it's private to the package.
*
* During the yarn pack lifecycle the exports[./internal] property is deleted from the package.json
* preventing the code from being importable by plugins or other npm packages making it truly "internal".
*
*/
export { UPlotChart } from '../components/uPlot/Plot';
export { type AxisProps, UPLOT_AXIS_FONT_SIZE, timeUnitSize } from '../components/uPlot/config/UPlotAxisBuilder';
export {
type Renderers,
UPlotConfigBuilder,
type UPlotConfigPrepFn,
} from '../components/uPlot/config/UPlotConfigBuilder';
export { type ScaleProps } from '../components/uPlot/config/UPlotScaleBuilder';
export {
pluginLog,
preparePlotData2,
getStackingGroups,
getDisplayValuesForCalcs,
type StackingGroup,
} from '../components/uPlot/utils';
export { hasVisibleLegendSeries, PlotLegend } from '../components/uPlot/PlotLegend';
export { getScaleGradientFn } from '../components/uPlot/config/gradientFills';
export { buildScaleKey } from '../components/uPlot/internal';
export { CloseButton } from '../components/uPlot/plugins/CloseButton';
export { type TimeRange2, TooltipHoverMode } from '../components/uPlot/plugins/TooltipPlugin2';
export type { FacetedData, FacetSeries } from '../components/uPlot/types';
export { getResponsiveStyle, type ResponsiveProp } from '../components/Layout/utils/responsiveness';
export { ColorSwatch } from '../components/ColorPicker/ColorSwatch';
export { FieldNamePicker } from '../components/MatchersUI/FieldNamePicker';
export { comparisonOperationOptions } from '../components/MatchersUI/FieldValueMatcher';
export {
frameHasName,
getFrameFieldsDisplayNames,
useFieldDisplayNames,
useSelectOptions,
} from '../components/MatchersUI/utils';
export type { FieldMatcherUIRegistryItem } from '../components/MatchersUI/types';
export { RefIDMultiPicker, RefIDPicker, stringsToRegexp } from '../components/MatchersUI/FieldsByFrameRefIdMatcher';
export { allFieldTypeIconOptions } from '../components/MatchersUI/FieldTypeMatcherEditor';
export { getStyles as getSliderStyles } from '../components/Slider/styles';
export { getSelectStyles } from '../components/Select/getSelectStyles';
export type { Props as InputProps } from '../components/Input/Input';
export type { ModalsContextState } from '../components/Modal/ModalsContext';
export { getModalStyles } from '../components/Modal/getModalStyles';
export { MultiValueRemove, type MultiValueRemoveProps } from '../components/Select/MultiValue';
export { getSvgSize } from '../components/Icon/utils';
export { LoadingIndicator } from '../components/PanelChrome/LoadingIndicator';
export { type ButtonLinkProps, getButtonStyles } from '../components/Button';
export {
type TableSortByFieldState,
type TableFieldOptions,
TableCellDisplayMode,
FILTER_FOR_OPERATOR,
FILTER_OUT_OPERATOR,
} from '../components/Table/types';
export { defaultSparklineCellConfig } from '../components/Table/Cells/SparklineCell';
export { TableCell } from '../components/Table/Cells/TableCell';
export { useTableStyles } from '../components/Table/TableRT/styles';
export { Trans } from '../utils/i18n';
export { migrateTableDisplayModeToCellOptions } from '../components/Table/utils';
export { type DataLinksContextMenuApi } from '../components/DataLinks/DataLinksContextMenu';
export { MenuDivider } from '../components/Menu/MenuDivider';
export { AbstractList } from '../components/List/AbstractList';
export type { HttpSettingsBaseProps, AzureAuthSettings } from '../components/DataSourceSettings/types';
export { TimeZoneOffset, formatUtcOffset } from '../components/DateTimePickers/TimeZonePicker/TimeZoneOffset';
export { TimeZoneTitle } from '../components/DateTimePickers/TimeZonePicker/TimeZoneTitle';
export type { CodeEditorProps } from '../components/Monaco/types';
export { type Props as InlineFieldProps } from '../components/Forms/InlineField';
export { DataLinkSuggestions } from '../components/DataLinks/DataLinkSuggestions';
export { type Props as AlertProps } from '../components/Alert/Alert';
export { type TooltipPlacement } from '../components/Tooltip';
export { ConfirmContent, type ConfirmContentProps } from '../components/ConfirmModal/ConfirmContent';
export { EmotionPerfTest } from '../components/ThemeDemos/EmotionPerfTest';
export { VizTooltipContent } from '../components/VizTooltip/VizTooltipContent';
export { VizTooltipFooter } from '../components/VizTooltip/VizTooltipFooter';
export { VizTooltipHeader } from '../components/VizTooltip/VizTooltipHeader';
export { VizTooltipWrapper } from '../components/VizTooltip/VizTooltipWrapper';
export { VizTooltipRow } from '../components/VizTooltip/VizTooltipRow';
export { getContentItems } from '../components/VizTooltip/utils';
export { ColorIndicator, ColorPlacement, type VizTooltipItem } from '../components/VizTooltip/types';
export { mapMouseEventToMode } from '../components/VizLegend/utils';
export { getFocusStyles, getMouseFocusStyles, getTooltipContainerStyles } from '../themes/mixins';
export { optsWithHideZeros } from '../options/builder/tooltip';
export { StackingEditor } from '../options/builder/stacking';
export { addHideFrom } from '../options/builder/hideSeries';
export { ScaleDistributionEditor } from '../options/builder/axis';
export { useComponentInstanceId } from '../utils/useComponetInstanceId';
export { closePopover } from '../utils/closePopover';
export { flattenTokens } from '../slate-plugins/slate-prism';

@ -136,7 +136,7 @@ require (
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/oauth2 v0.27.0 // indirect
golang.org/x/sync v0.11.0 // indirect
@ -144,7 +144,7 @@ require (
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.29.0 // indirect
golang.org/x/tools v0.30.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect

@ -408,8 +408,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -467,8 +467,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -71,6 +71,7 @@ func (hs *HTTPServer) declareFixedRoles() error {
Grants: []string{string(org.RoleEditor)},
}
//nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility
if hs.Cfg.ViewersCanEdit {
datasourcesExplorerRole.Grants = append(datasourcesExplorerRole.Grants, string(org.RoleViewer))
}
@ -256,9 +257,7 @@ func (hs *HTTPServer) declareFixedRoles() error {
}
teamCreatorGrants := []string{string(org.RoleAdmin)}
if hs.Cfg.EditorsCanAdmin {
teamCreatorGrants = append(teamCreatorGrants, string(org.RoleEditor))
}
teamsCreatorRole := ac.RoleRegistration{
Role: ac.RoleDTO{
Name: "fixed:teams:creator",

@ -207,7 +207,6 @@ type FrontendSettingsDTO struct {
ExternalUserMngAnalyticsParams string `json:"externalUserMngAnalyticsParams"`
ViewersCanEdit bool `json:"viewersCanEdit"`
AngularSupportEnabled bool `json:"angularSupportEnabled"`
EditorsCanAdmin bool `json:"editorsCanAdmin"`
DisableSanitizeHtml bool `json:"disableSanitizeHtml"`
TrustedTypesDefaultPolicyEnabled bool `json:"trustedTypesDefaultPolicyEnabled"`
CSPReportOnlyEnabled bool `json:"cspReportOnlyEnabled"`

@ -228,9 +228,9 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
ExternalUserMngLinkName: hs.Cfg.ExternalUserMngLinkName,
ExternalUserMngAnalytics: hs.Cfg.ExternalUserMngAnalytics,
ExternalUserMngAnalyticsParams: hs.Cfg.ExternalUserMngAnalyticsParams,
//nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility
ViewersCanEdit: hs.Cfg.ViewersCanEdit,
AngularSupportEnabled: hs.Cfg.AngularSupportEnabled,
EditorsCanAdmin: hs.Cfg.EditorsCanAdmin,
DisableSanitizeHtml: hs.Cfg.DisableSanitizeHtml,
TrustedTypesDefaultPolicyEnabled: trustedTypesDefaultPolicyEnabled,
CSPReportOnlyEnabled: hs.Cfg.CSPReportOnlyEnabled,

@ -607,6 +607,22 @@ func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string {
if name.IsValid() && name.Kind() == reflect.String {
return name.String()
}
// Unstructured uses Object subtype
object := spec.FieldByName("Object")
if object.IsValid() && object.Kind() == reflect.Map {
key := reflect.ValueOf("title")
value := object.MapIndex(key)
if value.IsValid() {
if value.CanInterface() {
v := value.Interface()
t, ok := v.(string)
if ok {
return t
}
}
}
}
}
obj, ok := m.obj.(*unstructured.Unstructured)

@ -2,12 +2,15 @@ package conversion
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
dashboardV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
dashboardV1 "github.com/grafana/grafana/pkg/apis/dashboard/v1alpha1"
dashboardV2 "github.com/grafana/grafana/pkg/apis/dashboard/v2alpha1"
@ -15,9 +18,9 @@ import (
func TestConversionMatrixExist(t *testing.T) {
versions := []v1.Object{
&dashboardV0.Dashboard{},
&dashboardV1.Dashboard{},
&dashboardV2.Dashboard{},
&dashboardV0.Dashboard{Spec: v0alpha1.Unstructured{Object: map[string]any{"title": "dashboardV0"}}},
&dashboardV1.Dashboard{Spec: v0alpha1.Unstructured{Object: map[string]any{"title": "dashboardV1"}}},
&dashboardV2.Dashboard{Spec: dashboardV2.DashboardSpec{Title: "dashboardV2"}},
}
scheme := runtime.NewScheme()
@ -34,6 +37,11 @@ func TestConversionMatrixExist(t *testing.T) {
err = scheme.Convert(in, out, nil)
require.NoError(t, err)
}
// Make sure we get the right title for each value
meta, err := utils.MetaAccessor(in)
require.NoError(t, err)
require.True(t, strings.HasPrefix(meta.FindTitle(""), "dashboard"))
})
}
}

@ -3,10 +3,11 @@ package v0alpha1
import (
"fmt"
"github.com/grafana/grafana/pkg/apimachinery/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/grafana/pkg/apimachinery/utils"
)
const (

@ -4,6 +4,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
)
// When this code is changed, make sure to update the code generation.
@ -347,12 +348,28 @@ type ResourceStats struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
// Stats across all unified storage
// When legacy storage is still used, this will offer a shim
// +listType=atomic
Items []ResourceCount `json:"items,omitempty"`
Instance []ResourceCount `json:"instance,omitempty"`
// Stats for each manager
// +listType=atomic
Managed []ManagerStats `json:"managed,omitempty"`
}
type ManagerStats struct {
// Manager kind
Kind utils.ManagerKind `json:"kind,omitempty"`
// Manager identity
Identity string `json:"id,omitempty"`
// stats
Stats []ResourceCount `json:"stats"`
}
type ResourceCount struct {
Repository string `json:"repository,omitempty"`
Group string `json:"group"`
Resource string `json:"resource"`
Count int64 `json:"count"`

@ -352,6 +352,27 @@ func (in *LocalRepositoryConfig) DeepCopy() *LocalRepositoryConfig {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ManagerStats) DeepCopyInto(out *ManagerStats) {
*out = *in
if in.Stats != nil {
in, out := &in.Stats, &out.Stats
*out = make([]ResourceCount, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagerStats.
func (in *ManagerStats) DeepCopy() *ManagerStats {
if in == nil {
return nil
}
out := new(ManagerStats)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MigrateJobOptions) DeepCopyInto(out *MigrateJobOptions) {
*out = *in
@ -656,11 +677,18 @@ func (in *ResourceStats) DeepCopyInto(out *ResourceStats) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
if in.Instance != nil {
in, out := &in.Instance, &out.Instance
*out = make([]ResourceCount, len(*in))
copy(*out, *in)
}
if in.Managed != nil {
in, out := &in.Managed, &out.Managed
*out = make([]ManagerStats, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}

@ -28,6 +28,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobSpec": schema_pkg_apis_provisioning_v0alpha1_JobSpec(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.JobStatus": schema_pkg_apis_provisioning_v0alpha1_JobStatus(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.LocalRepositoryConfig": schema_pkg_apis_provisioning_v0alpha1_LocalRepositoryConfig(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ManagerStats": schema_pkg_apis_provisioning_v0alpha1_ManagerStats(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.MigrateJobOptions": schema_pkg_apis_provisioning_v0alpha1_MigrateJobOptions(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.PullRequestJobOptions": schema_pkg_apis_provisioning_v0alpha1_PullRequestJobOptions(ref),
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.Repository": schema_pkg_apis_provisioning_v0alpha1_Repository(ref),
@ -761,6 +762,49 @@ func schema_pkg_apis_provisioning_v0alpha1_LocalRepositoryConfig(ref common.Refe
}
}
func schema_pkg_apis_provisioning_v0alpha1_ManagerStats(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
Description: "Manager kind",
Type: []string{"string"},
Format: "",
},
},
"id": {
SchemaProps: spec.SchemaProps{
Description: "Manager identity",
Type: []string{"string"},
Format: "",
},
},
"stats": {
SchemaProps: spec.SchemaProps{
Description: "stats",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount"),
},
},
},
},
},
},
Required: []string{"stats"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount"},
}
}
func schema_pkg_apis_provisioning_v0alpha1_MigrateJobOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@ -1187,12 +1231,6 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceCount(ref common.ReferenceCal
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"repository": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"group": {
SchemaProps: spec.SchemaProps{
Default: "",
@ -1468,13 +1506,14 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref common.ReferenceCal
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
"instance": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Stats across all unified storage When legacy storage is still used, this will offer a shim",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
@ -1486,11 +1525,30 @@ func schema_pkg_apis_provisioning_v0alpha1_ResourceStats(ref common.ReferenceCal
},
},
},
"managed": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{
Description: "Stats for each manager",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ManagerStats"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
"github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ManagerStats", "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1.ResourceCount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}

@ -3,14 +3,15 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provis
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobResourceSummary,Errors
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobStatus,Errors
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobStatus,Summary
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ManagerStats,Stats
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,Workflows
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositoryViewList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceList,Items
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceStats,Items
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,TestResults,Errors
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ManagerStats,Identity
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,RepositorySpec,GitHub
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,ResourceWrapper,URLs
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1,SyncStatus,JobID

@ -87,7 +87,7 @@ require (
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.29.0 // indirect
golang.org/x/tools v0.30.0 // indirect
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect

@ -287,8 +287,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -27,7 +27,7 @@ require (
go.opentelemetry.io/otel/sdk v1.35.0 // indirect; @grafana/grafana-backend-group
go.opentelemetry.io/otel/trace v1.35.0 // indirect; @grafana/grafana-backend-group
golang.org/x/crypto v0.35.0 // indirect; @grafana/grafana-backend-group
golang.org/x/mod v0.22.0 // @grafana/grafana-backend-group
golang.org/x/mod v0.23.0 // @grafana/grafana-backend-group
golang.org/x/net v0.36.0 // indirect; @grafana/oss-big-tent @grafana/partner-datasources
golang.org/x/oauth2 v0.27.0 // @grafana/identity-access-team
golang.org/x/sync v0.11.0 // indirect; @grafana/alerting-backend

@ -303,8 +303,8 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

@ -6,10 +6,10 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/subcommands v1.2.0
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
golang.org/x/tools v0.29.0
golang.org/x/tools v0.30.0
)
require (
golang.org/x/mod v0.22.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/sync v0.11.0 // indirect
)

@ -4,9 +4,9 @@ github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=

@ -6,7 +6,7 @@ require (
cuelang.org/go v0.11.1
github.com/dave/dst v0.27.3
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d
github.com/grafana/cog v0.0.18
github.com/grafana/cog v0.0.27
github.com/grafana/cuetsy v0.1.11
github.com/matryer/is v1.4.1
)
@ -43,11 +43,11 @@ require (
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/yalue/merged_fs v1.3.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.29.0 // indirect
golang.org/x/tools v0.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

@ -31,8 +31,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk=
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
github.com/grafana/cog v0.0.18 h1:pEmzo/yhIFZMHM58ua0M9Eb5frJj6CgTrTTUVlY8e2o=
github.com/grafana/cog v0.0.18/go.mod h1:jrS9indvWuDs60RHEZpLaAkmZdgyoLKMOEUT0jiB1t0=
github.com/grafana/cog v0.0.27 h1:ZKipAtp6KuB08R16nZbqEjnje3e2r1O1bzOp1CetDEo=
github.com/grafana/cog v0.0.27/go.mod h1:JB5lhdn4Hqc0ztYCaNOTKZXoojzJvydBxMkMCGWS6+Q=
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ=
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f/go.mod h1:okjJBHFQFer+a41sAe2SaGm1glWS8oEb6CmJvn5Zdws=
github.com/grafana/cuetsy v0.1.11 h1:I3IwBhF+UaQxRM79HnImtrAn8REGdb5M3+C4QrYHoWk=
@ -98,16 +98,16 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY=
github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -24,10 +24,10 @@ func (jenny *GoSpecJenny) Generate(sfg ...SchemaForGen) (codejen.Files, error) {
for i, v := range sfg {
packageName := strings.ToLower(v.Name)
cueValue := v.CueFile.LookupPath(cue.ParsePath("lineage.schemas[0].schema.spec"))
cueValue := v.CueFile.LookupPath(cue.ParsePath("lineage.schemas[0].schema"))
b, err := cog.TypesFromSchema().
CUEValue(packageName, cueValue, cog.ForceEnvelope("Spec")).
CUEValue(packageName, cueValue).
Golang(cog.GoConfig{}).
Run(context.Background())
if err != nil {

@ -63,6 +63,9 @@ func allowedNode(node sqlparser.SQLNode) (b bool) {
case sqlparser.BoolVal:
return
case *sqlparser.CaseExpr, *sqlparser.When:
return
case sqlparser.ColIdent, *sqlparser.ColName, sqlparser.Columns:
return
@ -75,7 +78,7 @@ func allowedNode(node sqlparser.SQLNode) (b bool) {
case *sqlparser.ComparisonExpr:
return
case *sqlparser.ConvertExpr:
case *sqlparser.ConvertExpr, *sqlparser.ConvertType:
return
case sqlparser.GroupBy:
@ -84,6 +87,9 @@ func allowedNode(node sqlparser.SQLNode) (b bool) {
case *sqlparser.IndexHints:
return
case *sqlparser.IntervalExpr:
return
case *sqlparser.Into:
return
@ -120,6 +126,9 @@ func allowedNode(node sqlparser.SQLNode) (b bool) {
case sqlparser.TableName, sqlparser.TableExprs, sqlparser.TableIdent:
return
case *sqlparser.TrimExpr:
return
case *sqlparser.With:
return
@ -136,17 +145,55 @@ func allowedFunction(f *sqlparser.FuncExpr) (b bool) {
b = true // so don't have to return true in every case but default
switch strings.ToLower(f.Name.String()) {
case "if":
// Conditional functions
case "if", "coalesce", "ifnull", "nullif":
return
// Aggregation functions
case "sum", "avg", "count", "min", "max":
return
case "stddev", "std", "stddev_pop":
return
case "variance", "var_pop":
return
case "coalesce":
// Mathematical functions
case "abs":
return
case "round", "floor", "ceiling", "ceil":
return
case "sqrt", "pow", "power":
return
case "mod", "log", "log10", "exp":
return
case "sign":
return
// String functions
case "concat", "length", "char_length":
return
case "lower", "upper":
return
case "substring":
return
// Date functions
case "str_to_date":
return
case "date_format", "now", "curdate", "curtime":
return
case "date_add", "date_sub":
return
case "year", "month", "day", "weekday":
return
case "datediff":
return
case "unix_timestamp", "from_unixtime":
return
// Type conversion
case "cast":
return
default:
return false

@ -22,6 +22,16 @@ func TestAllowQuery(t *testing.T) {
q: example_argo_commit_example,
err: nil,
},
{
name: "case statement",
q: example_case_statement,
err: nil,
},
{
name: "all allowed functions",
q: example_all_allowed_functions,
err: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
@ -109,3 +119,78 @@ FROM drone,
argo_success,
argo_failure,
workflows;`
var example_case_statement = `SELECT
value,
CASE
WHEN value > 100 THEN 'High'
WHEN value > 50 THEN 'Medium'
ELSE 'Low'
END AS category
FROM metrics`
var example_all_allowed_functions = `SELECT
-- Conditional functions
IF(value > 100, 'High', 'Low') AS conditional_if,
COALESCE(value, 0) AS conditional_coalesce,
IFNULL(value, 0) AS conditional_ifnull,
NULLIF(value, 0) AS conditional_nullif,
-- Aggregation functions
SUM(value) AS agg_sum,
AVG(value) AS agg_avg,
COUNT(*) AS agg_count,
MIN(value) AS agg_min,
MAX(value) AS agg_max,
STDDEV(value) AS agg_stddev,
STD(value) AS agg_std,
STDDEV_POP(value) AS agg_stddev_pop,
VARIANCE(value) AS agg_variance,
VAR_POP(value) AS agg_var_pop,
-- Mathematical functions
ABS(value) AS math_abs,
ROUND(value, 2) AS math_round,
FLOOR(value) AS math_floor,
CEILING(value) AS math_ceiling,
CEIL(value) AS math_ceil,
SQRT(ABS(value)) AS math_sqrt,
POW(value, 2) AS math_pow,
POWER(value, 2) AS math_power,
MOD(value, 10) AS math_mod,
LOG(value) AS math_log,
LOG10(value) AS math_log10,
EXP(value) AS math_exp,
SIGN(value) AS math_sign,
-- String functions
CONCAT('value: ', CAST(value AS CHAR)) AS str_concat,
LENGTH(name) AS str_length,
CHAR_LENGTH(name) AS str_char_length,
LOWER(name) AS str_lower,
UPPER(name) AS str_upper,
SUBSTRING(name, 1, 5) AS str_substring,
TRIM(name) AS str_trim,
-- Date functions
STR_TO_DATE('2023-01-01', '%Y-%m-%d') AS date_str_to_date,
DATE_FORMAT(NOW(), '%Y-%m-%d') AS date_format,
NOW() AS date_now,
CURDATE() AS date_curdate,
CURTIME() AS date_curtime,
DATE_ADD(created_at, INTERVAL 1 DAY) AS date_add,
DATE_SUB(created_at, INTERVAL 1 DAY) AS date_sub,
YEAR(created_at) AS date_year,
MONTH(created_at) AS date_month,
DAY(created_at) AS date_day,
WEEKDAY(created_at) AS date_weekday,
DATEDIFF(NOW(), created_at) AS date_datediff,
UNIX_TIMESTAMP(created_at) AS date_unix_timestamp,
FROM_UNIXTIME(1634567890) AS date_from_unixtime,
-- Type conversion
CAST(value AS CHAR) AS type_cast,
CONVERT(value, CHAR) AS type_convert
FROM metrics
GROUP BY name, value, created_at
LIMIT 10`

@ -7,7 +7,6 @@ package v0alpha1
// ResourceCountApplyConfiguration represents a declarative configuration of the ResourceCount type for use
// with apply.
type ResourceCountApplyConfiguration struct {
Repository *string `json:"repository,omitempty"`
Group *string `json:"group,omitempty"`
Resource *string `json:"resource,omitempty"`
Count *int64 `json:"count,omitempty"`
@ -19,14 +18,6 @@ func ResourceCount() *ResourceCountApplyConfiguration {
return &ResourceCountApplyConfiguration{}
}
// WithRepository sets the Repository field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Repository field is set to the value of the last call.
func (b *ResourceCountApplyConfiguration) WithRepository(value string) *ResourceCountApplyConfiguration {
b.Repository = &value
return b
}
// WithGroup sets the Group field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the Group field is set to the value of the last call.

@ -39,6 +39,8 @@ func parseRedisConnStr(connStr string) (*redis.Options, error) {
switch connKey {
case "addr":
options.Addr = connVal
case "username":
options.Username = connVal
case "password":
options.Password = connVal
case "db":

@ -16,11 +16,12 @@ func Test_parseRedisConnStr(t *testing.T) {
ShouldErr bool
}{
"all redis options should parse": {
"addr=127.0.0.1:6379,pool_size=100,db=1,password=grafanaRocks,ssl=false",
"addr=127.0.0.1:6379,pool_size=100,db=1,username=grafana,password=grafanaRocks,ssl=false",
&redis.Options{
Addr: "127.0.0.1:6379",
PoolSize: 100,
DB: 1,
Username: "grafana",
Password: "grafanaRocks",
Network: "tcp",
TLSConfig: nil,

File diff suppressed because it is too large Load Diff

@ -15,15 +15,31 @@ import (
time "time"
)
type LibraryElementDTOMetaUser struct {
Id int64 `json:"id"`
type Spec struct {
// Folder UID
FolderUid *string `json:"folderUid,omitempty"`
// Library element UID
Uid string `json:"uid"`
// Panel name (also saved in the model)
Name string `json:"name"`
AvatarUrl string `json:"avatarUrl"`
// Panel description
Description *string `json:"description,omitempty"`
// The panel type (from inside the model)
Type string `json:"type"`
// Dashboard version when this was saved (zero if unknown)
SchemaVersion *uint16 `json:"schemaVersion,omitempty"`
// panel version, incremented each time the dashboard is updated.
Version int64 `json:"version"`
// TODO: should be the same panel schema defined in dashboard
// Typescript: Omit<Panel, 'gridPos' | 'id' | 'libraryPanel'>;
Model map[string]any `json:"model"`
// Object storage metadata
Meta *LibraryElementDTOMeta `json:"meta,omitempty"`
}
// NewLibraryElementDTOMetaUser creates a new LibraryElementDTOMetaUser object.
func NewLibraryElementDTOMetaUser() *LibraryElementDTOMetaUser {
return &LibraryElementDTOMetaUser{}
// NewSpec creates a new Spec object.
func NewSpec() *Spec {
return &Spec{}
}
type LibraryElementDTOMeta struct {
@ -44,29 +60,13 @@ func NewLibraryElementDTOMeta() *LibraryElementDTOMeta {
}
}
type Spec struct {
// Folder UID
FolderUid *string `json:"folderUid,omitempty"`
// Library element UID
Uid string `json:"uid"`
// Panel name (also saved in the model)
type LibraryElementDTOMetaUser struct {
Id int64 `json:"id"`
Name string `json:"name"`
// Panel description
Description *string `json:"description,omitempty"`
// The panel type (from inside the model)
Type string `json:"type"`
// Dashboard version when this was saved (zero if unknown)
SchemaVersion *uint16 `json:"schemaVersion,omitempty"`
// panel version, incremented each time the dashboard is updated.
Version int64 `json:"version"`
// TODO: should be the same panel schema defined in dashboard
// Typescript: Omit<Panel, 'gridPos' | 'id' | 'libraryPanel'>;
Model map[string]any `json:"model"`
// Object storage metadata
Meta *LibraryElementDTOMeta `json:"meta,omitempty"`
AvatarUrl string `json:"avatarUrl"`
}
// NewSpec creates a new Spec object.
func NewSpec() *Spec {
return &Spec{}
// NewLibraryElementDTOMetaUser creates a new LibraryElementDTOMetaUser object.
func NewLibraryElementDTOMetaUser() *LibraryElementDTOMetaUser {
return &LibraryElementDTOMetaUser{}
}

@ -11,6 +11,33 @@
package preferences
// Spec defines user, team or org Grafana preferences
// swagger:model Preferences
type Spec struct {
// UID for the home dashboard
HomeDashboardUID *string `json:"homeDashboardUID,omitempty"`
// The timezone selection
// TODO: this should use the timezone defined in common
Timezone *string `json:"timezone,omitempty"`
// day of the week (sunday, monday, etc)
WeekStart *string `json:"weekStart,omitempty"`
// light, dark, empty is default
Theme *string `json:"theme,omitempty"`
// Selected language (beta)
Language *string `json:"language,omitempty"`
// Explore query history preferences
QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"`
// Cookie preferences
CookiePreferences *CookiePreferences `json:"cookiePreferences,omitempty"`
// Navigation preferences
Navbar *NavbarPreference `json:"navbar,omitempty"`
}
// NewSpec creates a new Spec object.
func NewSpec() *Spec {
return &Spec{}
}
type QueryHistoryPreference struct {
// one of: '' | 'query' | 'starred';
HomeTab *string `json:"homeTab,omitempty"`
@ -40,30 +67,3 @@ type NavbarPreference struct {
func NewNavbarPreference() *NavbarPreference {
return &NavbarPreference{}
}
// Spec defines user, team or org Grafana preferences
// swagger:model Preferences
type Spec struct {
// UID for the home dashboard
HomeDashboardUID *string `json:"homeDashboardUID,omitempty"`
// The timezone selection
// TODO: this should use the timezone defined in common
Timezone *string `json:"timezone,omitempty"`
// day of the week (sunday, monday, etc)
WeekStart *string `json:"weekStart,omitempty"`
// light, dark, empty is default
Theme *string `json:"theme,omitempty"`
// Selected language (beta)
Language *string `json:"language,omitempty"`
// Explore query history preferences
QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"`
// Cookie preferences
CookiePreferences *CookiePreferences `json:"cookiePreferences,omitempty"`
// Navigation preferences
Navbar *NavbarPreference `json:"navbar,omitempty"`
}
// NewSpec creates a new Spec object.
func NewSpec() *Spec {
return &Spec{}
}

@ -7,7 +7,7 @@ replace github.com/grafana/grafana/pkg/codegen => ../../codegen
require (
cuelang.org/go v0.11.1
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d
github.com/grafana/cog v0.0.18
github.com/grafana/cog v0.0.27
github.com/grafana/cuetsy v0.1.11
github.com/grafana/grafana/pkg/codegen v0.0.0-00010101000000-000000000000
)
@ -42,11 +42,11 @@ require (
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/yalue/merged_fs v1.3.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/oauth2 v0.24.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.29.0 // indirect
golang.org/x/tools v0.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

@ -30,8 +30,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d h1:hrXbGJ5jgp6yNITzs5o+zXq0V5yT3siNJ+uM8LGwWKk=
github.com/grafana/codejen v0.0.4-0.20230321061741-77f656893a3d/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
github.com/grafana/cog v0.0.18 h1:pEmzo/yhIFZMHM58ua0M9Eb5frJj6CgTrTTUVlY8e2o=
github.com/grafana/cog v0.0.18/go.mod h1:jrS9indvWuDs60RHEZpLaAkmZdgyoLKMOEUT0jiB1t0=
github.com/grafana/cog v0.0.27 h1:ZKipAtp6KuB08R16nZbqEjnje3e2r1O1bzOp1CetDEo=
github.com/grafana/cog v0.0.27/go.mod h1:JB5lhdn4Hqc0ztYCaNOTKZXoojzJvydBxMkMCGWS6+Q=
github.com/grafana/cuetsy v0.1.11 h1:I3IwBhF+UaQxRM79HnImtrAn8REGdb5M3+C4QrYHoWk=
github.com/grafana/cuetsy v0.1.11/go.mod h1:Ix97+CPD8ws9oSSxR3/Lf4ahU1I4Np83kjJmDVnLZvc=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -92,8 +92,8 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY=
github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
@ -104,8 +104,8 @@ golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

@ -88,6 +88,9 @@ func TestLoader_Load(t *testing.T) {
Small: "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
Large: "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
},
Links: []plugins.InfoLink{
{Name: "Raise issue", URL: "https://github.com/grafana/grafana/issues/new"},
},
},
Includes: []*plugins.Includes{
{Name: "EC2", Path: "dashboards/ec2.json", Type: "dashboard", Role: "Viewer"},

@ -6,6 +6,7 @@ import (
"slices"
"strconv"
"time"
"unsafe"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
@ -597,17 +598,17 @@ func readScalar(iter *sdkjsoniter.Iterator, dataPlane bool) backend.DataResponse
func readMatrixOrVectorMulti(iter *sdkjsoniter.Iterator, resultType string, opt Options) backend.DataResponse {
rsp := backend.DataResponse{}
size := 0
for more, err := iter.ReadArray(); more; more, err = iter.ReadArray() {
if err != nil {
return rspErr(err)
}
timeField := data.NewFieldFromFieldType(data.FieldTypeTime, 0)
timeField.Name = data.TimeSeriesTimeFieldName
valueField := data.NewFieldFromFieldType(data.FieldTypeFloat64, 0)
valueField.Name = data.TimeSeriesValueFieldName
valueField.Labels = data.Labels{}
// First read all values to temporary storage
tempTimes := make([]time.Time, 0, size)
tempValues := make([]float64, 0, size)
var labels data.Labels
var histogram *histogramInfo
for l1Field, err := iter.ReadObject(); l1Field != ""; l1Field, err = iter.ReadObject() {
@ -616,7 +617,7 @@ func readMatrixOrVectorMulti(iter *sdkjsoniter.Iterator, resultType string, opt
}
switch l1Field {
case "metric":
if err = iter.ReadVal(&valueField.Labels); err != nil {
if err = iter.ReadVal(&labels); err != nil {
return rspErr(err)
}
@ -625,10 +626,9 @@ func readMatrixOrVectorMulti(iter *sdkjsoniter.Iterator, resultType string, opt
if err != nil {
return rspErr(err)
}
timeField.Append(t)
valueField.Append(v)
tempTimes = append(tempTimes, t)
tempValues = append(tempValues, v)
// nolint:goconst
case "values":
for more, err := iter.ReadArray(); more; more, err = iter.ReadArray() {
if err != nil {
@ -638,8 +638,8 @@ func readMatrixOrVectorMulti(iter *sdkjsoniter.Iterator, resultType string, opt
if err != nil {
return rspErr(err)
}
timeField.Append(t)
valueField.Append(v)
tempTimes = append(tempTimes, t)
tempValues = append(tempValues, v)
}
case "histogram":
@ -673,17 +673,18 @@ func readMatrixOrVectorMulti(iter *sdkjsoniter.Iterator, resultType string, opt
}
if histogram != nil {
histogram.yMin.Labels = valueField.Labels
frame := data.NewFrame(valueField.Name, histogram.time, histogram.yMin, histogram.yMax, histogram.count, histogram.yLayout)
histogram.yMin.Labels = labels
histogram.yMax.Labels = labels
histogram.count.Labels = labels
histogram.yLayout.Labels = labels
histogram.time.Labels = labels
frame := data.NewFrame("", histogram.time, histogram.yMin, histogram.yMax, histogram.count, histogram.yLayout)
frame.Meta = &data.FrameMeta{
Type: "heatmap-cells",
}
if frame.Name == data.TimeSeriesValueFieldName {
frame.Name = "" // only set the name if useful
}
rsp.Frames = append(rsp.Frames, frame)
} else {
frame := data.NewFrame("", timeField, valueField)
frame := data.NewFrame("", data.NewField(data.TimeSeriesTimeFieldName, nil, tempTimes), data.NewField(data.TimeSeriesValueFieldName, labels, tempValues))
frame.Meta = &data.FrameMeta{
Type: data.FrameTypeTimeSeriesMulti,
Custom: resultTypeToCustomMeta(resultType),
@ -695,6 +696,7 @@ func readMatrixOrVectorMulti(iter *sdkjsoniter.Iterator, resultType string, opt
frame.Meta.TypeVersion = data.FrameTypeVersion{0, 1}
}
rsp.Frames = append(rsp.Frames, frame)
size = len(tempTimes)
}
}
@ -857,14 +859,17 @@ func readHistogram(iter *sdkjsoniter.Iterator, hist *histogramInfo) error {
}
func appendValueFromString(iter *sdkjsoniter.Iterator, field *data.Field) error {
var err error
var s string
if s, err = iter.ReadString(); err != nil {
// Read the string directly into our buffer
buf, err := iter.ReadStringAsSlice()
if err != nil {
return err
}
var v float64
if v, err = strconv.ParseFloat(s, 64); err != nil {
// #nosec G103
// Convert string to float64 without allocation
// https://github.com/search?q=org%3Agrafana+yoloString&type=code
v, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&buf)), 64)
if err != nil {
return err
}

@ -0,0 +1,45 @@
package converter
import (
"os"
"testing"
jsoniter "github.com/json-iterator/go"
)
// readTestData reads a JSON file from testdata directory
func readTestData(t *testing.B, filename string) []byte {
// Can ignore gosec G304 here, because this is a constant defined below benchmark test
// nolint:gosec
data, err := os.ReadFile("testdata/" + filename)
if err != nil {
t.Fatal(err)
}
return data
}
// BenchmarkReadPrometheusStyleResult_FromFile benchmarks processing different test files
// go test -benchmem -run=^$ -bench=BenchmarkReadPrometheusStyleResult_FromFile$ github.com/grafana/grafana/pkg/promlib/converter/ -memprofile pmem.out -count 6 | tee pmem.0.txt
func BenchmarkReadPrometheusStyleResult_FromFile(b *testing.B) {
testFiles := []string{
"prom-query-range.json",
"prom-query-range-big.json",
"prom-matrix-histogram-partitioned.json",
}
opt := Options{Dataplane: true}
for _, tf := range testFiles {
testData := readTestData(b, tf)
iter := jsoniter.ParseBytes(jsoniter.ConfigDefault, testData)
b.Run(tf, func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ReadPrometheusStyleResult(iter, opt)
iter.ResetBytes(testData)
}
})
}
}

@ -36,6 +36,8 @@ var files = []string{
"prom-exemplars-a",
"prom-exemplars-b",
"prom-exemplars-diff-labels",
"prom-query-range",
"prom-query-range-big",
"loki-streams-a",
"loki-streams-b",
"loki-streams-c",

@ -46,7 +46,8 @@
"type": "time",
"typeInfo": {
"frame": "time.Time"
}
},
"labels": {}
},
{
"name": "yMin",
@ -61,21 +62,24 @@
"type": "number",
"typeInfo": {
"frame": "float64"
}
},
"labels": {}
},
{
"name": "count",
"type": "number",
"typeInfo": {
"frame": "float64"
}
},
"labels": {}
},
{
"name": "yLayout",
"type": "number",
"typeInfo": {
"frame": "int8"
}
},
"labels": {}
}
]
},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -46,7 +46,8 @@
"type": "time",
"typeInfo": {
"frame": "time.Time"
}
},
"labels": {}
},
{
"name": "yMin",
@ -61,21 +62,24 @@
"type": "number",
"typeInfo": {
"frame": "float64"
}
},
"labels": {}
},
{
"name": "count",
"type": "number",
"typeInfo": {
"frame": "float64"
}
},
"labels": {}
},
{
"name": "yLayout",
"type": "number",
"typeInfo": {
"frame": "int8"
}
},
"labels": {}
}
]
},

@ -108,12 +108,12 @@ require (
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/mod v0.23.0 // indirect
golang.org/x/net v0.36.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.29.0 // indirect
golang.org/x/tools v0.30.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/api v0.220.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250204164813-702378808489 // indirect

@ -336,8 +336,8 @@ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWB
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -377,8 +377,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -109,7 +109,7 @@ func RegisterAPIService(
}
func (b *DashboardsAPIBuilder) GetGroupVersions() []schema.GroupVersion {
if featuremgmt.AnyEnabled(b.features, featuremgmt.FlagUseV2DashboardsAPI) {
if featuremgmt.AnyEnabled(b.features, featuremgmt.FlagDashboardNewLayouts) {
// If dashboards v2 is enabled, we want to use v2alpha1 as the default API version.
return []schema.GroupVersion{
dashboardv2alpha1.DashboardResourceInfo.GroupVersion(),

@ -192,7 +192,7 @@ func TestDashboardAPIBuilder_GetGroupVersions(t *testing.T) {
{
name: "should return v2alpha1 as the default if dashboards v2 is enabled",
enabledFeatures: []string{
featuremgmt.FlagUseV2DashboardsAPI,
featuremgmt.FlagDashboardNewLayouts,
},
expected: []schema.GroupVersion{
v2alpha1.DashboardResourceInfo.GroupVersion(),

@ -10,6 +10,7 @@ import (
"github.com/google/wire"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/api/avatar"
"github.com/grafana/grafana/pkg/api/routing"
@ -421,6 +422,7 @@ var wireSet = wire.NewSet(
prefimpl.ProvideService,
oauthtoken.ProvideService,
wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)),
wire.Bind(new(cleanup.AlertRuleService), new(*ngstore.DBstore)),
)
var wireCLISet = wire.NewSet(
@ -453,6 +455,7 @@ var wireTestSet = wire.NewSet(
oauthtoken.ProvideService,
oauthtokentest.ProvideService,
wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtokentest.Service)),
wire.Bind(new(cleanup.AlertRuleService), new(*ngstore.DBstore)),
)
func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Server, error) {

@ -319,7 +319,7 @@ func (s *UserAuthTokenService) rotateToken(ctx context.Context, token *auth.User
now := getTime()
var affected int64
err = s.sqlStore.WithTransactionalDbSession(ctx, func(dbSession *db.Session) error {
res, err := dbSession.Exec(sql, userAgent, clientIPStr, hashedToken, s.sqlStore.GetDialect().BooleanStr(false), now.Unix(), token.Id)
res, err := dbSession.Exec(sql, userAgent, clientIPStr, hashedToken, s.sqlStore.GetDialect().BooleanValue(false), now.Unix(), token.Id)
if err != nil {
return err
}

@ -31,15 +31,11 @@ func (s *Service) getUsageStats(ctx context.Context) (map[string]any, error) {
// FIXME: Move this to accesscontrol OSS.
// FIXME: Access Control OSS usage stats is currently disabled if Enterprise is enabled.
m["stats.authz.viewers_can_edit.count"] = 0
//nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility
if s.cfg.ViewersCanEdit {
m["stats.authz.viewers_can_edit.count"] = 1
}
m["stats.authz.editors_can_admin.count"] = 0
if s.cfg.EditorsCanAdmin {
m["stats.authz.editors_can_admin.count"] = 1
}
for _, client := range s.clients {
if usac, ok := client.(authn.UsageStatClient); ok {
clientStats, err := usac.UsageStatFn(ctx)

@ -23,7 +23,7 @@ func TestService_getUsageStats(t *testing.T) {
svc.cfg.AuthProxy.Enabled = true
svc.cfg.JWTAuth.Enabled = true
svc.cfg.LDAPAuthEnabled = true
svc.cfg.EditorsCanAdmin = true
//nolint:staticcheck
svc.cfg.ViewersCanEdit = true
got, err := svc.getUsageStats(context.Background())
@ -35,7 +35,6 @@ func TestService_getUsageStats(t *testing.T) {
"stats.auth_enabled.jwt.count": 1,
"stats.auth_enabled.ldap.count": 1,
"stats.auth_enabled.login_form.count": 1,
"stats.authz.editors_can_admin.count": 1,
"stats.authz.viewers_can_edit.count": 1,
"stats.test.enabled.count": 1,
}

@ -105,6 +105,10 @@ func FallbackUsed(ctx context.Context) bool {
return ctx.Value(contextFallbackKey{}) != nil
}
func WithFallback(ctx context.Context) context.Context {
return context.WithValue(ctx, contextFallbackKey{}, true)
}
func (f *authenticatorWithFallback) Authenticate(ctx context.Context) (context.Context, error) {
ctx, span := f.tracer.Start(ctx, "grpcutils.AuthenticatorWithFallback.Authenticate")
defer span.End()
@ -122,7 +126,7 @@ func (f *authenticatorWithFallback) Authenticate(ctx context.Context) (context.C
span.SetAttributes(attribute.Bool("fallback_used", true))
newCtx, err = f.fallback.Authenticate(ctx)
if newCtx != nil {
newCtx = context.WithValue(newCtx, contextFallbackKey{}, true)
newCtx = WithFallback(newCtx)
}
f.metrics.requestsTotal.WithLabelValues("true", fmt.Sprintf("%t", err == nil)).Inc()
return newCtx, err

@ -27,6 +27,10 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
type AlertRuleService interface {
CleanUpDeletedAlertRules(ctx context.Context) (int64, error)
}
type CleanUpService struct {
log log.Logger
tracer tracing.Tracer
@ -41,12 +45,13 @@ type CleanUpService struct {
tempUserService tempuser.Service
annotationCleaner annotations.Cleaner
dashboardService dashboards.DashboardService
alertRuleService AlertRuleService
}
func ProvideService(cfg *setting.Cfg, serverLockService *serverlock.ServerLockService,
shortURLService shorturls.Service, sqlstore db.DB, queryHistoryService queryhistory.Service,
dashboardVersionService dashver.Service, dashSnapSvc dashboardsnapshots.Service, deleteExpiredImageService *image.DeleteExpiredService,
tempUserService tempuser.Service, tracer tracing.Tracer, annotationCleaner annotations.Cleaner, dashboardService dashboards.DashboardService) *CleanUpService {
tempUserService tempuser.Service, tracer tracing.Tracer, annotationCleaner annotations.Cleaner, dashboardService dashboards.DashboardService, service AlertRuleService) *CleanUpService {
s := &CleanUpService{
Cfg: cfg,
ServerLockService: serverLockService,
@ -61,6 +66,7 @@ func ProvideService(cfg *setting.Cfg, serverLockService *serverlock.ServerLockSe
tracer: tracer,
annotationCleaner: annotationCleaner,
dashboardService: dashboardService,
alertRuleService: service,
}
return s
}
@ -112,6 +118,10 @@ func (srv *CleanUpService) clean(ctx context.Context) {
cleanupJobs = append(cleanupJobs, cleanUpJob{"delete stale short URLs", srv.deleteStaleShortURLs})
}
if srv.Cfg.UnifiedAlerting.DeletedRuleRetention > 0 {
cleanupJobs = append(cleanupJobs, cleanUpJob{"cleanup trash alert rules", srv.cleanUpTrashAlertRules})
}
logger := srv.log.FromContext(ctx)
logger.Debug("Starting cleanup jobs", "jobs", fmt.Sprintf("%v", cleanupJobs))
@ -313,3 +323,13 @@ func (srv *CleanUpService) cleanUpTrashDashboards(ctx context.Context) {
logger.Debug("Cleaned up deleted dashboards", "dashboards affected", affected)
}
}
func (srv *CleanUpService) cleanUpTrashAlertRules(ctx context.Context) {
logger := srv.log.FromContext(ctx)
affected, err := srv.alertRuleService.CleanUpDeletedAlertRules(ctx)
if err != nil {
logger.Error("Problem cleaning up deleted alert rules", "error", err)
} else {
logger.Debug("Cleaned up deleted alert rules", "rows affected", affected)
}
}

@ -627,7 +627,7 @@ func (d *dashboardStore) SoftDeleteDashboardsInFolders(ctx context.Context, orgI
for _, folderUID := range folderUids {
args = append(args, folderUID)
}
args = append(args, orgID, d.store.GetDialect().BooleanStr(false))
args = append(args, orgID, d.store.GetDialect().BooleanValue(false))
_, err := sess.Exec(args...)
return err
@ -674,14 +674,20 @@ func (d *dashboardStore) deleteDashboard(cmd *dashboards.DeleteDashboardCommand,
if dashboard.IsFolder {
if !d.features.IsEnabledGlobally(featuremgmt.FlagDashboardRestore) {
sqlStatements = append(sqlStatements, statement{SQL: "DELETE FROM dashboard WHERE org_id = ? AND folder_uid = ? AND is_folder = ? AND deleted IS NULL", args: []any{dashboard.OrgID, dashboard.UID, d.store.GetDialect().BooleanStr(false)}})
sqlStatements = append(sqlStatements, statement{
SQL: "DELETE FROM dashboard WHERE org_id = ? AND folder_uid = ? AND is_folder = ? AND deleted IS NULL",
args: []any{dashboard.OrgID, dashboard.UID, d.store.GetDialect().BooleanValue(false)},
})
if err := d.deleteChildrenDashboardAssociations(sess, &dashboard); err != nil {
return err
}
} else {
// soft delete all dashboards in the folder
sqlStatements = append(sqlStatements, statement{SQL: "UPDATE dashboard SET deleted = ? WHERE org_id = ? AND folder_uid = ? AND is_folder = ? ", args: []any{time.Now(), dashboard.OrgID, dashboard.UID, d.store.GetDialect().BooleanStr(false)}})
sqlStatements = append(sqlStatements, statement{
SQL: "UPDATE dashboard SET deleted = ? WHERE org_id = ? AND folder_uid = ? AND is_folder = ? ",
args: []any{time.Now(), dashboard.OrgID, dashboard.UID, d.store.GetDialect().BooleanValue(false)},
})
}
// remove all access control permission with folder scope
@ -1083,7 +1089,7 @@ func (d *dashboardStore) CountDashboardsInFolders(
}
}
s.WriteString(" AND org_id = ? AND is_folder = ? AND deleted IS NULL")
args = append(args, req.OrgID, d.store.GetDialect().BooleanStr(false))
args = append(args, req.OrgID, d.store.GetDialect().BooleanValue(false))
sql := s.String()
_, err := sess.SQL(sql, args...).Get(&count)
return err

@ -40,7 +40,7 @@ func (m *FolderUIDMigration) Exec(sess *xorm.Session, mgrtr *migrator.Migrator)
WHERE d.is_folder = ?`
}
r, err := sess.Exec(q, mgrtr.Dialect.BooleanStr(false))
r, err := sess.Exec(q, mgrtr.Dialect.BooleanValue(false))
if err != nil {
mgrtr.Logger.Error("Failed to migrate dashboard folder_uid for dashboards", "error", err)
return err
@ -68,7 +68,7 @@ func (m *FolderUIDMigration) Exec(sess *xorm.Session, mgrtr *migrator.Migrator)
)
WHERE is_folder = ?`
}
r, err = sess.Exec(q, mgrtr.Dialect.BooleanStr(true))
r, err = sess.Exec(q, mgrtr.Dialect.BooleanValue(true))
if err != nil {
mgrtr.Logger.Error("Failed to migrate dashboard folder_uid for folders", "error", err)
return err

@ -138,7 +138,7 @@ func (ss *SqlStore) GetPrunableProvisionedDataSources(ctx context.Context) ([]*d
dataSources := make([]*datasources.DataSource, 0)
return dataSources, ss.db.WithDbSession(ctx, func(sess *db.Session) error {
return sess.Where(prunableQuery, ss.db.GetDialect().BooleanStr(true)).Asc("id").Find(&dataSources)
return sess.Where(prunableQuery, ss.db.GetDialect().BooleanValue(true)).Asc("id").Find(&dataSources)
})
}

@ -1565,13 +1565,6 @@ var (
FrontendOnly: true,
Expression: "true",
},
{
Name: "useV2DashboardsAPI",
Description: "Use the v2 kubernetes API in the frontend for dashboards",
Stage: FeatureStageExperimental,
Owner: grafanaDashboardsSquad,
RequiresRestart: true, // changes the API routing
},
{
Name: "feedbackButton",
Description: "Enables a button to send feedback from the Grafana UI",

@ -207,7 +207,6 @@ reportingUseRawTimeRange,GA,@grafana/sharing-squad,false,false,false
alertingUIOptimizeReducer,GA,@grafana/alerting-squad,false,false,true
azureMonitorEnableUserAuth,GA,@grafana/partner-datasources,false,false,false
alertingNotificationsStepMode,GA,@grafana/alerting-squad,false,false,true
useV2DashboardsAPI,experimental,@grafana/dashboards-squad,false,true,false
feedbackButton,experimental,@grafana/grafana-operator-experience-squad,false,false,false
unifiedStorageSearchUI,experimental,@grafana/search-and-storage,false,false,false
elasticsearchCrossClusterSearch,preview,@grafana/aws-datasources,false,false,false

1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
207 alertingUIOptimizeReducer GA @grafana/alerting-squad false false true
208 azureMonitorEnableUserAuth GA @grafana/partner-datasources false false false
209 alertingNotificationsStepMode GA @grafana/alerting-squad false false true
useV2DashboardsAPI experimental @grafana/dashboards-squad false true false
210 feedbackButton experimental @grafana/grafana-operator-experience-squad false false false
211 unifiedStorageSearchUI experimental @grafana/search-and-storage false false false
212 elasticsearchCrossClusterSearch preview @grafana/aws-datasources false false false

@ -839,10 +839,6 @@ const (
// Enables simplified step mode in the notifications section
FlagAlertingNotificationsStepMode = "alertingNotificationsStepMode"
// FlagUseV2DashboardsAPI
// Use the v2 kubernetes API in the frontend for dashboards
FlagUseV2DashboardsAPI = "useV2DashboardsAPI"
// FlagFeedbackButton
// Enables a button to send feedback from the Grafana UI
FlagFeedbackButton = "feedbackButton"

File diff suppressed because it is too large Load Diff

@ -154,6 +154,7 @@ func (s *Service) DBMigration(db db.DB) {
ctx := context.Background()
err := db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
var err error
deleteOldFolders := true
if db.GetDialect().DriverName() == migrator.SQLite {
// covered by UQE_folder_org_id_uid
_, err = sess.Exec(`
@ -168,6 +169,10 @@ func (s *Service) DBMigration(db db.DB) {
SELECT uid, org_id, title, created, updated FROM dashboard WHERE is_folder = true
ON CONFLICT(uid, org_id) DO UPDATE SET title=excluded.title, updated=excluded.updated
`)
} else if db.GetDialect().DriverName() == migrator.Spanner {
// We may eventually make this migration work with Spanner, but for now don't do anything.
// We intend to store dashboards and folders only in unified storage when using spanner.
deleteOldFolders = false
} else {
// covered by UQE_folder_org_id_uid
_, err = sess.Exec(`
@ -180,11 +185,13 @@ func (s *Service) DBMigration(db db.DB) {
return err
}
if deleteOldFolders {
// covered by UQE_folder_org_id_uid
_, err = sess.Exec(`
DELETE FROM folder WHERE NOT EXISTS
(SELECT 1 FROM dashboard WHERE dashboard.uid = folder.uid AND dashboard.org_id = folder.org_id AND dashboard.is_folder = true)
`)
}
return err
})
if err != nil {

@ -221,6 +221,7 @@ func (a *accessControlDashboardGuardian) CanEdit() (bool, error) {
return false, ErrGuardianDashboardNotFound.Errorf("failed to check edit permissions for dashboard")
}
//nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility
if a.cfg.ViewersCanEdit {
return a.CanView()
}
@ -235,6 +236,7 @@ func (a *accessControlFolderGuardian) CanEdit() (bool, error) {
return false, ErrGuardianFolderNotFound.Errorf("failed to check edit permissions for folder")
}
//nolint:staticcheck // ViewersCanEdit is deprecated but still used for backward compatibility
if a.cfg.ViewersCanEdit {
return a.CanView()
}

@ -341,6 +341,7 @@ func TestAccessControlDashboardGuardian_CanEdit(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
cfg := setting.NewCfg()
//nolint:staticcheck
cfg.ViewersCanEdit = tt.viewersCanEdit
guardian := setupAccessControlGuardianTest(t, tt.dashboard, tt.permissions, cfg)

@ -294,6 +294,11 @@ type AlertRule struct {
IsPaused bool
NotificationSettings []NotificationSettings
Metadata AlertRuleMetadata
// MissingSeriesEvalsToResolve specifies the number of consecutive evaluation intervals
// required before resolving an alert state (a dimension) when data is missing.
// If nil, alerts resolve after 2 missing evaluation intervals
// (i.e., resolution occurs during the second evaluation where data is absent).
MissingSeriesEvalsToResolve *int
}
type AlertRuleMetadata struct {
@ -578,6 +583,18 @@ func (alertRule *AlertRule) GetGroupKey() AlertRuleGroupKey {
return AlertRuleGroupKey{OrgID: alertRule.OrgID, NamespaceUID: alertRule.NamespaceUID, RuleGroup: alertRule.RuleGroup}
}
// GetMissingSeriesEvalsToResolve returns the number of consecutive evaluation intervals
// to wait before resolving an alert rule instance when its data is missing.
// If not configured, it returns the default value (2), which means the alert
// resolves after missing for two evaluation intervals.
func (alertRule *AlertRule) GetMissingSeriesEvalsToResolve() int {
if alertRule.MissingSeriesEvalsToResolve == nil {
return 2 // default value
}
return *alertRule.MissingSeriesEvalsToResolve
}
// PreSave sets default values and loads the updated model for each alert query.
func (alertRule *AlertRule) PreSave(timeNow func() time.Time, userUID *UserUID) error {
for i, q := range alertRule.Data {
@ -659,6 +676,10 @@ func validateAlertRuleFields(rule *AlertRule) error {
return err
}
if rule.MissingSeriesEvalsToResolve != nil && *rule.MissingSeriesEvalsToResolve <= 0 {
return fmt.Errorf("%w: field `missing_series_evals_to_resolve` must be greater than 0", ErrAlertRuleFailedValidation)
}
return nil
}
@ -727,6 +748,7 @@ func (alertRule *AlertRule) Copy() *AlertRule {
Record: alertRule.Record,
IsPaused: alertRule.IsPaused,
Metadata: alertRule.Metadata,
MissingSeriesEvalsToResolve: alertRule.MissingSeriesEvalsToResolve,
}
if alertRule.DashboardUID != nil {
@ -789,6 +811,7 @@ func ClearRecordingRuleIgnoredFields(rule *AlertRule) {
rule.Condition = ""
rule.For = 0
rule.NotificationSettings = nil
rule.MissingSeriesEvalsToResolve = nil
}
// GetAlertRuleByUIDQuery is the query for retrieving/deleting an alert rule by UID and organisation ID.

@ -18,6 +18,7 @@ import (
"golang.org/x/exp/maps"
"gopkg.in/yaml.v3"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/cmputil"
)
@ -386,6 +387,7 @@ func TestPatchPartialAlertRule(t *testing.T) {
})
}
// nolint:gocyclo
func TestDiff(t *testing.T) {
t.Run("should return nil if there is no diff", func(t *testing.T) {
rule1 := RuleGen.GenerateRef()
@ -406,7 +408,9 @@ func TestDiff(t *testing.T) {
t.Run("should find diff in simple fields", func(t *testing.T) {
rule1 := RuleGen.GenerateRef()
rule2 := RuleGen.GenerateRef()
rule2 := RuleGen.With(
RuleGen.WithMissingSeriesEvalsToResolve(*rule1.MissingSeriesEvalsToResolve + 1),
).GenerateRef()
diffs := rule1.Diff(rule2, "Data", "Annotations", "Labels", "NotificationSettings", "Metadata") // these fields will be tested separately
@ -540,6 +544,13 @@ func TestDiff(t *testing.T) {
assert.Equal(t, rule2.Record, diff[0].Right.String())
difCnt++
}
if rule1.MissingSeriesEvalsToResolve != rule2.MissingSeriesEvalsToResolve {
diff := diffs.GetDiffsForField("MissingSeriesEvalsToResolve")
assert.Len(t, diff, 1)
assert.Equal(t, *rule1.MissingSeriesEvalsToResolve, int(diff[0].Left.Int()))
assert.Equal(t, *rule2.MissingSeriesEvalsToResolve, int(diff[0].Right.Int()))
difCnt++
}
require.Lenf(t, diffs, difCnt, "Got some unexpected diffs. Either add to ignore or add assert to it")
@ -963,6 +974,21 @@ func TestAlertRuleGetKeyWithGroup(t *testing.T) {
})
}
func TestAlertRuleGetMissingSeriesEvalsToResolve(t *testing.T) {
t.Run("should return the default 2 if MissingSeriesEvalsToResolve is nil", func(t *testing.T) {
rule := RuleGen.GenerateRef()
rule.MissingSeriesEvalsToResolve = nil
require.Equal(t, 2, rule.GetMissingSeriesEvalsToResolve())
})
t.Run("should return the correct value", func(t *testing.T) {
rule := RuleGen.With(
RuleMuts.WithMissingSeriesEvalsToResolve(3),
).GenerateRef()
require.Equal(t, 3, rule.GetMissingSeriesEvalsToResolve())
})
}
func TestAlertRuleCopy(t *testing.T) {
t.Run("should return a copy of the rule", func(t *testing.T) {
for i := 0; i < 100; i++ {
@ -1084,3 +1110,54 @@ func TestAlertRule_PrometheusRuleDefinition(t *testing.T) {
})
}
}
func TestMissingSeriesEvalsToResolveValidation(t *testing.T) {
testCases := []struct {
name string
missingSeriesEvalsToResolve *int
expectedErrorContains string
}{
{
name: "should allow nil value",
missingSeriesEvalsToResolve: nil,
},
{
name: "should reject negative value",
missingSeriesEvalsToResolve: util.Pointer(-1),
expectedErrorContains: "field `missing_series_evals_to_resolve` must be greater than 0",
},
{
name: "should reject 0",
missingSeriesEvalsToResolve: util.Pointer(0),
expectedErrorContains: "field `missing_series_evals_to_resolve` must be greater than 0",
},
{
name: "should accept positive value",
missingSeriesEvalsToResolve: util.Pointer(2),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
baseIntervalSeconds := int64(10)
cfg := setting.UnifiedAlertingSettings{
BaseInterval: time.Duration(baseIntervalSeconds) * time.Second,
}
rule := RuleGen.With(
RuleMuts.WithIntervalSeconds(baseIntervalSeconds * 2),
).Generate()
rule.MissingSeriesEvalsToResolve = tc.missingSeriesEvalsToResolve
err := rule.ValidateAlertRule(cfg)
if tc.expectedErrorContains != "" {
require.Error(t, err)
require.ErrorIs(t, err, ErrAlertRuleFailedValidation)
require.Contains(t, err.Error(), tc.expectedErrorContains)
} else {
require.NoError(t, err)
}
})
}
}

@ -126,6 +126,7 @@ func (g *AlertRuleGenerator) Generate() AlertRule {
Labels: labels,
NotificationSettings: ns,
Metadata: GenerateMetadata(),
MissingSeriesEvalsToResolve: util.Pointer(2),
}
for _, mutator := range g.mutators {
@ -499,6 +500,15 @@ func (a *AlertRuleMutators) WithSameGroup() AlertRuleMutator {
}
}
func (a *AlertRuleMutators) WithMissingSeriesEvalsToResolve(timesOfInterval int) AlertRuleMutator {
return func(rule *AlertRule) {
if timesOfInterval <= 0 {
panic("timesOfInterval must be greater than 0")
}
rule.MissingSeriesEvalsToResolve = util.Pointer(timesOfInterval)
}
}
func (a *AlertRuleMutators) WithNotificationSettingsGen(ns func() NotificationSettings) AlertRuleMutator {
return func(rule *AlertRule) {
rule.NotificationSettings = []NotificationSettings{ns()}
@ -1343,6 +1353,7 @@ func ConvertToRecordingRule(rule *AlertRule) {
rule.ExecErrState = ""
rule.For = 0
rule.NotificationSettings = nil
rule.MissingSeriesEvalsToResolve = nil
}
func nameToUid(name string) string { // Avoid legacy_storage.NameToUid import cycle.

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

Loading…
Cancel
Save