diff --git a/.drone.yml b/.drone.yml index 59f88baab41..d27ffd886ad 100644 --- a/.drone.yml +++ b/.drone.yml @@ -134,7 +134,7 @@ steps: environment: HOST: start-storybook PORT: "9001" - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-storybook-suite trigger: event: @@ -561,7 +561,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-dashboards-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/dashboards-suite @@ -570,7 +570,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/dashboards-suite - commands: - ./bin/build e2e-tests --port 3001 --suite smoke-tests-suite @@ -579,7 +579,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-smoke-tests-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/smoke-tests-suite @@ -588,7 +588,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/smoke-tests-suite - commands: - ./bin/build e2e-tests --port 3001 --suite panels-suite @@ -597,7 +597,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-panels-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/panels-suite @@ -606,7 +606,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/panels-suite - commands: - ./bin/build e2e-tests --port 3001 --suite various-suite @@ -615,7 +615,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-various-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/various-suite @@ -624,7 +624,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/various-suite - commands: - GITHUB_TOKEN=$(cat /github-app/token) @@ -1658,7 +1658,7 @@ steps: environment: HOST: start-storybook PORT: "9001" - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-storybook-suite trigger: branch: main @@ -1851,7 +1851,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-dashboards-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/dashboards-suite @@ -1860,7 +1860,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/dashboards-suite - commands: - ./bin/build e2e-tests --port 3001 --suite smoke-tests-suite @@ -1869,7 +1869,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-smoke-tests-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/smoke-tests-suite @@ -1878,7 +1878,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/smoke-tests-suite - commands: - ./bin/build e2e-tests --port 3001 --suite panels-suite @@ -1887,7 +1887,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-panels-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/panels-suite @@ -1896,7 +1896,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/panels-suite - commands: - ./bin/build e2e-tests --port 3001 --suite various-suite @@ -1905,7 +1905,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-various-suite - commands: - ./bin/build e2e-tests --port 3001 --suite old-arch/various-suite @@ -1914,7 +1914,7 @@ steps: - build-test-plugins environment: HOST: grafana-server - image: cypress/included:13.10.0 + image: cypress/included:14.3.2 name: end-to-end-tests-old-arch/various-suite - commands: - GITHUB_TOKEN=$(cat /github-app/token) @@ -4923,7 +4923,7 @@ steps: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/drone-downstream - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/docker-puppeteer:1.1.0 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/docs-base:latest - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM cypress/included:13.10.0 + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM cypress/included:14.3.2 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM jwilder/dockerize:0.6.1 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM koalaman/shellcheck:stable - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM rockylinux:9 @@ -4961,7 +4961,7 @@ steps: - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/drone-downstream - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/docker-puppeteer:1.1.0 - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/docs-base:latest - - trivy --exit-code 1 --severity HIGH,CRITICAL cypress/included:13.10.0 + - trivy --exit-code 1 --severity HIGH,CRITICAL cypress/included:14.3.2 - trivy --exit-code 1 --severity HIGH,CRITICAL jwilder/dockerize:0.6.1 - trivy --exit-code 1 --severity HIGH,CRITICAL koalaman/shellcheck:stable - trivy --exit-code 1 --severity HIGH,CRITICAL rockylinux:9 @@ -5210,6 +5210,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: 8e25f1f786b8de4eb21dfbeca8c5fcb9701b1e62ecf98287d9225ecc6e8c29e8 +hmac: 16029e3922ae0a13a31233717aa172c06bf0e6fc8cf01f5148de62147c259ac8 ... diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 67ceca61366..c8fdc572b60 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -138,6 +138,7 @@ /pkg/services/dashboardversion/ @grafana/grafana-backend-group /pkg/services/encryption/ @grafana/grafana-operator-experience-squad /pkg/services/folder/ @grafana/grafana-search-and-storage +/pkg/services/frontend/ @grafana/grafana-frontend-platform /pkg/services/apiserver @grafana/grafana-app-platform-squad /pkg/services/hooks/ @grafana/grafana-backend-group /pkg/services/kmsproviders/ @grafana/grafana-operator-experience-squad @@ -435,6 +436,7 @@ /packages/grafana-ui/src/graveyard/GraphNG/ @grafana/dataviz-squad /packages/grafana-ui/src/graveyard/TimeSeries/ @grafana/dataviz-squad /packages/grafana-ui/src/utils/storybook/ @grafana/grafana-frontend-platform +/packages/grafana-alerting/ @grafana/alerting-frontend # root files, mostly frontend /.browserslistrc @grafana/frontend-ops diff --git a/.github/workflows/frontend-lint.yml b/.github/workflows/frontend-lint.yml index cad4294407b..148838465fa 100644 --- a/.github/workflows/frontend-lint.yml +++ b/.github/workflows/frontend-lint.yml @@ -6,6 +6,10 @@ on: - main - release-*.*.* +permissions: + contents: read + id-token: write + jobs: lint-frontend-verify-i18n: name: Verify i18n @@ -30,6 +34,9 @@ jobs: exit 1 fi lint-frontend-prettier: + # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, + # the `lint-frontend-prettier-enterprise` workflow will run instead + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true name: Lint runs-on: ubuntu-latest steps: @@ -42,7 +49,29 @@ jobs: - run: yarn install --immutable --check-cache - run: yarn run prettier:check - run: yarn run lint + lint-frontend-prettier-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: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + cache-dependency-path: 'yarn.lock' + - name: Setup Enterprise + uses: ./.github/actions/setup-enterprise + with: + github-app-name: 'grafana-ci-bot' + - run: yarn install --immutable --check-cache + - run: yarn run prettier:check + - run: yarn run lint lint-frontend-typecheck: + # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, + # the `lint-frontend-typecheck-enterprise` workflow will run instead + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true name: Typecheck runs-on: ubuntu-latest steps: @@ -54,6 +83,24 @@ jobs: cache-dependency-path: 'yarn.lock' - run: yarn install --immutable --check-cache - run: yarn run typecheck + lint-frontend-typecheck-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: Typecheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + cache-dependency-path: 'yarn.lock' + - name: Setup Enterprise + uses: ./.github/actions/setup-enterprise + with: + github-app-name: 'grafana-ci-bot' + - run: yarn install --immutable --check-cache + - run: yarn run typecheck lint-frontend-betterer: name: Betterer runs-on: ubuntu-latest diff --git a/.github/workflows/pr-frontend-unit-tests.yml b/.github/workflows/pr-frontend-unit-tests.yml index f3c61032d52..beced0ac371 100644 --- a/.github/workflows/pr-frontend-unit-tests.yml +++ b/.github/workflows/pr-frontend-unit-tests.yml @@ -6,8 +6,15 @@ on: - main - release-*.*.* +permissions: + contents: read + id-token: write + jobs: frontend-unit-tests: + # Run this workflow only for PRs from forks; if it gets merged into `main` or `release-*`, + # the `frontend-unit-tests-enterprise` workflow will run instead + if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true runs-on: ubuntu-latest-8-cores name: "Unit tests (${{ matrix.chunk }} / 8)" strategy: @@ -27,3 +34,30 @@ jobs: TEST_MAX_WORKERS: 2 TEST_SHARD: ${{ matrix.chunk }} TEST_SHARD_TOTAL: 8 + + frontend-unit-tests-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 + runs-on: ubuntu-latest-8-cores + name: "Unit tests (${{ matrix.chunk }} / 8)" + strategy: + fail-fast: false + matrix: + chunk: [1, 2, 3, 4, 5, 6, 7, 8] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'yarn' + cache-dependency-path: 'yarn.lock' + - name: Setup Enterprise + uses: ./.github/actions/setup-enterprise + with: + github-app-name: 'grafana-ci-bot' + - run: yarn install --immutable --check-cache + - run: yarn run test:ci + env: + TEST_MAX_WORKERS: 2 + TEST_SHARD: ${{ matrix.chunk }} + TEST_SHARD_TOTAL: 8 diff --git a/.github/workflows/pr-patch-check-event.yml b/.github/workflows/pr-patch-check-event.yml index 03dd31a7539..e93d870adf8 100644 --- a/.github/workflows/pr-patch-check-event.yml +++ b/.github/workflows/pr-patch-check-event.yml @@ -3,7 +3,7 @@ name: Dispatch check for patch conflicts run-name: dispatch-check-patch-conflicts-${{ github.base_ref }}-${{ github.head_ref }} on: - pull_request: + pull_request_target: types: - opened - reopened @@ -26,7 +26,6 @@ jobs: # App needs Actions: Read/Write for the grafana/security-patch-actions repo app_id: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_ID }} private_key: ${{ secrets.GRAFANA_DELIVERY_BOT_APP_PEM }} - - name: "Dispatch job" uses: actions/github-script@v7 with: diff --git a/.github/workflows/pr-test-integration.yml b/.github/workflows/pr-test-integration.yml index a0acad9d0de..518e1150531 100644 --- a/.github/workflows/pr-test-integration.yml +++ b/.github/workflows/pr-test-integration.yml @@ -23,22 +23,9 @@ jobs: with: go-version-file: go.mod cache: true - - name: Restore GOCACHE - uses: actions/cache/restore@v4 - with: - key: go-test-cache-${{ github.ref_name }}-sqlite - restore-keys: | - go-test-cache-${{ github.base_ref }}-sqlite - go-test-cache-main-sqlite - path: /home/runner/.cache/go-build - run: | make gen-go go test -tags=sqlite -timeout=5m -run '^TestIntegration' $(find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u) - - name: Save GOCACHE - uses: actions/cache/save@v4 - with: - key: go-test-cache-${{ github.ref_name }}-sqlite - path: /home/runner/.cache/go-build mysql: name: MySQL runs-on: ubuntu-latest-8-cores @@ -64,24 +51,11 @@ jobs: with: go-version-file: go.mod cache: true - - name: Restore GOCACHE - uses: actions/cache/restore@v4 - with: - key: go-test-cache-${{ github.ref_name }}-mysql - restore-keys: | - go-test-cache-${{ github.base_ref }}-mysql - go-test-cache-main-mysql - path: /home/runner/.cache/go-build - run: | sudo apt-get update -yq && sudo apt-get install mariadb-client cat devenv/docker/blocks/mysql_tests/setup.sql | mariadb -h 127.0.0.1 -P 3306 -u root -prootpass --disable-ssl-verify-server-cert make gen-go go test -tags=mysql -p=1 -timeout=5m -run '^TestIntegration' $(find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u) - - name: Save GOCACHE - uses: actions/cache/save@v4 - with: - key: go-test-cache-${{ github.ref_name }}-mysql - path: /home/runner/.cache/go-build postgres: name: Postgres runs-on: ubuntu-latest-8-cores @@ -102,14 +76,6 @@ jobs: with: go-version-file: go.mod cache: true - - name: Restore GOCACHE - uses: actions/cache/restore@v4 - with: - key: go-test-cache-${{ github.ref_name }}-postgres - restore-keys: | - go-test-cache-${{ github.base_ref }}-postgres - go-test-cache-main-postgres - path: /home/runner/.cache/go-build - env: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest @@ -119,8 +85,3 @@ jobs: psql -p 5432 -h 127.0.0.1 -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql make gen-go go test -p=1 -tags=postgres -timeout=5m -run '^TestIntegration' $(find ./pkg -type f -name '*_test.go' -exec grep -l '^func TestIntegration' '{}' '+' | grep -o '\(.*\)/' | sort -u) - - name: Save GOCACHE - uses: actions/cache/save@v4 - with: - key: go-test-cache-${{ github.ref_name }}-postgres - path: /home/runner/.cache/go-build diff --git a/.vscode/launch.json b/.vscode/launch.json index 50393b02272..88a99c05737 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -82,6 +82,16 @@ "cwd": "${workspaceFolder}", "args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"] }, + { + "name": "Run Frontend Server", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/pkg/cmd/grafana/", + "cwd": "${workspaceFolder}", + "env": { "GF_DEFAULT_TARGET": "frontend-server", "GF_SERVER_HTTP_PORT": "3003" }, + "args": ["server", "target", "--homepath", "${workspaceFolder}", "--packaging", "dev"] + }, { "name": "Attach to Chrome", "port": 9222, diff --git a/Dockerfile b/Dockerfile index d6869491704..4ec06a8b9ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,7 +70,6 @@ COPY .citools/swagger .citools/swagger # Include vendored dependencies COPY pkg/util/xorm pkg/util/xorm -COPY pkg/apis/folder pkg/apis/folder COPY pkg/apis/secret pkg/apis/secret COPY pkg/apiserver pkg/apiserver COPY pkg/apimachinery pkg/apimachinery diff --git a/apps/advisor/pkg/app/utils.go b/apps/advisor/pkg/app/utils.go index ca8c6bad7f6..315c2ce29a6 100644 --- a/apps/advisor/pkg/app/utils.go +++ b/apps/advisor/pkg/app/utils.go @@ -189,7 +189,16 @@ func runStepsInParallel(ctx context.Context, spec *advisorv0alpha1.CheckSpec, st go func(step checks.Step, item any) { defer wg.Done() defer func() { <-limit }() - stepErr, err := step.Run(ctx, spec, item) + var stepErr *advisorv0alpha1.CheckReportFailure + var err error + func() { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic recovered in step %s: %v", step.ID(), r) + } + }() + stepErr, err = step.Run(ctx, spec, item) + }() mu.Lock() defer mu.Unlock() if err != nil { diff --git a/apps/advisor/pkg/app/utils_test.go b/apps/advisor/pkg/app/utils_test.go index c5b2db07201..79bd1540c64 100644 --- a/apps/advisor/pkg/app/utils_test.go +++ b/apps/advisor/pkg/app/utils_test.go @@ -134,14 +134,6 @@ func TestProcessCheck_RunError(t *testing.T) { } func TestProcessCheck_IgnoreSteps(t *testing.T) { - obj := &advisorv0alpha1.Check{} - obj.SetAnnotations(map[string]string{}) - meta, err := utils.MetaAccessor(obj) - if err != nil { - t.Fatal(err) - } - meta.SetCreatedBy("user:1") - client := &mockClient{} checkType := &advisorv0alpha1.CheckType{} checkType.SetAnnotations(map[string]string{checks.IgnoreStepsAnnotation: "mock"}) typesClient := &mockTypesClient{ @@ -152,6 +144,14 @@ func TestProcessCheck_IgnoreSteps(t *testing.T) { items: []any{"item"}, err: errors.New("run error, should not be triggered"), } + obj := &advisorv0alpha1.Check{} + obj.SetAnnotations(map[string]string{}) + meta, err := utils.MetaAccessor(obj) + if err != nil { + t.Fatal(err) + } + meta.SetCreatedBy("user:1") + client := &mockClient{} err = processCheck(ctx, client, typesClient, obj, check) assert.NoError(t, err) @@ -159,6 +159,29 @@ func TestProcessCheck_IgnoreSteps(t *testing.T) { assert.Equal(t, "mock", obj.GetAnnotations()[checks.IgnoreStepsAnnotation]) } +func TestProcessCheck_RunRecoversFromPanic(t *testing.T) { + obj := &advisorv0alpha1.Check{} + obj.SetAnnotations(map[string]string{}) + meta, err := utils.MetaAccessor(obj) + if err != nil { + t.Fatal(err) + } + meta.SetCreatedBy("user:1") + client := &mockClient{} + typesClient := &mockTypesClient{} + ctx := context.TODO() + + check := &mockCheck{ + items: []any{"item"}, + runPanics: true, + } + + err = processCheck(ctx, client, typesClient, obj, check) + assert.Error(t, err) + assert.Contains(t, err.Error(), "panic recovered in step") + assert.Equal(t, "error", obj.GetAnnotations()[checks.StatusAnnotation]) +} + func TestProcessCheckRetry_NoRetry(t *testing.T) { obj := &advisorv0alpha1.Check{} obj.SetAnnotations(map[string]string{}) @@ -257,8 +280,9 @@ func (m *mockTypesClient) Get(ctx context.Context, id resource.Identifier) (reso } type mockCheck struct { - err error - items []any + err error + items []any + runPanics bool } func (m *mockCheck) ID() string { @@ -275,15 +299,19 @@ func (m *mockCheck) Item(ctx context.Context, id string) (any, error) { func (m *mockCheck) Steps() []checks.Step { return []checks.Step{ - &mockStep{err: m.err}, + &mockStep{err: m.err, panics: m.runPanics}, } } type mockStep struct { - err error + err error + panics bool } func (m *mockStep) Run(ctx context.Context, obj *advisorv0alpha1.CheckSpec, items any) (*advisorv0alpha1.CheckReportFailure, error) { + if m.panics { + panic("panic") + } if m.err != nil { return nil, m.err } diff --git a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue index 31bfcc723cf..f2da39df16c 100644 --- a/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2alpha1/dashboard_spec.cue @@ -78,7 +78,7 @@ AnnotationPanelFilter: { exclude?: bool | *false // Panel IDs that should be included or excluded - ids: [...uint8] + ids: [...uint32] } // "Off" for no shared crosshair or tooltip (default). diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go index 50847df87c3..a267e0c8df8 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_object_gen.go @@ -294,8 +294,6 @@ var _ resource.ListObject = &DashboardList{} // Copy methods for all subresource types - - // DeepCopy creates a full deep copy of DashboardStatus func (s *DashboardStatus) DeepCopy() *DashboardStatus { cpy := &DashboardStatus{} diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go index 1423c7b0603..be021b5f003 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_object_gen.go @@ -294,8 +294,6 @@ var _ resource.ListObject = &DashboardList{} // Copy methods for all subresource types - - // DeepCopy creates a full deep copy of DashboardStatus func (s *DashboardStatus) DeepCopy() *DashboardStatus { cpy := &DashboardStatus{} diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue index a369449ea0e..85641129095 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec.cue @@ -82,7 +82,7 @@ AnnotationPanelFilter: { exclude?: bool | *false // Panel IDs that should be included or excluded - ids: [...uint8] + ids: [...uint32] } // "Off" for no shared crosshair or tooltip (default). diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go index 23d3c77fa76..877c2acc44e 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_spec_gen.go @@ -73,7 +73,7 @@ type DashboardAnnotationPanelFilter struct { // Should the specified panels be included or excluded Exclude *bool `json:"exclude,omitempty"` // Panel IDs that should be included or excluded - Ids []uint8 `json:"ids"` + Ids []uint32 `json:"ids"` } // NewDashboardAnnotationPanelFilter creates a new DashboardAnnotationPanelFilter object. diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go index f777f9c4f82..8df2424f977 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi.go @@ -544,7 +544,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAnnotationPanelFilter(ref commo SchemaProps: spec.SchemaProps{ Default: 0, Type: []string{"integer"}, - Format: "byte", + Format: "int64", }, }, }, diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list index 17477f9db13..889a4fcdfed 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/zz_generated.openapi_violation_exceptions.list @@ -3,6 +3,7 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,BaseFilters API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,DefaultKeys API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,Filters +API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAnnotationPanelFilter,Ids API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAutoGridLayoutSpec,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardConditionalRenderingGroupSpec,Items API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardCustomVariableSpec,Options diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index e8ebdfbc5ed..cc5451a78e8 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -11,8 +11,6 @@ import ( "github.com/grafana/grafana-app-sdk/app" ) -var () - var appManifestData = app.ManifestData{ AppName: "dashboard", Group: "dashboard.grafana.app", diff --git a/docs/sources/cli.md b/docs/sources/cli.md index eb89e24f04d..301741d644a 100644 --- a/docs/sources/cli.md +++ b/docs/sources/cli.md @@ -1,7 +1,7 @@ --- aliases: - administration/cli/ -description: Guide to using grafana cli +description: Guide to using grafana server cli keywords: - grafana - cli @@ -11,15 +11,15 @@ labels: products: - enterprise - oss -title: Grafana CLI +title: Grafana server CLI weight: 400 --- -# Grafana CLI +# Grafana server CLI -Grafana CLI is a small executable that's bundled with Grafana server. +Grafana server CLI is a small executable that's bundled with Grafana server. You can run it on the same machine Grafana server is running on. -Grafana CLI has `plugins` and `admin` commands, as well as global options. +Grafana server CLI has `plugins` and `admin` commands, as well as global options. To list all commands and options: @@ -27,9 +27,9 @@ To list all commands and options: grafana cli -h ``` -## Run Grafana CLI +## Run Grafana server CLI -To run Grafana CLI, add the path to the Grafana binaries in your `PATH` environment variable. +To run Grafana server CLI, add the path to the Grafana binaries in your `PATH` environment variable. Alternately, if your current directory is the `bin` directory, run `./grafana cli`. Otherwise, you can specify full path to the binary. For example, on Linux `/usr/share/grafana/bin/grafana` and on Windows `C:\Program Files\GrafanaLabs\grafana\bin\grafana.exe`, and run it with `grafana cli`. @@ -41,7 +41,7 @@ If you're on Windows, run Windows PowerShell as Administrator. ## Grafana CLI command syntax -The general syntax for commands in Grafana CLI is: +The general syntax for commands in Grafana server CLI is: ```bash grafana cli [global options] command [command options] [arguments...] @@ -49,11 +49,11 @@ grafana cli [global options] command [command options] [arguments...] ## Global options -Grafana CLI allows you to temporarily override certain Grafana default settings. Except for `--help` and `--version`, most global options are only used by developers. +Grafana server CLI allows you to temporarily override certain Grafana default settings. Except for `--help` and `--version`, most global options are only used by developers. Each global option applies only to the command in which it is used. For example, `--pluginsDir value` does not permanently change where Grafana saves plugins. It only changes it for command in which you apply the option. -### Display Grafana CLI help +### Display Grafana server CLI help `--help` or `-h` displays the help, including default paths and Docker configuration information. @@ -63,9 +63,9 @@ Each global option applies only to the command in which it is used. For example, grafana cli -h ``` -### Display Grafana CLI version +### Display Grafana server CLI version -`--version` or `-v` prints the version of Grafana CLI currently running. +`--version` or `-v` prints the version of Grafana server CLI currently running. **Example:** diff --git a/docs/sources/developers/http_api/apis.md b/docs/sources/developers/http_api/apis.md new file mode 100644 index 00000000000..c00d34b4de7 --- /dev/null +++ b/docs/sources/developers/http_api/apis.md @@ -0,0 +1,99 @@ +--- +aliases: + - ../../http_api/new_api_structure/ + - ../../http_api/new_api_structure/ +canonical: /docs/grafana/latest/developers/http_api/new_api_structure/ +description: '' +keywords: + - grafana + - http + - documentation + - api +labels: + products: + - enterprise + - oss +title: New API Structure +--- + +# Grafana's New API Structure + +## Overview + +Going forward, Grafana's HTTP API will follow a standardized API structure alongside consistent API versioning. + +## API Path Structure + +All Grafana APIs follow this standardized format: + +``` +/apis///namespaces//[/] +``` + +Where the final `/` segment is used for operations on individual resources (like Get, Update, Delete) and omitted for collection operations (like List, Create). + +## Understanding the Components + +### Group (``) + +Groups organize related functionality into logical collections. For example `dashboard.grafana.app` will be used for all dashboard-related operations. + +### Version (``) + +These APIs will also uses semantic versioning with three stability levels: + +| Level | Format | Description | Use Case | +| ----- | ---------- | --------------------------------------------------------------------------- | ------------------------ | +| Alpha | `v1alpha1` | Early development stage. Unstable, may contain bugs, and subject to removal | For testing new features | +| Beta | `v1beta1` | More stable than alpha, but may still have some changes | For early production use | +| GA | `v1` | Generally Available. Stable with backward compatibility guarantees | For production use | + +### Namespace (``) + +Namespaces isolate resources within your Grafana instance. The format varies by deployment type: + +#### OSS & On-Premise Grafana + +- Default organization (org 1): `default` +- Additional organizations: `org-` + +#### Grafana Cloud + +- Format: `stacks-` +- Your instance ID is the `stack_id`. You can find this value by either: + - Going to grafana.com, clicking on your stack, and selecting "Details" on your Grafana instance + - Accessing the /swagger page in your cloud instance, where the namespace will be automatically populated on the relevant endpoints + +### Resource (``) + +Represents the core resource you want to interact with, such as: + +- `dashboards` +- `playlists` +- `folders` + +### Name () + +The `` is the unique identifier for a specific instance of a resource within its namespace and resource type. `` is distinct from the metadata.uid field. The URL path will always use the metadata.name. + +For example, to get a dashboard defined as: + +``` +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "production-overview", // This value IS used in the URL path + "namespace": "default", + "uid": "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8" // This value is NOT used in the URL path + // ... other metadata + }, + "spec": { + // ... dashboard spec + } +} +``` + +You would use the following API call: + +`GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/production-overview` diff --git a/docs/sources/developers/http_api/dashboard.md b/docs/sources/developers/http_api/dashboard.md index d3e0a61242f..2dfe6322441 100644 --- a/docs/sources/developers/http_api/dashboard.md +++ b/docs/sources/developers/http_api/dashboard.md @@ -16,22 +16,859 @@ labels: title: Dashboard HTTP API --- -# Dashboard API +# New Dashboard APIs > If you are running Grafana Enterprise, for some endpoints you'll need to have specific permissions. Refer to [Role-based access control permissions](/docs/grafana/latest/administration/roles-and-permissions/access-control/custom-role-actions-scopes/) for more information. -## Identifier (id) vs unique identifier (uid) +> To view more about the new api structure, refer to [API overview]({{< ref "apis" >}}). -The identifier (id) of a dashboard is an auto-incrementing numeric value and is only unique per Grafana install. +## Create Dashboard -The unique identifier (uid) of a dashboard can be used for uniquely identify a dashboard between multiple Grafana installs. +`POST /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards` + +Creates a new dashboard. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ------------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:create` |
  • `folders:*`
  • `folders:uid:*`
| +| `dashboards:write` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Create Request**: + +```http +POST /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "metadata": { + "name": "gdxccn", + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b" + }, + }, + "spec": { + "annotations": { + "list": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Example annotation", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + } + }] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Example Link", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "description": "With a description", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Example panel", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": ["example"], + "templating": { + "list": [ + { + "current": { + "text": "", + "value": "" + }, + "definition": "", + "description": "example description", + "label": "ExampleLabel", + "name": "ExampleVariable", + "options": [], + "query": "", + "refresh": 1, + "regex": "cluster", + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Example Dashboard", + "version": 0 + } +} +``` + +JSON Body schema: + +- **metadata.name** – The Grafana [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}). If you do not want to provide this, set metadata.generateName instead to the prefix you would like for the randomly generated uid (cannot be an empty string). +- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the folder under which the dashboard should be created. +- **spec** – The dashboard json. + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 485 + +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "gdxccn", + "namespace": "default", + "uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX", + "resourceVersion": "1", + "generation": 1, + "creationTimestamp": "2025-04-24T20:35:29Z", + "labels": { + "grafana.app/deprecatedInternalID": "11" + }, + "annotations": { + "grafana.app/createdBy": "service-account:dejwtrofg77y8d", + "grafana.app/folder": "fef30w4jaxla8b" + }, + "managedFields": [ + { + "manager": "curl", + "operation": "Update", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "time": "2025-04-24T20:35:29Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:spec": { + "f:annotations": { + ".": {}, + "f:list": {} + }, + "f:editable": {}, + "f:fiscalYearStartMonth": {}, + "f:graphTooltip": {}, + "f:links": {}, + "f:panels": {}, + "f:preload": {}, + "f:schemaVersion": {}, + "f:tags": {}, + "f:templating": { + ".": {}, + "f:list": {} + }, + "f:time": { + ".": {}, + "f:from": {}, + "f:to": {} + }, + "f:timepicker": {}, + "f:timezone": {}, + "f:title": {}, + "f:version": {} + } + } + } + ] + }, + "spec": { + "annotations": { + "list": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": false, + "iconColor": "red", + "name": "Example annotation", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + } + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [ + { + "asDropdown": false, + "icon": "external link", + "includeVars": false, + "keepTime": false, + "tags": [], + "targetBlank": false, + "title": "Example Link", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "panels": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "description": "With a description", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.0.0", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "refId": "A" + } + ], + "title": "Example panel", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 41, + "tags": [ + "example" + ], + "templating": { + "list": [ + { + "current": { + "text": "", + "value": "" + }, + "definition": "", + "description": "example description", + "label": "ExampleLabel", + "name": "ExampleVariable", + "options": [], + "query": "", + "refresh": 1, + "regex": "cluster", + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Example Dashboard" + }, + "status": {} +``` + +Status Codes: + +- **201** – Created +- **400** – Errors (invalid json, missing or invalid fields, etc) +- **401** – Unauthorized +- **403** – Access denied +- **409** – Conflict (dashboard with the same uid already exists) + +## Update Dashboard + +`PUT /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid` + +Updates an existing dashboard via the dashboard uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the dashboard to update. this will be the _name_ in the dashboard response + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ------------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:write` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Update Request**: + +```http +POST /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/gdxccn HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "metadata": { + "name": "gdxccn", + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b", + "grafana.app/message": "commit message" + }, + }, + "spec": { + "title": "New dashboard - updated", + "schemaVersion": 41, + ... + } +} +``` + +JSON Body schema: + +- **metadata.name** – The [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}). +- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the folder under which the dashboard should be created. +- **metadata.annotations.grafana.app/message** - Optional field, to set a commit message for the version history. +- **spec** – The dashboard json. + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 485 + +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "gdxccn", + "namespace": "default", + "uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-03-06T19:57:18Z", + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b", + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T02:58:36Z" + } + }, + "spec": { + "schemaVersion": 41, + "title": "New dashboard - updated", + ... + } +} +``` + +Status Codes: + +- **200** – OK +- **400** – Errors (invalid json, missing or invalid fields, etc) +- **401** – Unauthorized +- **403** – Access denied +- **409** – Conflict (dashboard with the same version already exists) + +## Get Dashboard + +`GET /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid` + +Gets a dashboard via the dashboard uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the dashboard to update. this will be the _name_ in the dashboard response + +Note: For large dashboards, add `/dto` to the end of the URL to get the full dashboard body. + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ----------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:read` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Get Request**: + +```http +GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/gdxccn HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 485 + +{ + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1beta1", + "metadata": { + "name": "gdxccn", + "namespace": "default", + "uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX", + "resourceVersion": "2", + "generation": 2, + "creationTimestamp": "2025-03-06T19:57:18Z", + "annotations": { + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T02:58:36Z" + } + }, + "spec": { + "schemaVersion": 41, + "title": "New dashboard - updated", + ... + } +} +``` + +Status Codes: + +- **200** – OK +- **401** – Unauthorized +- **403** – Access denied +- **404** – Not Found + +## List Dashboards + +`GET /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards` + +Lists all dashboards in the given organization. You can control the maximum number of dashboards returned through the `limit` query parameter. You can then use the `continue` token returned to fetch the next page of dashboards. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +Note: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ----------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:read` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Get Request**: + +```http +GET /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards?limit=1 HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 644 + +{ + "kind": "DashboardList", + "apiVersion": "dashboard.grafana.app/v1alpha1", + "metadata": { + "resourceVersion": "1741315830000", + "continue": "org:1/start:1158/folder:" + }, + "items": [ + { + "kind": "Dashboard", + "apiVersion": "dashboard.grafana.app/v1alpha1", + "metadata": { + "name": "gpqcmf", + "namespace": "default", + "uid": "VQyL7pNTpfGPNlPM6HRJSePrBg5dXmxr4iPQL7txLtwX", + "resourceVersion": "1", + "generation": 1, + "creationTimestamp": "2025-03-06T19:50:30Z", + "annotations": { + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-06T19:50:30Z" + } + }, + "spec": { + "schemaVersion": 41, + "title": "New dashboard", + "uid": "gpqcmf", + "version": 1, + ... + } + } + ] +} +``` + +Status Codes: + +- **200** – OK +- **401** – Unauthorized +- **403** – Access denied + +## Delete Dashboard + +`DELETE /apis/dashboard.grafana.app/v1beta1/namespaces/:namespace/dashboards/:uid` + +Deletes a dashboard via the dashboard uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the dashboard to update. this will be the _name_ in the dashboard response + +**Required permissions** + +See note in the [introduction]({{< ref "#dashboard-api" >}}) for an explanation. + + +| Action | Scope | +| ------------------- | ------------------------------------------------------------------------------------------------------- | +| `dashboards:delete` |
  • `dashboards:*`
  • `dashboards:uid:*`
  • `folders:*`
  • `folders:uid:*`
| +{ .no-spacing-list } + + +**Example Delete Request**: + +```http +DELETE /apis/dashboard.grafana.app/v1beta1/namespaces/default/dashboards/gdxccn HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Content-Length: 78 + +{ + "kind": "Status", + "apiVersion": "v1", + "metadata": {}, + "status": "Success", + "details": { + "name": "gdxccn", + "group": "dashboard.grafana.app", + "kind": "dashboards", + "uid": "Cc7fA5ffHY94NnHZyMxXvFlpFtOmkK3qkBcVZPKSPXcX" + } +} +``` + +Status Codes: + +- **200** – OK +- **401** – Unauthorized +- **403** – Access denied +- **404** – Not found + +## Gets the home dashboard + +`GET /api/dashboards/home` + +Will return the home dashboard. + +**Example Request**: + +```http +GET /api/dashboards/home HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +{ + "dashboard": { + "editable":false, + "nav":[ + { + "enable":false, + "type":"timepicker" + } + ], + "style":"dark", + "tags":[], + "templating":{ + "list":[ + ] + }, + "time":{ + }, + "timezone":"browser", + "title":"Home", + "version":5 + }, + "meta": { + "isHome":true, + "canSave":false, + "canEdit":false, + "canStar":false, + "url":"", + "expires":"0001-01-01T00:00:00Z", + "created":"0001-01-01T00:00:00Z" + } +} +``` + +## Tags for Dashboard + +`GET /api/dashboards/tags` + +Get all tags of dashboards + +**Example Request**: + +```http +GET /api/dashboards/tags HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +[ + { + "term":"tag1", + "count":1 + }, + { + "term":"tag2", + "count":4 + } +] +``` + +## Dashboard Search + +See [Folder/Dashboard Search API]({{< relref "folder_dashboard_search/" >}}). + +## APIs + +### Unique identifier (uid) vs identifier (id) + +The unique identifier (uid) of a dashboard can be used to uniquely identify a dashboard within a given org. It's automatically generated if not provided when creating a dashboard. The uid allows having consistent URLs for accessing dashboards and when syncing dashboards between multiple Grafana installs, see [dashboard provisioning](/docs/grafana/latest/administration/provisioning/#dashboards) for more information. This means that changing the title of a dashboard will not break any bookmarked links to that dashboard. The uid can have a maximum length of 40 characters. -## Create / Update dashboard +The identifier (id) of a dashboard is deprecated in favor of the unique identifier (uid). + +### Create / Update dashboard `POST /api/dashboards/db` @@ -155,7 +992,7 @@ Content-Length: 97 } ``` -## Get dashboard by uid +### Get dashboard by uid `GET /api/dashboards/uid/:uid` @@ -214,7 +1051,7 @@ Status Codes: - **403** – Access denied - **404** – Not found -## Delete dashboard by uid +### Delete dashboard by uid `DELETE /api/dashboards/uid/:uid` diff --git a/docs/sources/developers/http_api/folder.md b/docs/sources/developers/http_api/folder.md index 7a65f7fa56d..6a8e2b1f771 100644 --- a/docs/sources/developers/http_api/folder.md +++ b/docs/sources/developers/http_api/folder.md @@ -16,19 +16,388 @@ labels: title: Folder HTTP API --- -# Folder API +# New Folders APIs > If you are running Grafana Enterprise, for some endpoints you'll need to have specific permissions. Refer to [Role-based access control permissions](/docs/grafana/latest/administration/roles-and-permissions/access-control/custom-role-actions-scopes/) for more information. +> To view more about the new api structure, refer to [API overview]({{< ref "apis" >}}). + +### Get all folders + +`GET /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders` + +Returns all folders that the authenticated user has permission to view within the given organization. Use the `limit` query parameter to control the maximum number of dashboards returned. To retrieve additional dashboards, utilize the `continue` token provided in the response to fetch the next page. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +| Action | Scope | +| -------------- | ----------- | +| `folders:read` | `folders:*` | + +**Example Request**: + +```http +GET /apis/folder.grafana.app/v1beta1/namespaces/default/folders?limit=1 HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json +{ + "kind": "FolderList", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "continue": "org:1/start:1158/folder:" + }, + "items": [ + { + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "aef30vrzxs3y8d", + "namespace": "default", + "uid": "KCtv1FXDsJmTYQoTgcPnfuwZhDZge3uMpXOefaOHjb4X", + "resourceVersion": "1741343686000", + "creationTimestamp": "2025-03-07T10:34:46Z", + "annotations": { + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T10:34:46Z" + } + }, + "spec": { + "title": "example" + } + } + ] +} +``` + +Status Codes: + +- **200** – OK +- **401** – Unauthorized +- **403** – Access Denied + +### Get folder by uid + +`GET /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:uid` + +Will return the folder given the folder uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the folder to update. this will be the _name_ in the folder response + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +| Action | Scope | +| -------------- | ----------- | +| `folders:read` | `folders:*` | + +**Example Request**: + +```http +GET /apis/folder.grafana.app/v1beta1/namespaces/default/folders/aef30vrzxs3y8d HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json +{ + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "aef30vrzxs3y8d", + "namespace": "default", + "uid": "KCtv1FXDsJmTYQoTgcPnfuwZhDZge3uMpXOefaOHjb4X", + "resourceVersion": "1741343686000", + "creationTimestamp": "2025-03-07T10:34:46Z", + "annotations": { + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T10:34:46Z", + "grafana.app/folder": "fef30w4jaxla8b" + } + }, + "spec": { + "title": "test" + } +} +``` + +Note the annotation `grafana.app/folder` which contains the uid of the parent folder. + +Status Codes: + +- **200** – Found +- **401** – Unauthorized +- **403** – Access Denied +- **404** – Folder not found + +### Create folder + +`POST /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders` + +Creates a new folder. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +`folders:create` allows creating folders and subfolders. If granted with scope `folders:uid:general`, allows creating root level folders. Otherwise, allows creating subfolders under the specified folders. + +| Action | Scope | +| ---------------- | ----------- | +| `folders:create` | `folders:*` | +| `folders:write` | `folders:*` | + +**Example Request**: + +```http +POST /apis/folder.grafana.app/v1beta1/namespaces/default/folders HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +{ + "metadata": { + "name": "aef30vrzxs3y8d", + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b" + } + } + "spec": { + "title": "child-folder" + }, +} +``` + +JSON Body schema: + +- **metadata.name** – The Grafana [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}). If you do not want to provide this, set metadata.generateName to the prefix you would like for the uid. +- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the parent folder under which the folder should be created. Requires nested folders to be enabled. +- **spec.title** – The title of the folder. + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json +{ + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "eef33r1fprd34d", + "namespace": "default", + "uid": "X8momvVZnsXdOqvLD9I4ngqLVif2CgRWXHy9xb2UgjQX", + "resourceVersion": "1741320415009", + "creationTimestamp": "2025-03-07T04:06:55Z", + "labels": { + "grafana.app/deprecatedInternalID": "1159" + }, + "annotations": { + "grafana.app/folder": "fef30w4jaxla8b", + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb" + } + }, + "spec": { + "title": "child-folder" + } +} +``` + +Status Codes: + +- **201** – Created +- **400** – Errors (invalid json, missing or invalid fields, etc) +- **401** – Unauthorized +- **403** – Access denied +- **409** – Conflict (folder with the same uid already exists) + +### Update folder + +`PUT /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:uid` + +Updates an existing folder identified by uid. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the folder to update. this will be the _name_ in the folder response + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +| Action | Scope | +| --------------- | ----------- | +| `folders:write` | `folders:*` | + +**Example Request**: + +```http +PUT /apis/folder.grafana.app/v1beta1/namespaces/default/folders/fef30w4jaxla8b HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +"metadata": { + "name": "aef30vrzxs3y8d", + "annotations": { + "grafana.app/folder": "xkj92m5pqw3vn4" + } + } + "spec": { + "title": "updated title" + }, +``` + +JSON Body schema: + +- **metadata.name** – The [unique identifier]({{< ref "#identifier-id-vs-unique-identifier-uid" >}}) of the folder. +- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the parent folder under which the folder should be - update this to move the folder under a different parent folder. Requires nested folders to be enabled. +- **spec.title** – The title of the folder. + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +{ + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "fef30w4jaxla8b", + "namespace": "default", + "uid": "YaWLsFrMwEaTlIQwX2iMnhHlJuZHtZugps50BQoyjXEX", + "resourceVersion": "1741345736000", + "creationTimestamp": "2025-03-07T11:08:56Z", + "annotations": { + "grafana.app/folder": "xkj92m5pqw3vn4", + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T11:08:56Z" + } + }, + "spec": { + "title": "updated title" + } +} +``` + +Status Codes: + +- **200** – Updated +- **400** – Errors (invalid json, missing or invalid fields, etc) +- **401** – Unauthorized +- **403** – Access Denied +- **404** – Folder not found +- **412** – Precondition failed (the folder has been changed by someone else). With this status code, the response body will have the following properties: + +```http +HTTP/1.1 412 Precondition Failed +Content-Type: application/json; charset=UTF-8 +Content-Length: 97 + +{ + "message": "The folder has been changed by someone else", + "status": "version-mismatch" +} +``` + +### Delete folder + +`DELETE /apis/folder.grafana.app/v1beta1/namespaces/:namespace/folders/:uid` + +Deletes an existing folder identified by UID along with all dashboards (and their alerts) stored in the folder. This operation cannot be reverted. + +If [Grafana Alerting]({{< relref "/docs/grafana/latest/alerting" >}}) is enabled, you can set an optional query parameter `forceDeleteRules=false` so that requests will fail with 400 (Bad Request) error if the folder contains any Grafana alerts. However, if this parameter is set to `true` then it will delete any Grafana alerts under this folder. + +- namespace: to read more about the namespace to use, see the [API overview]({{< ref "apis" >}}). +- uid: the unique identifier of the folder to delete. this will be the _name_ in the folder response + +**Required permissions** + +See note in the [introduction]({{< ref "#folder-api" >}}) for an explanation. + +| Action | Scope | +| ---------------- | ----------- | +| `folders:delete` | `folders:*` | + +**Example Request**: + +```http +DELETE /apis/folder.grafana.app/v1beta1/namespaces/default/folders/fef30w4jaxla8b HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +{ + "kind": "Folder", + "apiVersion": "folder.grafana.app/v1beta1", + "metadata": { + "name": "fef30w4jaxla8b", + "namespace": "default", + "uid": "YaWLsFrMwEaTlIQwX2iMnhHlJuZHtZugps50BQoyjXEX", + "resourceVersion": "1741345736000", + "creationTimestamp": "2025-03-07T11:08:56Z", + "annotations": { + "grafana.app/folder": "xkj92m5pqw3vn4", + "grafana.app/createdBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedBy": "service-account:cef2t2rfm73lsb", + "grafana.app/updatedTimestamp": "2025-03-07T11:08:56Z" + } + }, + "spec": { + "title": "updated title" + } +} +``` + +Status Codes: + +- **200** – Deleted +- **401** – Unauthorized +- **400** – Bad Request +- **403** – Access Denied +- **404** – Folder not found + +## APIs + ## Identifier (id) vs unique identifier (uid) -The identifier (id) of a folder is an auto-incrementing numeric value and is only unique per Grafana install. - -The unique identifier (uid) of a folder can be used for uniquely identify folders between multiple Grafana installs. It's automatically generated if not provided when creating a folder. The uid allows having consistent URLs for accessing folders and when syncing folders between multiple Grafana installs. This means that changing the title of a folder will not break any bookmarked links to that folder. +The unique identifier (uid) of a folder can be used for uniquely identify folders within an org. It's automatically generated if not provided when creating a folder. The uid allows having consistent URLs for accessing folders and when syncing folders between multiple Grafana installs. This means that changing the title of a folder will not break any bookmarked links to that folder. The uid can have a maximum length of 40 characters. -## Get all folders +The identifier (id) of a folder is deprecated in favor of the unique identifier (uid). + +### Get all folders `GET /api/folders` @@ -75,7 +444,7 @@ Content-Type: application/json ] ``` -## Get folder by uid +### Get folder by uid `GET /api/folders/:uid` @@ -133,7 +502,7 @@ Status Codes: - **403** – Access Denied - **404** – Folder not found -## Create folder +### Create folder `POST /api/folders` @@ -207,7 +576,7 @@ Status Codes: - **403** – Access Denied - **412** - Folder already exists -## Update folder +### Update folder `PUT /api/folders/:uid` @@ -296,7 +665,7 @@ Content-Length: 97 } ``` -## Delete folder +### Delete folder `DELETE /api/folders/:uid` @@ -342,7 +711,7 @@ Status Codes: - **403** – Access Denied - **404** – Folder not found -## Move folder +### Move folder `POST /api/folders/:uid/move` diff --git a/docs/sources/observability-as-code/grafana-cli/_index.md b/docs/sources/observability-as-code/grafana-cli/_index.md new file mode 100644 index 00000000000..58f9d3edc3d --- /dev/null +++ b/docs/sources/observability-as-code/grafana-cli/_index.md @@ -0,0 +1,52 @@ +--- +_build: + list: false +noindex: true +cascade: + noindex: true +description: Overview of Grafana CLI, a command line tool for managing Grafana resources as code. +keywords: + - observability + - configuration + - as code + - as-code + - dashboards + - git integration + - git sync + - github +labels: + products: + - cloud + - enterprise + - oss +cards: + items: + - description: Learn how to install Grafana CLI + height: 24 + href: ./install-grafana-cli/ + title: Install Grafana CLI + - description: Set up Grafana CLI + height: 24 + href: ./set-up-grafana-cli/ + title: Set up your Grafana CLI + - description: Learn how to manage resources with Grafana CLI + height: 24 + href: ./grafanacli-workflows + title: Manage resources with Grafana CLI + title_class: pt-0 lh-1 +hero: + description: Grafana CLI (`grafanactl`) is a command-line tool designed to simplify interaction with Grafana instances. It enables users to authenticate, manage multiple environments, and perform administrative tasks through Grafana’s REST API, all from the terminal. Whether you're automating workflows in CI/CD pipelines or switching between staging and production environments, Grafana CLI provides a flexible and scriptable way to manage your Grafana setup efficiently. + height: 110 + level: 1 + title: Grafana CLI + width: 110 +title: Introduction to Grafana CLI +menuTitle: Grafana CLI +weight: 130 +--- + +{{< docs/hero-simple key="hero" >}} + +## Explore + +{{< card-grid key="cards" type="simple" >}} diff --git a/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md b/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md new file mode 100644 index 00000000000..980c6a92b83 --- /dev/null +++ b/docs/sources/observability-as-code/grafana-cli/grafanacli-workflows.md @@ -0,0 +1,226 @@ +--- +_build: + list: false +noindex: true +cascade: + noindex: true +description: Learn more about the supported workflows and use cases for Grafana CLI +keywords: + - workflows + - Grafana CLI + - CLI + - command line + - grafanactl +labels: + products: + - cloud + - enterprise + - oss +title: Manage resources with Grafana CLI +weight: 300 +--- + +# Manage resources with Grafana CLI + +{{< admonition type="note" >}} +`grafanactl` is under active development. Command-line flags and subcommands described here may change. This document outlines the target workflows the tool is expected to support. +{{< /admonition >}} + +## Migrate resources between environments + +Using the `config` and `resources` options, you can migrate Grafana resources from one environment to another, for example, from a development to production environment. +The `config` option lets you define the configuration context. +Using `resources` with `pull`, `push`, and `serve` lets you pull a defined resource from one instance, and push that resource to another instance. `Serve` allows you to preview changes locally before pushing. + +Use these steps to migrate resources between environments: + +{{< admonition type="note" >}} +Currently, the `serve` command only works with dashboards. +{{< /admonition >}} + +Use these steps to migrate resources between environments: + +{{< admonition type="note" >}} +Resources are pulled and pushed from the `./resources` directory by default. +This directory can be configured with the `--directory`/`-d` flags. +{{< /admonition >}} + +1. Make changes to dashboards and other resources using the Grafana UI in your **development instance**. +1. Pull those resources from the development environment to your local machine: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "dev" + grafanactl resources pull -d ./resources/ -o yaml # or json + ``` + +1. (Optional) Preview the resources locally before pushing: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources serve -d ./resources/ + ``` + +1. Switch to the **production instance** and push the resources: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources push -d ./resources/ + ``` + +## Back up Grafana resources + +This workflow helps you back up all Grafana resources from one instance and later restore them. This is useful to replicate a configuration or perform disaster recovery. + +1. Use `grafanactl` to pull all resources from your target environment: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources pull -d ./resources/ -o yaml # or json + ``` + +1. Save the exported resources to version control or cloud storage. + +## Restore Grafana resources + +1. (Optional) Preview the backup locally: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources serve -d ./resources/ + ``` + +1. To restore the resources later or restore them on another instance, push the saved resources: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + grafanactl resources push -d ./resources/ + ``` + +## Manage dashboards as code + +With this workflow, you can define and manage dashboards as code, saving them to a version control system like Git. This is useful for teams that want to maintain a history of changes, collaborate on dashboard design, and ensure consistency across environments. + +1. Use a dashboard generation script (for example, with the [Foundation SDK](https://github.com/grafana/grafana-foundation-sdk)). You can find an example implementation in the Grafana as code [hands-on lab repository](https://github.com/grafana/dashboards-as-code-workshop/tree/main/part-one-golang). + +1. Serve and preview the output of the dashboard generator locally: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "dev" + grafanactl resources serve --script 'go run scripts/generate-dashboard.go' --watch './scripts' + ``` + +1. When the output looks correct, generate dashboard manifest files: + + ```bash + go run scripts/generate-dashboard.go --generate-resource-manifests --output './resources' + ``` + +1. Push the generated resources to your Grafana instance: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "dev" + grafanactl resources push -d ./resources/ + ``` + +## Explore and modify resources from the terminal + +This section describes how to use the Grafana CLI to interact with Grafana resources directly from your terminal. These commands allow you to browse, inspect, update, and delete resources without using the Grafana UI. This approach is useful for advanced users who want to manage resources more efficiently or integrate Grafana operations into automated workflows. + +### Find and delete dashboards using invalid data sources + +Use this workflow to identify dashboards that reference incorrect or outdated data sources, and remove them if necessary. + +1. Set the context to the appropriate environment: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + ``` + +1. Find dashboards using specific data sources: + + ```bash + grafanactl resources get dashboards -ojson | jq '.items | map({ uid: .metadata.name, datasources: .spec.panels | map(.datasource.uid) })' + [ + { + "uid": "important-production-dashboard", + "datasources": [ + "mimir-prod" + ] + }, + { + "uid": "test-dashboard-from-dev", + "datasources": [ + "mimir-prod", + "mimir-dev" + ] + }, + { + "uid": "test-dashboard-from-stg", + "datasources": [ + "mimir-prod", + "mimir-stg", + "mimir-dev" + ] + } + ] + ``` + + This command lists dashboard UIDs along with the data source UIDs used in their panels. You can then identify the dashboards that are using invalid or unexpected data sources. + +1. Delete the identified dashboards directly: + + ```bash + grafanactl resources delete dashboards/test-dashboard-from-stg,test-dashboard-from-dev + ✔ 2 resources deleted, 0 errors + ``` + +### Find and deprecate dashboards using the old API version + +Use this workflow to locate dashboards using a deprecated API version and mark them accordingly. + +1. Set the context to the appropriate environment: + + ```bash + grafanactl config use-context YOUR_CONTEXT # for example "prod" + ``` + +1. List all available resources types and versions: + + ```bash + grafanactl resources list + ``` + + This command returns a list of resources, including their versions, types, and quantities: + + ```bash + GROUP VERSION KIND + folder.grafana.app v1 folder + dashboard.grafana.app v1 dashboard + dashboard.grafana.app v1 librarypanel + dashboard.grafana.app v2 dashboard + dashboard.grafana.app v2 librarypanel + playlist.grafana.app v1 playlist + ``` + +1. Find dashboards that are still using an old API version: + + ```bash + grafanactl resources get dashboards.v1.dashboard.grafana.app + ``` + + This command returns a table displaying the resource type, resource name, and associated namespace: + + ```bash + KIND NAME NAMESPACE + dashboards really-old-dashboard default + ``` + +1. Edit each of these dashboards to add a `deprecated` tag: + + ```bash + grafanactl resources edit dashboards.v1.dashboard.grafana.app/really-old-dashboard -p '{"spec":{"tags":["deprecated"]}}' + ``` + +{{< admonition type="tip" >}} +You can get help by using the `grafanactl --help` command. +{{< /admonition >}} diff --git a/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md b/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md new file mode 100644 index 00000000000..3277090c21f --- /dev/null +++ b/docs/sources/observability-as-code/grafana-cli/install-grafana-cli.md @@ -0,0 +1,51 @@ +--- +_build: + list: false +noindex: true +cascade: + noindex: true +description: Installation guide for Grafana CLI, a command line tool for managing Grafana Observability as Code +keywords: + - configuration + - Grafana CLI + - CLI + - command line + - grafanactl + - installation +labels: + products: + - cloud + - enterprise + - oss +title: Install Grafana CLI +weight: 100 +--- + +# Install Grafana CLI + +You can install the project using one of the following supported methods: + +## 1. Download a pre-built binary + +Download the latest binary for your platform from the [Releases page](https://github.com/grafana/grafanactl/releases). + +Prebuilt binaries are available for a variety of operating systems and architectures. Visit the latest release page, and scroll down to the Assets section. + +To install the binary, follow the instructions below: + +1. Download the archive for the desired operating system and architecture +1. Extract the archive +1. Move the executable to the desired directory +1. Ensure this directory is included in the PATH environment variable +1. Verify that you have execute permission on the file + +## 2. Build from source + +To build `grafanactl` from source you must: + +- Have `git` installed +- Have `go` v1.24 (or greater) installed + +```bash +go install github.com/grafana/grafanactl/cmd@latest +``` diff --git a/docs/sources/observability-as-code/grafana-cli/set-up-grafana-cli.md b/docs/sources/observability-as-code/grafana-cli/set-up-grafana-cli.md new file mode 100644 index 00000000000..8da1a45257d --- /dev/null +++ b/docs/sources/observability-as-code/grafana-cli/set-up-grafana-cli.md @@ -0,0 +1,122 @@ +--- +_build: + list: false +noindex: true +cascade: + noindex: true +description: Configuration guide for Grafana CLI, a command line tool for managing Grafana resources as code. +keywords: + - configuration + - Grafana CLI + - CLI + - command line + - grafanactl +labels: + products: + - cloud + - enterprise + - oss +title: Set up Grafana CLI +weight: 200 +--- + +# Set up Grafana CLI + +You can configure Grafana CLI in two ways: using environment variables or through a configuration file. + +- **Environment variables** are ideal for CI environments and support a single context. +- **Configuration files** can manage multiple contexts, making it easier to switch between different Grafana instances. + +## Use environment variables + +Grafana CLI communicates with Grafana via its REST API, which requires authentication credentials. + +At a minimum, set the URL of your Grafana instance and the organization ID: + +```bash +GRAFANA_SERVER='http://localhost:3000' GRAFANA_ORG_ID='1' grafanactl config check +``` + +Depending on your authentication method, you may also need to set: + +- A [token](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#grafana_token) for a [Grafana service account](https://grafana.com/docs/grafana/latest/administration/service-accounts/) (recommended) +- A [username](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#grafana_user) and [password](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#grafana_password) for basic authentication + +To persist your configuration, consider [creating a context](#defining-contexts). + +A full list of supported environment variables is available in the [reference documentation](https://github.com/grafana/grafanactl/blob/main/docs/reference/environment-variables/index.md#environment-variables-reference). + +## Define contexts + +Contexts allow you to easily switch between multiple Grafana instances. By default, the CLI uses a context named `default`. + +To configure the `default` context: + +```bash +grafanactl config set contexts.default.grafana.server http://localhost:3000 +grafanactl config set contexts.default.grafana.org-id 1 + +# Authenticate with a service account token +grafanactl config set contexts.default.grafana.token service-account-token + +# Or use basic authentication +grafanactl config set contexts.default.grafana.user admin +grafanactl config set contexts.default.grafana.password admin +``` + +You can define additional contexts in the same way: + +```bash +grafanactl config set contexts.staging.grafana.server https://staging.grafana.example +grafanactl config set contexts.staging.grafana.org-id 1 +``` + +{{< admonition type="note" >}} +In these examples, `default` and `staging` are the names of the contexts. +{{< /admonition >}} + +## Configuration file + +Grafana CLI stores its configuration in a YAML file. The CLI determines the configuration file location in the following order: + +1. If the `--config` flag is provided, the specified file is used. +2. If `$XDG_CONFIG_HOME` is set: + `$XDG_CONFIG_HOME/grafanactl/config.yaml` +3. If `$HOME` is set: + `$HOME/.config/grafanactl/config.yaml` +4. If `$XDG_CONFIG_DIRS` is set: + `$XDG_CONFIG_DIRS/grafanactl/config.yaml` + +{{< admonition type="note" >}} +Use `grafanactl config check` to display the configuration file currently in use. +{{< /admonition >}} + +## Useful commands + +Check the current configuration: + +```bash +grafanactl config check +``` + +{{< admonition type="note" >}} +This command is useful to troubleshoot your configuration. +{{< /admonition >}} + +List all available contexts: + +```bash +grafanactl config list-contexts +``` + +Switch to a specific context: + +```bash +grafanactl config use-context staging +``` + +View the full configuration: + +```bash +grafanactl config view +``` diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index ad403d30d28..ff082322cb8 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -855,6 +855,23 @@ Path to the default home dashboard. If this value is empty, then Grafana uses St On Linux, Grafana uses `/usr/share/grafana/public/dashboards/home.json` as the default home dashboard location. {{< /admonition >}} +### `[dashboard_cleanup]` + +Settings related to cleaning up associated dashboards information if the dashboard was deleted through /apis. + +#### `interval` + +How often to run the job to cleanup associated resources. The default interval is `30s`. The minimum allowed value is `10s` to ensure the system isn't overloaded. + +The interval string must include a unit suffix (ms, s, m, h), e.g. 30s or 1m. + +#### `batch_size` + +Number of deleted dashboards to process in each batch during the cleanup process. +Default: `10`, Minimum: `5`, Maximum: `200`. + +Increasing this value allows processing more dashboards in each cleanup cycle but may impact system performance. +
### `[datasources]` diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 3472114015f..f340ab99a01 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -46,7 +46,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes | | `formatString` | Enable format string transformer | Yes | | `kubernetesClientDashboardsFolders` | Route the folder and dashboard service requests to k8s | Yes | -| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes | | `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes | | `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes | | `annotationPermissionUpdate` | Change the way annotation permissions work by scoping them to folders and dashboards. | Yes | diff --git a/docs/sources/upgrade-guide/when-to-upgrade/index.md b/docs/sources/upgrade-guide/when-to-upgrade/index.md index 4c6f9602f04..0a464e96f5d 100644 --- a/docs/sources/upgrade-guide/when-to-upgrade/index.md +++ b/docs/sources/upgrade-guide/when-to-upgrade/index.md @@ -46,22 +46,30 @@ We provide release documentation in multiple places to address different needs: ## When to expect releases -Currently, Grafana is on a monthly release cycle. Here’s a look at scheduled releases for the first half of 2025: +Currently, Grafana is on a monthly release cycle. Here’s a look at scheduled releases for 2025: -| **Anticipated release date** | **Grafana versions** | **Release type** | -| ---------------------------- | --------------------------------- | ---------------- | -| Jan. 28, 2025 | 11.5 & Supported versions | Minor & patching | -| Feb. 18, 2025 | Supported versions | Patching | -| March 25, 2025 | 11.6 & Supported versions | Minor & patching | -| April 15, 2025 | Supported versions | Patching | -| May 5, 2025 | Grafana 12.0 & Supported versions | Major & patching | +| **Release date** | **Grafana versions** | **Release type** | +| ---------------- | ------------------------- | ---------------- | +| Jan. 28, 2025 | 11.5 & Supported versions | Minor & patching | +| Feb. 18, 2025 | Supported versions | Patching | +| March 25, 2025 | 11.6 & Supported versions | Minor & patching | +| April 23, 2025 | Supported versions | Patching | +| May 5, 2025 | Grafana 12.0 | Major only | +| May 20, 2025 | Supported versions | Patching | +| June 17, 2025 | Supported versions | Patching | +| July 22, 2025 | 12.1 & Supported versions | Minor & patching | +| Aug. 12, 2025 | Supported versions | Patching | +| Sept. 23, 2025 | 12.2 & Supported versions | Minor & patching | +| Oct. 21, 2025 | Supported versions | Patching | +| Nov. 18, 2025 | 12.3 & Supported versions | Minor & patching | +| Dec. 16, 2025 | Supported versions | Patching | ### A few important notes - The schedule above outlines how we plan release dates. However, unforeseen events and circumstances may cause dates to change. - High severity security and feature degradation incidents will result in ad-hoc releases that are not scheduled ahead of time. - Patching releases are for the current (last released) minor version of Grafana. Additional older versions of Grafana may be included if there is a critical bug or security vulnerability that needs to be patched. -- Release freezes: Each year Grafana implements two release freezes to accommodate for the holiday season, these dates will be announced as the holiday season approaches. During these times, no scheduled releases will be executed. However, this does not apply to changes that may be required during the course of an operational or security incident. +- Release freezes: Each year Grafana implements two release freezes to accommodate for the holiday season. During these times, no scheduled releases will be executed. However, this does not apply to changes that may be required during the course of an operational or security incident. ## Grafana security releases: improved version naming convention @@ -81,34 +89,57 @@ This naming convention should make it easier to identify security updates and th ## What to know about version support -Self-managed Grafana users have control over when they upgrade to a new version of Grafana. To help you make an informed decision about whether it's time to upgrade, it’s important that you understand the level of support provided for your current version. +Self-managed Grafana users have control over when they upgrade to a new version of Grafana. To help you make an informed decision about whether it’s time to upgrade, it’s important that you understand the level of support provided for your current version. -For self-managed Grafana (both Enterprise and OSS), the support for versions is as follows: +For self-managed Grafana (both Enterprise and OSS), the support for versions follows these rules: -- Support for each minor release extends to nine months after the release date. -- Support for the last minor release of a major version is extended an additional six months, for a total of 15 months of support after the release date. +- Each minor release is supported for 9 months after its release date +- The last minor release of a major version receives extended support for 15 months after its release date +- Support levels change as new versions are released: + - **Full Support**: The most recently released major/minor (and the last minor of the previous major) version receive full support including new features, bug fixes, and security patches + - **Security & Critical Bugs Only**: Versions that are not the most recently released major/minor (or the last minor of the previous major) version, but still within their support period, receive only security patches and critical bug fixes + - **Not Supported**: Versions beyond their support period receive no updates -Here is an overview of projected version support through 2025: +Here is an overview of version support through 2026: -| **Version** | **Release date** | **Support end of life (EOL)** | -| ----------------------- | ---------------- | --------------------------------------- | -| 10.4 (Last minor of 10) | March 2024 | June 2025 (extended support) | -| 11.0 | May 2024 | NO LONGER SUPPORTED as of February 2025 | -| 11.1 | June 2024 | NO LONGER SUPPORTED as of March 2025 | -| 11.2 | August 2024 | May 2025 | -| 11.3 | October 2024 | July 2025 | -| 11.4 | December 2024 | September 2025 | -| 11.5 | January 2025 | October 2025 | -| 11.6 (Last minor of 11) | March 2025 | June 2026 | -| 12.0 | May 2025 | January 2026 | +| **Version** | **Release date** | **Support end date** | **Support level** | +| ------------------------- | ------------------ | -------------------- | ----------------------------- | +| 9.5.x (Last minor of 9) | April 26, 2023 | July 26, 2024 | Supported for Azure Only | +| 10.0.x | June 13, 2023 | March 13, 2024 | Not Supported | +| 10.1.x | August 22, 2023 | May 22, 2024 | Not Supported | +| 10.2.x | October 24, 2023 | July 24, 2024 | Not Supported | +| 10.3.x | January 23, 2024 | October 23, 2024 | Not Supported | +| 10.4.x (Last minor of 10) | March 5, 2024 | June 5, 2025 | Security & Critical Bugs Only | +| 11.0.x | May 14, 2024 | February 14, 2025 | Security & Critical Bugs Only | +| 11.1.x | June 25, 2024 | April 23, 2025 | Security & Critical Bugs Only | +| 11.2.x | August 27, 2024 | May 27, 2025 | Security & Critical Bugs Only | +| 11.3.x | October 22, 2024 | July 22, 2025 | Security & Critical Bugs Only | +| 11.4.x | December 5, 2024 | September 5, 2025 | Security & Critical Bugs Only | +| 11.5.x | January 28, 2025 | October 28, 2025 | Security & Critical Bugs Only | +| 11.6.x (Last minor of 11) | March 25, 2025 | May 25, 2026 | Full Support | +| 12.0.x | May 5, 2025 | February 5, 2026 | Full Support until next minor | +| 12.1.x | July 22, 2025 | April 22, 2026 | Full Support until next minor | +| 12.2.x | September 23, 2025 | June 23, 2026 | Full Support until next minor | +| 12.3.x | November 18, 2025 | August 18, 2026 | Full Support until next minor | ## How are these versions supported? -The level of support changes as new versions of Grafana are released. Here are a few details to keep in mind: +The level of support changes as new versions of Grafana are released. Here are the key details: -- The current (most recently released) version of Grafana gets the highest level of support. Releases for this version include all the new features along with all bug fixes. -- All supported versions receive security patches for vulnerabilities impacting that version. -- All supported versions receive patches for bugs that cause critical feature degradation incidents. +- **Full Support**: + + - All new features + - All bug fixes + - Security patches + - Regular updates + +- **Security & Critical Bugs Only**: + + - Security vulnerability patches + - Critical bug fixes that cause feature degradation + - No new features + +- **Not Supported**: Versions beyond their support period receive no updates and should be upgraded. Keeping all this in mind, users that want to receive the most recent features and all bug fixes should be on the current (most recently released) version of Grafana. diff --git a/e2e/test-plugins/grafana-extensionstest-app/package.json b/e2e/test-plugins/grafana-extensionstest-app/package.json index d5f05088d35..73e3b952bc8 100644 --- a/e2e/test-plugins/grafana-extensionstest-app/package.json +++ b/e2e/test-plugins/grafana-extensionstest-app/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/extensions-test-app", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "private": true, "scripts": { "build": "webpack -c ./webpack.config.ts --env production", diff --git a/e2e/test-plugins/grafana-test-datasource/package.json b/e2e/test-plugins/grafana-test-datasource/package.json index fce57ed8757..bfac0051d93 100644 --- a/e2e/test-plugins/grafana-test-datasource/package.json +++ b/e2e/test-plugins/grafana-test-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/grafana-e2etest-datasource", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "private": true, "scripts": { "build": "webpack -c ./webpack.config.ts --env production", diff --git a/go.mod b/go.mod index 054f66e485a..5aaa373613b 100644 --- a/go.mod +++ b/go.mod @@ -77,7 +77,7 @@ require ( github.com/googleapis/go-sql-spanner v1.11.1 // @grafana/grafana-search-and-storage github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1 // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 012e22ebd0e..797bef5179e 100644 --- a/go.sum +++ b/go.sum @@ -1565,8 +1565,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1 h1:xdTzIQqFJxzRLgODsFMAT/8LIWtqCXLqltaWo6AmFVA= -github.com/grafana/alerting v0.0.0-20250418140648-7268b2b6d9f1/go.mod h1:XTbf+jPVVMC1C2NuSGa3hIVbO+KSQD0qHjiUr1GoWVI= +github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb h1:Nkqatz7R7K/2YaK+7pw8CAYgylKfksPVeT7gMARKjwI= +github.com/grafana/alerting v0.0.0-20250425150043-be11a2ae18bb/go.mod h1:pMfhRxL2LZ3Pm8iy7VcVsb9CLYuBtjFYbf1oxgx7yFA= github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a h1:irEH0Abl6mKbkPx/xtmB5Wai4ipzEB6hGPNsLya/p9Y= github.com/grafana/authlib v0.0.0-20250422131730-e8482efe6b8a/go.mod h1:PBtQaXwkFu4BAt2aXsR7w8p8NVpdjV5aJYhqRDei9Us= github.com/grafana/authlib/types v0.0.0-20250325095148-d6da9c164a7d h1:34E6btDAhdDOiSEyrMaYaHwnJpM8w9QKzVQZIBzLNmM= diff --git a/lerna.json b/lerna.json index e0645ae04bc..71b3ceeef8f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "npmClient": "yarn", - "version": "12.0.0-pre" + "version": "12.1.0-pre" } diff --git a/package.json b/package.json index 226898e3351..1fb6e4e45f5 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "grafana", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "repository": "github:grafana/grafana", "scripts": { "build": "NODE_ENV=production nx exec --verbose -- webpack --config scripts/webpack/webpack.prod.js --progress", @@ -64,7 +64,8 @@ "plugin:build:commit": "nx run-many -t build:commit --projects='tag:scope:plugin'", "plugin:build:dev": "nx run-many -t dev --projects='tag:scope:plugin' --maxParallel=100", "process-specs": "node --experimental-strip-types scripts/process-specs.ts", - "generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts" + "generate-apis": "yarn process-specs && rtk-query-codegen-openapi ./scripts/generate-rtk-apis.ts", + "generate:api-client": "NODE_OPTIONS='--experimental-strip-types' plop --plopfile public/app/api/generator/plopfile.ts" }, "grafana": { "whatsNewUrl": "https://grafana.com/docs/grafana/next/whatsnew/whats-new-in-v%[1]s-%[2]s/", @@ -168,7 +169,7 @@ "crashme": "0.0.15", "css-loader": "7.1.2", "css-minimizer-webpack-plugin": "7.0.0", - "cypress": "13.10.0", + "cypress": "14.3.2", "cypress-file-upload": "5.0.8", "cypress-recurse": "^1.35.3", "esbuild": "0.25.0", @@ -216,6 +217,7 @@ "nx": "20.7.1", "openapi-types": "^12.1.3", "pdf-parse": "^1.1.1", + "plop": "^4.0.1", "postcss": "8.5.1", "postcss-loader": "8.1.1", "postcss-reporter": "7.1.0", @@ -255,9 +257,10 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@fingerprintjs/fingerprintjs": "^3.4.2", - "@floating-ui/react": "0.27.7", + "@floating-ui/react": "0.27.8", "@formatjs/intl-durationformat": "^0.7.0", "@glideapps/glide-data-grid": "^6.0.0", + "@grafana/alerting": "workspace:*", "@grafana/aws-sdk": "0.6.0", "@grafana/azure-sdk": "0.0.7", "@grafana/data": "workspace:*", @@ -274,8 +277,8 @@ "@grafana/plugin-ui": "0.10.5", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "6.10.0", - "@grafana/scenes-react": "6.10.0", + "@grafana/scenes": "6.10.2", + "@grafana/scenes-react": "6.10.2", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", @@ -344,7 +347,7 @@ "lru-cache": "11.0.2", "lru-memoize": "^1.1.0", "lucene": "^2.1.1", - "marked": "15.0.6", + "marked": "15.0.11", "memoize-one": "6.0.0", "micro-memoize": "^4.1.2", "ml-regression-polynomial": "^3.0.0", diff --git a/packages/grafana-alerting/CHANGELOG.md b/packages/grafana-alerting/CHANGELOG.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/grafana-alerting/LICENSE_APACHE2 b/packages/grafana-alerting/LICENSE_APACHE2 new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/packages/grafana-alerting/LICENSE_APACHE2 @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/grafana-alerting/README.md b/packages/grafana-alerting/README.md new file mode 100644 index 00000000000..94284bad290 --- /dev/null +++ b/packages/grafana-alerting/README.md @@ -0,0 +1,9 @@ +# Grafana Alerting + +> **@grafana/alerting is currently in ALPHA**. + +This package is a collection of types, libraries, utilities, React components and hooks for interacting with the Grafana alerting system and is meant to be the foundation of alerting-related features in Grafana and its plugins. + +We plan to eventually publish this package on NPM; however, it is currently in ALPHA and is not yet ready for public consumption. We are actively working on this package and will be making breaking changes to it as we iterate on the design and implementation. + +Once we've settled on a public API that we feel is stable and useful, we will publish this package on NPM and provide documentation on how to use it. diff --git a/packages/grafana-alerting/package.json b/packages/grafana-alerting/package.json new file mode 100644 index 00000000000..b52ef4b1139 --- /dev/null +++ b/packages/grafana-alerting/package.json @@ -0,0 +1,63 @@ +{ + "author": "Grafana Labs", + "license": "Apache-2.0", + "name": "@grafana/alerting", + "version": "12.0.0-pre", + "private": true, + "description": "Grafana Alerting Library – Build vertical integrations on top of the industry-leading alerting solution", + "keywords": [ + "typescript", + "grafana", + "alerting", + "alertmanager", + "prometheus" + ], + "sideEffects": false, + "repository": { + "type": "git", + "url": "http://github.com/grafana/grafana.git", + "directory": "packages/grafana-alerting" + }, + "main": "src/index.ts", + "types": "src/index.ts", + "module": "src/index.ts", + "exports": { + ".": { + "import": "./src/index.ts", + "require": "./src/index.ts" + }, + "./internal": { + "import": "./src/internal.ts", + "require": "./src/internal.ts" + }, + "./unstable": { + "import": "./src/unstable.ts", + "require": "./src/unstable.ts" + } + }, + "scripts": { + "typecheck": "tsc --emitDeclarationOnly false --noEmit", + "codegen": "yarn run rtk-query-codegen-openapi ./scripts/codegen.ts" + }, + "devDependencies": { + "@grafana/tsconfig": "^2.0.0", + "@rtk-query/codegen-openapi": "^2.0.0", + "@types/lodash": "^4", + "@types/react": "18.3.18", + "@types/react-dom": "18.3.5", + "react": "18.3.1", + "react-dom": "18.3.1", + "type-fest": "^4.40.0", + "typescript": "5.7.3" + }, + "peerDependencies": { + "@grafana/runtime": "^12.0.0-pre", + "@grafana/ui": "^12.0.0-pre", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "dependencies": { + "@reduxjs/toolkit": "^2.7.0", + "lodash": "^4.17.21" + } +} diff --git a/packages/grafana-alerting/project.json b/packages/grafana-alerting/project.json new file mode 100644 index 00000000000..2fe46a23eff --- /dev/null +++ b/packages/grafana-alerting/project.json @@ -0,0 +1,6 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "tags": ["scope:package", "type:ui"], + "targets": {} +} diff --git a/packages/grafana-alerting/scripts/codegen.ts b/packages/grafana-alerting/scripts/codegen.ts new file mode 100644 index 00000000000..1af5010092f --- /dev/null +++ b/packages/grafana-alerting/scripts/codegen.ts @@ -0,0 +1,21 @@ +/** + * This script will generate TypeScript type definitions and a RTKQ client for the alerting k8s APIs. + * It downloads the OpenAPI schema from a running Grafana instance and generates the types. + * + * Run `yarn run codegen` from the "grafana-alerting" package to invoke this script. + */ +import { type ConfigFile } from '@rtk-query/codegen-openapi'; +import { resolve } from 'node:path'; + +// these snapshots are generated by running "go test pkg/tests/apis/openapi_test.go", see the README in the "openapi_snapshots" directory +const OPENAPI_SCHEMA_LOCATION = resolve( + '../../../pkg/tests/apis/openapi_snapshots/notifications.alerting.grafana.app-v0alpha1.json' +); + +export default { + exportName: 'alertingAPI', + schemaFile: OPENAPI_SCHEMA_LOCATION, + apiFile: '../src/grafana/api.ts', + outputFile: resolve('../src/grafana/api.gen.ts'), + tag: true, +} satisfies ConfigFile; diff --git a/packages/grafana-alerting/src/grafana/api.gen.ts b/packages/grafana-alerting/src/grafana/api.gen.ts new file mode 100644 index 00000000000..dd6ebf30830 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/api.gen.ts @@ -0,0 +1,1602 @@ +import { api } from './api'; +export const addTagTypes = ['API Discovery', 'Receiver', 'RoutingTree', 'TemplateGroup', 'TimeInterval'] as const; +const injectedRtkApi = api + .enhanceEndpoints({ + addTagTypes, + }) + .injectEndpoints({ + endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/notifications.alerting.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), + listReceiver: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['Receiver'], + }), + createReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers`, + method: 'POST', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Receiver'], + }), + deletecollectionReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['Receiver'], + }), + getReceiver: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Receiver'], + }), + replaceReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers/${queryArg.name}`, + method: 'PUT', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Receiver'], + }), + deleteReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['Receiver'], + }), + updateReceiver: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/receivers/${queryArg.name}`, + method: 'PATCH', + body: queryArg.ioK8SApimachineryPkgApisMetaV1Patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Receiver'], + }), + listRoutingTree: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['RoutingTree'], + }), + createRoutingTree: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees`, + method: 'POST', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + deletecollectionRoutingTree: build.mutation< + DeletecollectionRoutingTreeApiResponse, + DeletecollectionRoutingTreeApiArg + >({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + getRoutingTree: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['RoutingTree'], + }), + replaceRoutingTree: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees/${queryArg.name}`, + method: 'PUT', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + deleteRoutingTree: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + updateRoutingTree: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/routingtrees/${queryArg.name}`, + method: 'PATCH', + body: queryArg.ioK8SApimachineryPkgApisMetaV1Patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['RoutingTree'], + }), + listTemplateGroup: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['TemplateGroup'], + }), + createTemplateGroup: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups`, + method: 'POST', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + deletecollectionTemplateGroup: build.mutation< + DeletecollectionTemplateGroupApiResponse, + DeletecollectionTemplateGroupApiArg + >({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + getTemplateGroup: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['TemplateGroup'], + }), + replaceTemplateGroup: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups/${queryArg.name}`, + method: 'PUT', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + deleteTemplateGroup: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + updateTemplateGroup: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/templategroups/${queryArg.name}`, + method: 'PATCH', + body: queryArg.ioK8SApimachineryPkgApisMetaV1Patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['TemplateGroup'], + }), + listTimeInterval: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals`, + params: { + pretty: queryArg.pretty, + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['TimeInterval'], + }), + createTimeInterval: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals`, + method: 'POST', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + deletecollectionTimeInterval: build.mutation< + DeletecollectionTimeIntervalApiResponse, + DeletecollectionTimeIntervalApiArg + >({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + continue: queryArg['continue'], + dryRun: queryArg.dryRun, + fieldSelector: queryArg.fieldSelector, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + getTimeInterval: build.query({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['TimeInterval'], + }), + replaceTimeInterval: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals/${queryArg.name}`, + method: 'PUT', + body: queryArg.comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + deleteTimeInterval: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + updateTimeInterval: build.mutation({ + query: (queryArg) => ({ + url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals/${queryArg.name}`, + method: 'PATCH', + body: queryArg.ioK8SApimachineryPkgApisMetaV1Patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['TimeInterval'], + }), + }), + overrideExisting: false, + }); +export { injectedRtkApi as alertingAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1ApiResourceList; +export type GetApiResourcesApiArg = void; +export type ListReceiverApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1ReceiverList; +export type ListReceiverApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateReceiverApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver + | /** status 202 Accepted */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +export type CreateReceiverApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +}; +export type DeletecollectionReceiverApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeletecollectionReceiverApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetReceiverApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +export type GetReceiverApiArg = { + /** name of the Receiver */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceReceiverApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +export type ReplaceReceiverApiArg = { + /** name of the Receiver */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +}; +export type DeleteReceiverApiResponse = /** status 200 OK */ + | IoK8SApimachineryPkgApisMetaV1Status + | /** status 202 Accepted */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeleteReceiverApiArg = { + /** name of the Receiver */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateReceiverApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver; +export type UpdateReceiverApiArg = { + /** name of the Receiver */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + ioK8SApimachineryPkgApisMetaV1Patch: IoK8SApimachineryPkgApisMetaV1Patch; +}; +export type ListRoutingTreeApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTreeList; +export type ListRoutingTreeApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateRoutingTreeApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree + | /** status 202 Accepted */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +export type CreateRoutingTreeApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +}; +export type DeletecollectionRoutingTreeApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeletecollectionRoutingTreeApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetRoutingTreeApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +export type GetRoutingTreeApiArg = { + /** name of the RoutingTree */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceRoutingTreeApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +export type ReplaceRoutingTreeApiArg = { + /** name of the RoutingTree */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +}; +export type DeleteRoutingTreeApiResponse = /** status 200 OK */ + | IoK8SApimachineryPkgApisMetaV1Status + | /** status 202 Accepted */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeleteRoutingTreeApiArg = { + /** name of the RoutingTree */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateRoutingTreeApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree; +export type UpdateRoutingTreeApiArg = { + /** name of the RoutingTree */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + ioK8SApimachineryPkgApisMetaV1Patch: IoK8SApimachineryPkgApisMetaV1Patch; +}; +export type ListTemplateGroupApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroupList; +export type ListTemplateGroupApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateTemplateGroupApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup + | /** status 202 Accepted */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +export type CreateTemplateGroupApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +}; +export type DeletecollectionTemplateGroupApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeletecollectionTemplateGroupApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetTemplateGroupApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +export type GetTemplateGroupApiArg = { + /** name of the TemplateGroup */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceTemplateGroupApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +export type ReplaceTemplateGroupApiArg = { + /** name of the TemplateGroup */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +}; +export type DeleteTemplateGroupApiResponse = /** status 200 OK */ + | IoK8SApimachineryPkgApisMetaV1Status + | /** status 202 Accepted */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeleteTemplateGroupApiArg = { + /** name of the TemplateGroup */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateTemplateGroupApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup; +export type UpdateTemplateGroupApiArg = { + /** name of the TemplateGroup */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + ioK8SApimachineryPkgApisMetaV1Patch: IoK8SApimachineryPkgApisMetaV1Patch; +}; +export type ListTimeIntervalApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeIntervalList; +export type ListTimeIntervalApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** allowWatchBookmarks requests watch events with type "BOOKMARK". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored. */ + allowWatchBookmarks?: boolean; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; + /** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */ + watch?: boolean; +}; +export type CreateTimeIntervalApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval + | /** status 202 Accepted */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +export type CreateTimeIntervalApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +}; +export type DeletecollectionTimeIntervalApiResponse = /** status 200 OK */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeletecollectionTimeIntervalApiArg = { + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key". + + This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */ + continue?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */ + fieldSelector?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */ + labelSelector?: string; + /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true. + + The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */ + limit?: number; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; + /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersion?: string; + /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details. + + Defaults to unset */ + resourceVersionMatch?: string; + /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched. + + When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan + is interpreted as "data at least as new as the provided `resourceVersion`" + and the bookmark event is send when the state is synced + to a `resourceVersion` at least as fresh as the one provided by the ListOptions. + If `resourceVersion` is unset, this is interpreted as "consistent read" and the + bookmark event is send when the state is synced at least to the moment + when request started being processed. + - `resourceVersionMatch` set to any other value or unset + Invalid error is returned. + + Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */ + sendInitialEvents?: boolean; + /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */ + timeoutSeconds?: number; +}; +export type GetTimeIntervalApiResponse = + /** status 200 OK */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +export type GetTimeIntervalApiArg = { + /** name of the TimeInterval */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; +}; +export type ReplaceTimeIntervalApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +export type ReplaceTimeIntervalApiArg = { + /** name of the TimeInterval */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + comGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +}; +export type DeleteTimeIntervalApiResponse = /** status 200 OK */ + | IoK8SApimachineryPkgApisMetaV1Status + | /** status 202 Accepted */ IoK8SApimachineryPkgApisMetaV1Status; +export type DeleteTimeIntervalApiArg = { + /** name of the TimeInterval */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */ + gracePeriodSeconds?: number; + /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */ + ignoreStoreReadErrorWithClusterBreakingPotential?: boolean; + /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */ + orphanDependents?: boolean; + /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */ + propagationPolicy?: string; +}; +export type UpdateTimeIntervalApiResponse = /** status 200 OK */ + | ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval + | /** status 201 Created */ ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval; +export type UpdateTimeIntervalApiArg = { + /** name of the TimeInterval */ + name: string; + /** object name and auth scope, such as for teams and projects */ + namespace: string; + /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */ + pretty?: string; + /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */ + dryRun?: string; + /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch). */ + fieldManager?: string; + /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */ + fieldValidation?: string; + /** Force is going to "force" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests. */ + force?: boolean; + ioK8SApimachineryPkgApisMetaV1Patch: IoK8SApimachineryPkgApisMetaV1Patch; +}; +export type IoK8SApimachineryPkgApisMetaV1ApiResource = { + /** categories is a list of the grouped resources this resource belongs to (e.g. 'all') */ + categories?: string[]; + /** group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale". */ + group?: string; + /** kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') */ + kind: string; + /** name is the plural name of the resource. */ + name: string; + /** namespaced indicates if a resource is namespaced or not. */ + namespaced: boolean; + /** shortNames is a list of suggested short names of the resource. */ + shortNames?: string[]; + /** singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface. */ + singularName: string; + /** The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates. */ + storageVersionHash?: string; + /** verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy) */ + verbs: string[]; + /** version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". */ + version?: string; +}; +export type IoK8SApimachineryPkgApisMetaV1ApiResourceList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** groupVersion is the group and version this APIResourceList is for. */ + groupVersion: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** resources contains the name of the resources and if they are namespaced. */ + resources: IoK8SApimachineryPkgApisMetaV1ApiResource[]; +}; +export type IoK8SApimachineryPkgApisMetaV1Time = string; +export type IoK8SApimachineryPkgApisMetaV1FieldsV1 = object; +export type IoK8SApimachineryPkgApisMetaV1ManagedFieldsEntry = { + /** APIVersion defines the version of this resource that this field set applies to. The format is "group/version" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted. */ + apiVersion?: string; + /** FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: "FieldsV1" */ + fieldsType?: string; + /** FieldsV1 holds the first JSON version format as described in the "FieldsV1" type. */ + fieldsV1?: IoK8SApimachineryPkgApisMetaV1FieldsV1; + /** Manager is an identifier of the workflow managing these fields. */ + manager?: string; + /** Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'. */ + operation?: string; + /** Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource. */ + subresource?: string; + /** Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over. */ + time?: IoK8SApimachineryPkgApisMetaV1Time; +}; +export type IoK8SApimachineryPkgApisMetaV1OwnerReference = { + /** API version of the referent. */ + apiVersion: string; + /** If true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs "delete" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned. */ + blockOwnerDeletion?: boolean; + /** If true, this reference points to the managing controller. */ + controller?: boolean; + /** Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind: string; + /** Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names */ + name: string; + /** UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid: string; +}; +export type IoK8SApimachineryPkgApisMetaV1ObjectMeta = { + /** Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations */ + annotations?: { + [key: string]: string; + }; + /** CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + + Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + creationTimestamp?: IoK8SApimachineryPkgApisMetaV1Time; + /** Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only. */ + deletionGracePeriodSeconds?: number; + /** DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested. + + Populated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + deletionTimestamp?: IoK8SApimachineryPkgApisMetaV1Time; + /** Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list. */ + finalizers?: string[]; + /** GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server. + + If this field is specified and the generated name exists, the server will return a 409. + + Applied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency */ + generateName?: string; + /** A sequence number representing a specific generation of the desired state. Populated by the system. Read-only. */ + generation?: number; + /** Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels */ + labels?: { + [key: string]: string; + }; + /** ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like "ci-cd". The set of fields is always in the version that the workflow used when modifying the object. */ + managedFields?: IoK8SApimachineryPkgApisMetaV1ManagedFieldsEntry[]; + /** Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names */ + name?: string; + /** Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the "default" namespace, but "default" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty. + + Must be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces */ + namespace?: string; + /** List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller. */ + ownerReferences?: IoK8SApimachineryPkgApisMetaV1OwnerReference[]; + /** An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources. + + Populated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */ + resourceVersion?: string; + /** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */ + selfLink?: string; + /** UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations. + + Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid?: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Integration = { + disableResolveMessage?: boolean; + secureFields?: { + [key: string]: boolean; + }; + settings: { + [key: string]: object; + }; + type: string; + uid?: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Spec = { + integrations: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Integration[]; + title: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1StatusOperatorState = { + /** descriptiveState is an optional more descriptive state field which has no requirements on format */ + descriptiveState?: string; + /** details contains any extra information that is operator-specific */ + details?: { + [key: string]: object; + }; + /** lastEvaluation is the ResourceVersion last evaluated */ + lastEvaluation: string; + /** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */ + state: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Status = { + /** additionalFields is reserved for future use */ + additionalFields?: { + [key: string]: object; + }; + /** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ + operatorStates?: { + [key: string]: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1StatusOperatorState; + }; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ObjectMeta; + /** Spec is the spec of the Receiver */ + spec: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Spec; + status: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Status; +}; +export type IoK8SApimachineryPkgApisMetaV1ListMeta = { + /** continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. */ + continue?: string; + /** remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact. */ + remainingItemCount?: number; + /** String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency */ + resourceVersion?: string; + /** Deprecated: selfLink is a legacy read-only field that is no longer populated by the system. */ + selfLink?: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1ReceiverList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ListMeta; +}; +export type IoK8SApimachineryPkgApisMetaV1StatusCause = { + /** The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional. + + Examples: + "name" - the field "name" on the current resource + "items[0].name" - the field "name" on the first array entry in "items" */ + field?: string; + /** A human-readable description of the cause of the error. This field may be presented as-is to a reader. */ + message?: string; + /** A machine-readable description of the cause of the error. If this value is empty there is no information available. */ + reason?: string; +}; +export type IoK8SApimachineryPkgApisMetaV1StatusDetails = { + /** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */ + causes?: IoK8SApimachineryPkgApisMetaV1StatusCause[]; + /** The group attribute of the resource associated with the status StatusReason. */ + group?: string; + /** The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described). */ + name?: string; + /** If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action. */ + retryAfterSeconds?: number; + /** UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */ + uid?: string; +}; +export type IoK8SApimachineryPkgApisMetaV1Status = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Suggested HTTP return code for this status, 0 if not set. */ + code?: number; + /** Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type. */ + details?: IoK8SApimachineryPkgApisMetaV1StatusDetails; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + /** A human-readable description of the status of this operation. */ + message?: string; + /** Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + metadata?: IoK8SApimachineryPkgApisMetaV1ListMeta; + /** A machine-readable description of why this operation is in the "Failure" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it. */ + reason?: string; + /** Status of the operation. One of: "Success" or "Failure". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status */ + status?: string; +}; +export type IoK8SApimachineryPkgApisMetaV1Patch = object; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RouteDefaults = { + group_by?: string[]; + group_interval?: string; + group_wait?: string; + receiver: string; + repeat_interval?: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Matcher = { + label: string; + type: string; + value: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Route = { + continue: boolean; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + matchers?: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Matcher[]; + mute_time_intervals?: string[]; + receiver?: string; + repeat_interval?: string; + routes?: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Route[]; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Spec = { + defaults: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RouteDefaults; + routes: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Route[]; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1StatusOperatorState = { + /** descriptiveState is an optional more descriptive state field which has no requirements on format */ + descriptiveState?: string; + /** details contains any extra information that is operator-specific */ + details?: { + [key: string]: object; + }; + /** lastEvaluation is the ResourceVersion last evaluated */ + lastEvaluation: string; + /** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */ + state: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Status = { + /** additionalFields is reserved for future use */ + additionalFields?: { + [key: string]: object; + }; + /** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ + operatorStates?: { + [key: string]: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1StatusOperatorState; + }; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ObjectMeta; + /** Spec is the spec of the RoutingTree */ + spec: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Spec; + status: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1Status; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTreeList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisRoutingtreeV0Alpha1RoutingTree[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ListMeta; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1Spec = { + content: string; + title: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1StatusOperatorState = { + /** descriptiveState is an optional more descriptive state field which has no requirements on format */ + descriptiveState?: string; + /** details contains any extra information that is operator-specific */ + details?: { + [key: string]: object; + }; + /** lastEvaluation is the ResourceVersion last evaluated */ + lastEvaluation: string; + /** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */ + state: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1Status = { + /** additionalFields is reserved for future use */ + additionalFields?: { + [key: string]: object; + }; + /** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ + operatorStates?: { + [key: string]: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1StatusOperatorState; + }; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ObjectMeta; + /** Spec is the spec of the TemplateGroup */ + spec: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1Spec; + status: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1Status; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroupList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTemplategroupV0Alpha1TemplateGroup[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ListMeta; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeRange = { + end_time: string; + start_time: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Interval = { + days_of_month?: string[]; + location?: string; + months?: string[]; + times?: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeRange[]; + weekdays?: string[]; + years?: string[]; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Spec = { + name: string; + time_intervals: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Interval[]; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1StatusOperatorState = { + /** descriptiveState is an optional more descriptive state field which has no requirements on format */ + descriptiveState?: string; + /** details contains any extra information that is operator-specific */ + details?: { + [key: string]: object; + }; + /** lastEvaluation is the ResourceVersion last evaluated */ + lastEvaluation: string; + /** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */ + state: string; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Status = { + /** additionalFields is reserved for future use */ + additionalFields?: { + [key: string]: object; + }; + /** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */ + operatorStates?: { + [key: string]: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1StatusOperatorState; + }; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ObjectMeta; + /** Spec is the spec of the TimeInterval */ + spec: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Spec; + status: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1Status; +}; +export type ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeIntervalList = { + /** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ + apiVersion?: string; + items: ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisTimeintervalV0Alpha1TimeInterval[]; + /** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ + kind?: string; + metadata: IoK8SApimachineryPkgApisMetaV1ListMeta; +}; diff --git a/packages/grafana-alerting/src/grafana/api.ts b/packages/grafana-alerting/src/grafana/api.ts new file mode 100644 index 00000000000..813a3623b2b --- /dev/null +++ b/packages/grafana-alerting/src/grafana/api.ts @@ -0,0 +1,11 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; + +const BASE_URL = '/'; + +export const api = createApi({ + reducerPath: 'grafanaAlertingAPI', + baseQuery: fetchBaseQuery({ + baseUrl: BASE_URL, + }), + endpoints: () => ({}), +}); diff --git a/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector.tsx b/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector.tsx new file mode 100644 index 00000000000..02a781719c9 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/contactPoints/components/ContactPointSelector.tsx @@ -0,0 +1,50 @@ +import { chain } from 'lodash'; + +import { Combobox, ComboboxOption } from '@grafana/ui'; + +import { useListContactPoints } from '../hooks/useContactPoints'; +import { ContactPoint } from '../types'; +import { getContactPointDescription } from '../utils'; + +const collator = new Intl.Collator('en', { sensitivity: 'accent' }); + +type ContactPointSelectorProps = { + onChange: (contactPoint: ContactPoint) => void; +}; + +/** + * Contact Point Combobox which lists all available contact points + * @TODO make ComboBox accept a ReactNode so we can use icons and such + */ +function ContactPointSelector({ onChange }: ContactPointSelectorProps) { + const { currentData: contactPoints, isLoading } = useListContactPoints(); + + // Create a mapping of options with their corresponding contact points + const contactPointOptions = chain(contactPoints?.items) + .toArray() + .map((contactPoint) => ({ + option: { + label: contactPoint.spec.title, + value: contactPoint.metadata.uid ?? contactPoint.spec.title, + description: getContactPointDescription(contactPoint), + }, + contactPoint, + })) + .value() + .sort((a, b) => collator.compare(a.option.label, b.option.label)); + + const options = contactPointOptions.map((item) => item.option); + + const handleChange = ({ value }: ComboboxOption) => { + const selectedItem = contactPointOptions.find(({ option }) => option.value === value); + if (!selectedItem) { + return; + } + + onChange(selectedItem.contactPoint); + }; + + return ; +} + +export { ContactPointSelector }; diff --git a/packages/grafana-alerting/src/grafana/contactPoints/hooks/useContactPoints.tsx b/packages/grafana-alerting/src/grafana/contactPoints/hooks/useContactPoints.tsx new file mode 100644 index 00000000000..354c34fe490 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/contactPoints/hooks/useContactPoints.tsx @@ -0,0 +1,29 @@ +import { fetchBaseQuery, TypedUseQueryHookResult } from '@reduxjs/toolkit/query/react'; + +import { config } from '@grafana/runtime'; + +import { alertingAPI, ListReceiverApiArg } from '../../api.gen'; +import { EnhancedListReceiverResponse } from '../types'; + +const { namespace } = config; + +// this is a workaround for the fact that the generated types are not narrow enough +type EnhancedHookResult = TypedUseQueryHookResult< + EnhancedListReceiverResponse, + ListReceiverApiArg, + ReturnType +>; + +/** + * useListContactPoints is a hook that fetches a list of contact points + * + * This function wraps the alertingAPI.useListReceiverQuery with proper typing + * to ensure that the returned ContactPoints are correctly typed in the data.items array. + * + * It automatically uses the configured namespace for the query. + */ +function useListContactPoints() { + return alertingAPI.useListReceiverQuery({ namespace }); +} + +export { useListContactPoints }; diff --git a/packages/grafana-alerting/src/grafana/contactPoints/types.ts b/packages/grafana-alerting/src/grafana/contactPoints/types.ts new file mode 100644 index 00000000000..ade85597d83 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/contactPoints/types.ts @@ -0,0 +1,76 @@ +/** + * This file contains enhanced and type-narrowed versions of the types generated by the RTKQ codegen package. + */ +import { MergeDeep, MergeExclusive, OverrideProperties } from 'type-fest'; + +import { + ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Receiver as ContactPointV0Alpha1, + ComGithubGrafanaGrafanaAppsAlertingNotificationsPkgApisReceiverV0Alpha1Integration as IntegrationV0Alpha1, + ListReceiverApiResponse, +} from '../api.gen'; + +type GenericIntegration = OverrideProperties< + IntegrationV0Alpha1, + { + settings: Record; + } +>; + +// Based on https://github.com/grafana/alerting/blob/main/receivers/email/config.go#L20-L25 +type EmailIntegration = OverrideProperties< + GenericIntegration, + { + type: 'email'; + settings: { + singleEmail?: boolean; + addresses: string; + message?: string; + subject?: string; + }; + secureFields: never; // email doesn't have any secure fields + } +>; + +// Based on https://github.com/grafana/alerting/blob/main/receivers/slack/config.go +type SlackIntegration = OverrideProperties< + GenericIntegration, + { + type: 'slack'; + settings: { + endpointUrl?: string; + url?: string; + recipient?: string; + text?: string; + title?: string; + username?: string; + icon_emoji?: string; + icon_url?: string; + mentionChannel?: string; + mentionUsers?: string; // comma separated string + mentionGroups?: string; // comma separated string + color?: string; + }; + // secureFields is a union type that can be either a token or a URL but you can't have both + secureFields: MergeExclusive<{ token: string }, { url: string }>; + } +>; + +export type Integration = EmailIntegration | SlackIntegration | GenericIntegration; + +// Enhanced version of ContactPoint with typed integrations +// ⚠️ MergeDeep does not check if the property you are overriding exists in the base type and there is no "DeepOverrideProperties" helper +export type ContactPoint = MergeDeep< + ContactPointV0Alpha1, + { + spec: { + integrations: Integration[]; + }; + } +>; + +export type EnhancedListReceiverResponse = OverrideProperties< + ListReceiverApiResponse, + { + items: ContactPoint[]; + } +>; diff --git a/packages/grafana-alerting/src/grafana/contactPoints/utils.ts b/packages/grafana-alerting/src/grafana/contactPoints/utils.ts new file mode 100644 index 00000000000..871dbb4fd87 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/contactPoints/utils.ts @@ -0,0 +1,31 @@ +import { countBy, isEmpty } from 'lodash'; + +import { ContactPoint } from './types'; + +/** + * Generates a human-readable description of a ContactPoint by summarizing its integrations. + * If the ContactPoint has no integrations, it returns an empty placeholder text. + * + * For integrations, it counts the occurrences of each type and formats them as a comma-separated list. + * Multiple integrations of the same type are indicated with a count in parentheses. + * + * @param contactPoint - The ContactPoint object to describe + * @returns A string description of the ContactPoint's integrations + */ +export function getContactPointDescription(contactPoint: ContactPoint): string { + if (isEmpty(contactPoint.spec.integrations)) { + return ''; + } + + // Count the occurrences of each integration type + const integrationCounts = countBy(contactPoint.spec.integrations, (integration) => integration.type); + + const description = Object.entries(integrationCounts) + .map(([type, count]) => { + // either "email" or "email (2)" but not "email (1)" + return count > 1 ? `${type} (${count})` : type; + }) + .join(', '); + + return description; +} diff --git a/packages/grafana-alerting/src/index.ts b/packages/grafana-alerting/src/index.ts new file mode 100644 index 00000000000..ee4141e38d0 --- /dev/null +++ b/packages/grafana-alerting/src/index.ts @@ -0,0 +1,9 @@ +/** + * Export things here that you want to be available under @grafana/alerting + * + * ⚠️ This implies everything in here is public API and should be considered stable and treated as such – make sure to + * think carefully about what you export here and the interfaces / data structures. + * + * Breaking changes should be avoided to maintain backwards compatibility for consumers of this package. + */ +export default {}; diff --git a/packages/grafana-alerting/src/internal.ts b/packages/grafana-alerting/src/internal.ts new file mode 100644 index 00000000000..800a6a5ee91 --- /dev/null +++ b/packages/grafana-alerting/src/internal.ts @@ -0,0 +1,6 @@ +/** + * Export things here that you want to be available under @grafana/alerting/internal + */ +export { alertingAPI } from './grafana/api.gen'; + +export default {}; diff --git a/packages/grafana-alerting/src/unstable.ts b/packages/grafana-alerting/src/unstable.ts new file mode 100644 index 00000000000..005c320eaa8 --- /dev/null +++ b/packages/grafana-alerting/src/unstable.ts @@ -0,0 +1,8 @@ +/** + * Export things here that you want to be available under @grafana/alerting/unstable + */ + +// Contact Points +export * from './grafana/contactPoints/types'; +export { useListContactPoints } from './grafana/contactPoints/hooks/useContactPoints'; +export { ContactPointSelector } from './grafana/contactPoints/components/ContactPointSelector'; diff --git a/packages/grafana-alerting/tsconfig.build.json b/packages/grafana-alerting/tsconfig.build.json new file mode 100644 index 00000000000..54309163ebc --- /dev/null +++ b/packages/grafana-alerting/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "exclude": ["dist", "node_modules", "test", "**/*.test.ts*"], + "extends": "./tsconfig.json" +} diff --git a/packages/grafana-alerting/tsconfig.json b/packages/grafana-alerting/tsconfig.json new file mode 100644 index 00000000000..2ec42c374d6 --- /dev/null +++ b/packages/grafana-alerting/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "declarationDir": "./compiled", + "emitDeclarationOnly": true, + "isolatedModules": true, + "rootDirs": ["."] + }, + "exclude": ["dist/**/*"], + "extends": "@grafana/tsconfig", + "include": ["typings/jest", "../../public/app/types/*.d.ts", "../grafana-ui/src/types/*.d.ts", "src/**/*.ts*"] +} diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 9713301dd87..8b5a31cf286 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/data", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Data Library", "keywords": [ "typescript" @@ -56,7 +56,7 @@ }, "dependencies": { "@braintree/sanitize-url": "7.0.1", - "@grafana/schema": "12.0.0-pre", + "@grafana/schema": "12.1.0-pre", "@types/d3-interpolate": "^3.0.0", "@types/string-hash": "1.1.3", "@types/systemjs": "6.15.1", @@ -67,7 +67,7 @@ "fast_array_intersect": "1.1.0", "history": "4.10.1", "lodash": "4.17.21", - "marked": "15.0.6", + "marked": "15.0.11", "marked-mangle": "1.1.10", "moment": "2.30.1", "moment-timezone": "0.5.47", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 2f86e020d67..513f38f4fdb 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -354,11 +354,6 @@ export interface FeatureToggles { */ cloudWatchBatchQueries?: boolean; /** - * Enables feature recovery threshold (aka hysteresis) for threshold server-side expression - * @default true - */ - recoveryThreshold?: boolean; - /** * Enables the loki data source to request structured metadata from the Loki server * @default true */ @@ -1019,6 +1014,10 @@ export interface FeatureToggles { */ pluginsAutoUpdate?: boolean; /** + * Register MT frontend + */ + multiTenantFrontend?: boolean; + /** * Enables the alerting list view v2 preview toggle */ alertingListViewV2PreviewToggle?: boolean; @@ -1027,4 +1026,9 @@ export interface FeatureToggles { * @default false */ alertRuleUseFiredAtForStartsAt?: boolean; + /** + * Enables the alerting bulk actions in the UI + * @default true + */ + alertingBulkActionsInUI?: boolean; } diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index d3dea16ff5c..64d4bdd7a88 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -139,6 +139,7 @@ export const availableIconsIndex = { 'gf-layout-simple': true, 'gf-logs': true, 'gf-ml': true, + 'gf-ml-alt': true, 'gf-movepane-left': true, 'gf-movepane-right': true, 'gf-portrait': true, diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 7525eb21469..3a81ff7a3aa 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e-selectors", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana End-to-End Test Selectors Library", "keywords": [ "cli", diff --git a/packages/grafana-eslint-rules/package.json b/packages/grafana-eslint-rules/package.json index 449c41eff0f..58f95284f26 100644 --- a/packages/grafana-eslint-rules/package.json +++ b/packages/grafana-eslint-rules/package.json @@ -1,7 +1,7 @@ { "name": "@grafana/eslint-plugin", "description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "main": "./index.cjs", "author": "Grafana Labs", "license": "Apache-2.0", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index fdd181fa8f6..f84432883d1 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/flamegraph", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana flamegraph visualization component", "keywords": [ "grafana", @@ -44,8 +44,8 @@ ], "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@leeoniya/ufuzzy": "1.0.18", "d3": "^7.8.5", "lodash": "4.17.21", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 62ab018c940..05b7ef3c8c0 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "name": "@grafana/o11y-ds-frontend", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Library to manage traces in Grafana.", "sideEffects": false, "repository": { @@ -18,12 +18,12 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "react-select": "5.10.0", "react-use": "17.6.0", "rxjs": "7.8.1", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index d88cf2ea261..b60ee20e30d 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -2,7 +2,7 @@ "name": "@grafana/plugin-configs", "description": "Shared dependencies and files for core plugins", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "tslib": "2.8.1" }, diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index a972bef78c0..e663f032f93 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "AGPL-3.0-only", "name": "@grafana/prometheus", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Prometheus Library", "keywords": [ "typescript" @@ -37,13 +37,13 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@floating-ui/react": "0.27.7", - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@floating-ui/react": "0.27.8", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.18", "@lezer/common": "1.2.3", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index fe80ca32676..baabb6b85d6 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/runtime", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Runtime Library", "keywords": [ "grafana", @@ -53,11 +53,11 @@ "postpack": "mv package.json.bak package.json && rimraf ./unstable" }, "dependencies": { - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/faro-web-sdk": "^1.13.2", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@types/systemjs": "6.15.1", "history": "4.10.1", "lodash": "4.17.21", diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index d6b8759e8b1..f922daf7569 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/schema", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Schema Library", "keywords": [ "typescript" diff --git a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts index b8c475c5cb0..6d79fbeceb8 100644 --- a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { limit: number; diff --git a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts index a613949f576..1bc5ff0f497 100644 --- a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip, common.OptionsWithTextFormatting { /** diff --git a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts index 8ea837b198b..4345b7fae05 100644 --- a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.OptionsWithLegend, common.SingleStatBaseOptions { displayMode: common.BarGaugeDisplayMode; diff --git a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts index 1ab1eeb4005..ee96c69c0a9 100644 --- a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum VizDisplayMode { Candles = 'candles', diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index 2e70c51ccbb..84c0c84408d 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum HorizontalConstraint { Center = 'center', diff --git a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts index 30ac8aa2af5..9f339bc8cc4 100644 --- a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface MetricStat { /** diff --git a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts index c345f24064b..328429a5829 100644 --- a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts index 14c4faac257..99b9e516dba 100644 --- a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { selectedSeries: number; diff --git a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts index 7ea1d48bd5d..6967fd64ccb 100644 --- a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export type UpdateConfig = { render: boolean, diff --git a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts index 57fa63d3a07..7cac1eab340 100644 --- a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested); diff --git a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts index 0678c2947de..abe40c63645 100644 --- a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.SingleStatBaseOptions { minVizHeight: number; diff --git a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts index 8258f6d0501..14a718cf6d9 100644 --- a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { basemap: ui.MapLayerOptions; diff --git a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts index b440943dacd..b87a6c3897f 100644 --- a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; /** * Controls the color mode of the heatmap diff --git a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts index 4871a0e625b..819bb518581 100644 --- a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip { /** diff --git a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts index a73bc96ce5d..d8b98890e5e 100644 --- a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { controlsStorageKey?: string; diff --git a/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts index 06c1a2c7735..8ac80fa1784 100644 --- a/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logsnew/panelcfg/x/LogsNewPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { dedupStrategy: common.LogsDedupStrategy; diff --git a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts index cc56fb954fa..59e8f4a13f7 100644 --- a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum QueryEditorMode { Builder = 'builder', diff --git a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts index cbd6c366085..c86b8b987f7 100644 --- a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts index db41a34dc9e..10f74d337b8 100644 --- a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface ArcOption { /** diff --git a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts index e9dbbd45d5c..5061e824e18 100644 --- a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; /** * Select the pie chart display style. diff --git a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts index cac66616404..3f290835b85 100644 --- a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.SingleStatBaseOptions { colorMode: common.BigValueColorMode; diff --git a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts index f3e32f729ee..40af7a986a7 100644 --- a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts index 554a2e83491..d0cc207400b 100644 --- a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts index fb3d375fd29..2529b11f635 100644 --- a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts index cf1dc84ee31..b97546048d7 100644 --- a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum TextMode { Code = 'code', diff --git a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts index 7cb398ca8de..b34b0b7e301 100644 --- a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export interface Options extends common.OptionsWithTimezones { legend: common.VizLegendOptions; diff --git a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts index a0cb5ed211c..9d3946018df 100644 --- a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; /** * Identical to timeseries... except it does not have timezone settings diff --git a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts index 9e86a2ac7de..35b7fdfd4e5 100644 --- a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "12.0.0-pre"; +export const pluginVersion = "12.1.0-pre"; export enum PointShape { Circle = 'circle', diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 7276014bd4b..8043f11ba0a 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "@grafana/sql", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git", @@ -15,11 +15,11 @@ }, "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@react-awesome-query-builder/ui": "6.6.14", "immutable": "5.0.3", "lodash": "4.17.21", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 09a16356aef..ad17eb046dc 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/ui", - "version": "12.0.0-pre", + "version": "12.1.0-pre", "description": "Grafana Components Library", "keywords": [ "grafana", @@ -65,11 +65,11 @@ "@emotion/css": "11.13.5", "@emotion/react": "11.14.0", "@emotion/serialize": "1.3.3", - "@floating-ui/react": "0.27.7", - "@grafana/data": "12.0.0-pre", - "@grafana/e2e-selectors": "12.0.0-pre", + "@floating-ui/react": "0.27.8", + "@grafana/data": "12.1.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", "@grafana/faro-web-sdk": "^1.13.2", - "@grafana/schema": "12.0.0-pre", + "@grafana/schema": "12.1.0-pre", "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.18", "@monaco-editor/react": "4.6.0", diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx index c1f151beded..e13dd5d8840 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; import { autoUpdate, flip, shift, useClick, useDismiss, useFloating, useInteractions } from '@floating-ui/react'; -import { ChangeEvent, useState } from 'react'; +import { ChangeEvent, forwardRef, useImperativeHandle, useState } from 'react'; import { GrafanaTheme2, dateTime } from '@grafana/data'; @@ -11,7 +11,7 @@ import { DatePicker } from '../DatePicker/DatePicker'; export const formatDate = (date: Date | string) => dateTime(date).format('L'); /** @public */ -export interface DatePickerWithInputProps extends Omit { +export interface DatePickerWithInputProps extends Omit { /** Value selected by the DatePicker */ value?: Date | string; /** The minimum date the value can be set to */ @@ -27,78 +27,78 @@ export interface DatePickerWithInputProps extends Omit { - const [open, setOpen] = useState(false); - const styles = useStyles2(getStyles); +export const DatePickerWithInput = forwardRef( + ({ value, minDate, maxDate, onChange, closeOnSelect, placeholder = 'Date', ...rest }, ref) => { + const [open, setOpen] = useState(false); + const styles = useStyles2(getStyles); - // the order of middleware is important! - // see https://floating-ui.com/docs/arrow#order - const middleware = [ - flip({ - // see https://floating-ui.com/docs/flip#combining-with-shift - crossAxis: false, - boundary: document.body, - }), - shift(), - ]; + // the order of middleware is important! + // see https://floating-ui.com/docs/arrow#order + const middleware = [ + flip({ + // see https://floating-ui.com/docs/flip#combining-with-shift + crossAxis: false, + boundary: document.body, + }), + shift(), + ]; - const { context, refs, floatingStyles } = useFloating({ - open, - placement: 'bottom-start', - onOpenChange: setOpen, - middleware, - whileElementsMounted: autoUpdate, - strategy: 'fixed', - }); + const { context, refs, floatingStyles } = useFloating({ + open, + placement: 'bottom-start', + onOpenChange: setOpen, + middleware, + whileElementsMounted: autoUpdate, + strategy: 'fixed', + }); - const click = useClick(context); - const dismiss = useDismiss(context); - const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]); + const click = useClick(context); + const dismiss = useDismiss(context); + const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, click]); - return ( -
- ) => { - // Allow resetting the date - if (ev.target.value === '') { - onChange(''); - } - }} - className={styles.input} - {...rest} - {...getReferenceProps()} - /> -
- { - onChange(ev); - if (closeOnSelect) { - setOpen(false); + useImperativeHandle(ref, () => refs.domReference.current, [ + refs.domReference, + ]); + + return ( +
+ ) => { + // Allow resetting the date + if (ev.target.value === '') { + onChange(''); } }} - onClose={() => setOpen(false)} + className={styles.input} + {...rest} + {...getReferenceProps()} /> +
+ { + onChange(ev); + if (closeOnSelect) { + setOpen(false); + } + }} + onClose={() => setOpen(false)} + /> +
-
- ); -}; + ); + } +); + +DatePickerWithInput.displayName = 'DatePickerWithInput'; const getStyles = (theme: GrafanaTheme2) => { return { diff --git a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx index ed0f4213544..c95d2e32e2c 100644 --- a/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx +++ b/packages/grafana-ui/src/components/Select/DropdownIndicator.tsx @@ -5,6 +5,6 @@ import { Icon } from '../Icon/Icon'; export function DropdownIndicator({ selectProps }: DropdownIndicatorProps) { const isOpen = selectProps.menuIsOpen; const icon = isOpen ? 'search' : 'angle-down'; - const size = isOpen ? 'sm' : 'md'; + const size = 'md'; return ; } diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index 899a9a460d8..c9fd5253441 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -1,5 +1,5 @@ import { isArray, negate } from 'lodash'; -import { ComponentProps, useCallback, useEffect, useRef, useState } from 'react'; +import { ComponentProps, useCallback, useEffect, useRef, useState, useImperativeHandle } from 'react'; import * as React from 'react'; import { default as ReactSelect, @@ -152,16 +152,19 @@ export function SelectBase({ isValidNewOption, formatOptionLabel, hideSelectedOptions, + selectRef, ...rest }: SelectBaseProps & Rest) { const theme = useTheme2(); const styles = getSelectStyles(theme); - const reactSelectRef = useRef<{ controlRef: HTMLElement }>(null); + const reactSelectRef = useRef(null); const [closeToBottom, setCloseToBottom] = useState(false); const selectStyles = useCustomSelectStyles(theme, width); const [hasInputValue, setHasInputValue] = useState(!!inputValue); + useImperativeHandle(selectRef, () => reactSelectRef.current!, []); + // Infer the menu position for asynchronously loaded options. menuPlacement="auto" doesn't work when the menu is // automatically opened when the component is created (it happens in SegmentSelect by setting menuIsOpen={true}). // We can remove this workaround when the bug in react-select is fixed: https://github.com/JedWatson/react-select/issues/4936 diff --git a/packages/grafana-ui/src/components/Select/types.ts b/packages/grafana-ui/src/components/Select/types.ts index b5183d97ea3..e31aee53d5a 100644 --- a/packages/grafana-ui/src/components/Select/types.ts +++ b/packages/grafana-ui/src/components/Select/types.ts @@ -116,6 +116,8 @@ export interface SelectCommonProps { loadingMessage?: string; /** Disables wrapping of multi value values when closed */ noMultiValueWrap?: boolean; + /** Use a custom ref because generic component as output of React.forwardRef is not directly possible */ + selectRef?: React.Ref; } export interface SelectAsyncProps { diff --git a/pkg/api/api.go b/pkg/api/api.go index 14218d00299..5503e367dfe 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -48,6 +48,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/frontend" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api" @@ -85,6 +86,17 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/login", hs.LoginView) r.Get("/invite/:code", hs.Index) + if hs.Features.IsEnabledGlobally(featuremgmt.FlagMultiTenantFrontend) { + index, err := frontend.NewIndexProvider(hs.Cfg, hs.License) + if err != nil { + panic(err) // ??? + } + r.Get("/mtfe", index.HandleRequest) + + // Temporarily expose the full bootdata via API + r.Get("/bootdata", reqNoAuth, hs.GetBootdata) + } + // authed views r.Get("/", reqSignedIn, hs.Index) r.Get("/profile/", reqSignedInNoAnonymous, hs.Index) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index b6a921d07c1..3ad8335151d 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -536,12 +536,6 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S return apierrors.ToDashboardErrorResponse(ctx, hs.pluginStore, saveErr) } - // Clear permission cache for the user who's created the dashboard, so that new permissions are fetched for their next call - // Required for cases when caller wants to immediately interact with the newly created object - if newDashboard { - hs.accesscontrolService.ClearUserPermissionCache(c.SignedInUser) - } - // connect library panels for this dashboard after the dashboard is stored and has an ID err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(ctx, c.SignedInUser, dashboard) if err != nil { diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 3909a70c561..687e09642f3 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -946,7 +946,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr if dashboardService == nil { dashboardService, err = service.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) @@ -956,7 +956,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr dashboardProvisioningService, err := service.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) diff --git a/pkg/api/dtos/index.go b/pkg/api/dtos/index.go index 6fa2d3766b8..2a55017e613 100644 --- a/pkg/api/dtos/index.go +++ b/pkg/api/dtos/index.go @@ -7,33 +7,33 @@ import ( ) type IndexViewData struct { - User *CurrentUser - Settings *FrontendSettingsDTO - AppUrl string - AppSubUrl string - GoogleAnalyticsId string - GoogleAnalytics4Id string - GoogleAnalytics4SendManualPageViews bool - GoogleTagManagerId string - NavTree *navtree.NavTreeRoot - BuildVersion string - BuildCommit string - ThemeType string - NewGrafanaVersionExists bool - NewGrafanaVersion string - AppName string - AppNameBodyClass string - FavIcon template.URL - AppleTouchIcon template.URL - AppTitle string - LoadingLogo template.URL - CSPContent string - CSPEnabled bool - IsDevelopmentEnv bool + User *CurrentUser `json:"user"` + Settings *FrontendSettingsDTO `json:"settings"` + AppUrl string `json:"-"` + AppSubUrl string `json:"-"` + GoogleAnalyticsId string `json:"-"` + GoogleAnalytics4Id string `json:"-"` + GoogleAnalytics4SendManualPageViews bool `json:"-"` + GoogleTagManagerId string `json:"-"` + NavTree *navtree.NavTreeRoot `json:"navtree"` + BuildVersion string `json:"-"` + BuildCommit string `json:"-"` + ThemeType string `json:"-"` + NewGrafanaVersionExists bool `json:"-"` + NewGrafanaVersion string `json:"-"` + AppName string `json:"-"` + AppNameBodyClass string `json:"-"` + FavIcon template.URL `json:"-"` + AppleTouchIcon template.URL `json:"-"` + AppTitle string `json:"-"` + LoadingLogo template.URL `json:"-"` + CSPContent string `json:"-"` + CSPEnabled bool `json:"-"` + IsDevelopmentEnv bool `json:"-"` // Nonce is a cryptographic identifier for use with Content Security Policy. - Nonce string - NewsFeedEnabled bool - Assets *EntryPointAssets // Includes CDN info + Nonce string `json:"-"` + NewsFeedEnabled bool `json:"-"` + Assets *EntryPointAssets `json:"assets"` // Includes CDN info } type EntryPointAssets struct { diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 02a8d95cb5c..2b03cba1397 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -206,10 +206,6 @@ func (hs *HTTPServer) CreateFolder(c *contextmodel.ReqContext) response.Response } } - // Clear permission cache for the user who's created the folder, so that new permissions are fetched for their next call - // Required for cases when caller wants to immediately interact with the newly created object - hs.accesscontrolService.ClearUserPermissionCache(c.SignedInUser) - folderDTO, err := hs.newToFolderDto(c, folder) if err != nil { return response.Err(err) @@ -226,7 +222,7 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int var permissions []accesscontrol.SetResourcePermissionCommand - if user.IsIdentityType(claims.TypeUser) { + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { userID, err := user.GetInternalID() if err != nil { return err @@ -246,7 +242,17 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int } _, err := hs.folderPermissionsService.SetPermissions(ctx, orgID, folder.UID, permissions...) - return err + if err != nil { + return err + } + + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + // Clear permission cache for the user who's created the folder, so that new permissions are fetched for their next call + // Required for cases when caller wants to immediately interact with the newly created object + hs.accesscontrolService.ClearUserPermissionCache(user) + } + + return nil } // swagger:route POST /folders/{folder_uid}/move folders moveFolder diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 222eaa6735a..f8d212a1d36 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -25,6 +25,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acdb "github.com/grafana/grafana/pkg/services/accesscontrol/database" "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/permreg" @@ -477,8 +478,8 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog require.NoError(b, err) dashboardSvc, err := dashboardservice.ProvideDashboardServiceImpl( sc.cfg, dashStore, folderStore, - features, folderPermissions, ac, - folderServiceWithFlagOn, fStore, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + features, folderPermissions, ac, actest.FakeService{}, + folderServiceWithFlagOn, nil, client.MockTestRestConfig{}, nil, quotaSrv, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sc.db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 935455a09c4..5333033d2bc 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -28,6 +28,18 @@ import ( "github.com/grafana/grafana/pkg/util" ) +// GetBootdataAPI returns the same data we currently have rendered into index.html +// NOTE: this should not be added to the public API docs, and is useful for a transition +// towards a fully static index.html -- this will likely be replaced with multiple calls +func (hs *HTTPServer) GetBootdata(c *contextmodel.ReqContext) { + data, err := hs.setIndexViewData(c) + if err != nil { + c.Handle(hs.Cfg, http.StatusInternalServerError, "Failed to get settings", err) + return + } + c.JSON(http.StatusOK, data) +} + // Returns a file that is easy to check for changes // Any changes to the file means we should refresh the frontend func (hs *HTTPServer) GetFrontendAssets(c *contextmodel.ReqContext) { diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index a6f75cd9ec0..00ccb0a665c 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -300,7 +300,15 @@ func (proxy *DataSourceProxy) validateRequest() error { } // route match - if !strings.HasPrefix(proxy.proxyPath, route.Path) { + r1, err := util.CleanRelativePath(proxy.proxyPath) + if err != nil { + return err + } + r2, err := util.CleanRelativePath(route.Path) + if err != nil { + return err + } + if !strings.HasPrefix(r1, r2) { continue } diff --git a/pkg/api/pluginproxy/ds_proxy_test.go b/pkg/api/pluginproxy/ds_proxy_test.go index 84920f50f52..6b7a113cd12 100644 --- a/pkg/api/pluginproxy/ds_proxy_test.go +++ b/pkg/api/pluginproxy/ds_proxy_test.go @@ -274,6 +274,14 @@ func TestDataSourceProxy_routeRule(t *testing.T) { err = proxy.validateRequest() require.NoError(t, err) }) + + t.Run("path with slashes and user is editor", func(t *testing.T) { + ctx, _ := setUp() + proxy, err := setupDSProxyTest(t, ctx, ds, routes, "//api//admin") + require.NoError(t, err) + err = proxy.validateRequest() + require.Error(t, err) + }) }) t.Run("plugin route with RBAC protection user is allowed", func(t *testing.T) { diff --git a/pkg/api/render.go b/pkg/api/render.go index 582c6ec1e4b..5db0b88fa35 100644 --- a/pkg/api/render.go +++ b/pkg/api/render.go @@ -98,6 +98,11 @@ func (hs *HTTPServer) RenderHandler(c *contextmodel.ReqContext) { Theme: themeModel, }, nil) if err != nil { + if errors.Is(err, rendering.ErrTooManyRequests) { + c.JsonApiErr(http.StatusTooManyRequests, "Too many rendering requests", err) + return + } + if errors.Is(err, rendering.ErrTimeout) { c.Handle(hs.Cfg, http.StatusInternalServerError, err.Error(), err) return diff --git a/pkg/apimachinery/identity/context.go b/pkg/apimachinery/identity/context.go index 08a3ed192b8..58ab1085d36 100644 --- a/pkg/apimachinery/identity/context.go +++ b/pkg/apimachinery/identity/context.go @@ -124,6 +124,7 @@ var serviceIdentityTokenPermissions = getTokenPermissions( "folder.grafana.app", "dashboard.grafana.app", "secret.grafana.app", + "query.grafana.app", ) var ServiceIdentityClaims = &authn.Claims[authn.AccessTokenClaims]{ diff --git a/pkg/apiserver/rest/dualwriter.go b/pkg/apiserver/rest/dualwriter.go index 6cd37cb8740..3b4196935de 100644 --- a/pkg/apiserver/rest/dualwriter.go +++ b/pkg/apiserver/rest/dualwriter.go @@ -164,6 +164,10 @@ func SetDualWritingMode( return Mode0, errDualWriterSetCurrentMode } case cfg.Mode >= Mode3 && currentMode < Mode3: + if cfg.SkipDataSync { + return currentMode, nil + } + // Transitioning to Mode3 or higher requires data synchronization. cfgModeTmp := cfg.Mode // Before running the sync, set the syncer config to the current mode, as we have to run the syncer diff --git a/pkg/apiserver/rest/dualwriter_syncer.go b/pkg/apiserver/rest/dualwriter_syncer.go index b15cf5376ac..fb064370fc6 100644 --- a/pkg/apiserver/rest/dualwriter_syncer.go +++ b/pkg/apiserver/rest/dualwriter_syncer.go @@ -36,6 +36,7 @@ type SyncerConfig struct { LegacyStorage Storage Storage Storage ServerLockService ServerLockService + SkipDataSync bool DataSyncerInterval time.Duration DataSyncerRecordsLimit int diff --git a/pkg/apiserver/rest/dualwriter_test.go b/pkg/apiserver/rest/dualwriter_test.go index 3faf131424e..fa6fa7e5f4a 100644 --- a/pkg/apiserver/rest/dualwriter_test.go +++ b/pkg/apiserver/rest/dualwriter_test.go @@ -20,6 +20,7 @@ func TestSetDualWritingMode(t *testing.T) { kvStore *fakeNamespacedKV desiredMode DualWriterMode expectedMode DualWriterMode + skipDataSync bool serverLockError error } tests := @@ -61,6 +62,13 @@ func TestSetDualWritingMode(t *testing.T) { expectedMode: Mode2, serverLockError: fmt.Errorf("lock already exists"), }, + { + name: "should keep mode2 when trying to go from mode2 to mode3 and migration is disabled", + kvStore: &fakeNamespacedKV{data: map[string]string{"playlist.grafana.app/playlists": "2"}, namespace: "storage.dualwriting"}, + desiredMode: Mode3, + expectedMode: Mode2, + skipDataSync: true, + }, } for _, tt := range tests { @@ -86,6 +94,7 @@ func TestSetDualWritingMode(t *testing.T) { Storage: us, Kind: "playlist.grafana.app/playlists", Mode: tt.desiredMode, + SkipDataSync: tt.skipDataSync, ServerLockService: serverLockSvc, RequestInfo: &request.RequestInfo{}, Reg: p, diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index b39e17a1384..660a0a6b8fc 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -161,7 +161,7 @@ func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles, sqlExpression case TypeClassicConditions: node.Command, err = classic.UnmarshalConditionsCmd(rn.Query, rn.RefID) case TypeThreshold: - node.Command, err = UnmarshalThresholdCommand(rn, toggles) + node.Command, err = UnmarshalThresholdCommand(rn) case TypeSQL: node.Command, err = UnmarshalSQLCommand(rn, sqlExpressionCellLimit) default: diff --git a/pkg/expr/reader.go b/pkg/expr/reader.go index 6d1349ae84c..c108b913ccd 100644 --- a/pkg/expr/reader.go +++ b/pkg/expr/reader.go @@ -158,7 +158,7 @@ func (h *ExpressionQueryReader) ReadQuery( eq.Command = threshold eq.Properties = q - if firstCondition.UnloadEvaluator != nil && h.features.IsEnabledGlobally(featuremgmt.FlagRecoveryThreshold) { + if firstCondition.UnloadEvaluator != nil { unloading, err := NewThresholdCommand(common.RefID, referenceVar, firstCondition.UnloadEvaluator.Type, firstCondition.UnloadEvaluator.Params) unloading.Invert = true if err != nil { diff --git a/pkg/expr/threshold.go b/pkg/expr/threshold.go index f9e9656e03e..b23e26f57dd 100644 --- a/pkg/expr/threshold.go +++ b/pkg/expr/threshold.go @@ -13,7 +13,6 @@ import ( "github.com/grafana/grafana/pkg/expr/mathexp" "github.com/grafana/grafana/pkg/expr/metrics" "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/util" ) @@ -131,7 +130,7 @@ type ConditionEvalJSON struct { } // UnmarshalResampleCommand creates a ResampleCMD from Grafana's frontend query. -func UnmarshalThresholdCommand(rn *rawNode, features featuremgmt.FeatureToggles) (Command, error) { +func UnmarshalThresholdCommand(rn *rawNode) (Command, error) { cmdConfig := ThresholdCommandConfig{} if err := json.Unmarshal(rn.QueryRaw, &cmdConfig); err != nil { return nil, fmt.Errorf("failed to parse the threshold command: %w", err) @@ -151,7 +150,7 @@ func UnmarshalThresholdCommand(rn *rawNode, features featuremgmt.FeatureToggles) if err != nil { return nil, fmt.Errorf("invalid condition: %w", err) } - if firstCondition.UnloadEvaluator != nil && features.IsEnabledGlobally(featuremgmt.FlagRecoveryThreshold) { + if firstCondition.UnloadEvaluator != nil { unloading, err := NewThresholdCommand(rn.RefID, referenceVar, firstCondition.UnloadEvaluator.Type, firstCondition.UnloadEvaluator.Params) if err != nil { return nil, fmt.Errorf("invalid unloadCondition: %w", err) diff --git a/pkg/expr/threshold_test.go b/pkg/expr/threshold_test.go index 0bdc695e7ed..58f162c9ce0 100644 --- a/pkg/expr/threshold_test.go +++ b/pkg/expr/threshold_test.go @@ -15,7 +15,6 @@ import ( "github.com/grafana/grafana/pkg/expr/mathexp" "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/util" ) @@ -277,7 +276,7 @@ func TestUnmarshalThresholdCommand(t *testing.T) { QueryRaw: []byte(tc.query), QueryType: "", DataSource: nil, - }, featuremgmt.WithFeatures(featuremgmt.FlagRecoveryThreshold)) + }) if tc.shouldError { require.Nil(t, cmd) @@ -466,7 +465,7 @@ func TestSetLoadedDimensionsToHysteresisCommand(t *testing.T) { cmd, err := UnmarshalThresholdCommand(&rawNode{ RefID: "B", QueryRaw: raw, - }, featuremgmt.WithFeatures(featuremgmt.FlagRecoveryThreshold)) + }) require.NoError(t, err) require.Equal(t, fingerprints, cmd.(*HysteresisCommand).LoadedDimensions) diff --git a/pkg/extensions/enterprise_imports.go b/pkg/extensions/enterprise_imports.go index 361121f80d5..7e96faa86ac 100644 --- a/pkg/extensions/enterprise_imports.go +++ b/pkg/extensions/enterprise_imports.go @@ -32,11 +32,6 @@ import ( _ "golang.org/x/time/rate" _ "xorm.io/builder" - _ "github.com/grafana/authlib/authn" - _ "github.com/grafana/authlib/authz" - _ "github.com/grafana/authlib/cache" - _ "github.com/grafana/authlib/grpcutils" - _ "github.com/grafana/authlib/types" _ "github.com/grafana/dskit/backoff" _ "github.com/grafana/dskit/flagext" _ "github.com/grafana/e2e" diff --git a/pkg/infra/httpclient/httpclientprovider/datasource_metrics_middleware.go b/pkg/infra/httpclient/httpclientprovider/datasource_metrics_middleware.go index 6334b373899..6a55c9f38ca 100644 --- a/pkg/infra/httpclient/httpclientprovider/datasource_metrics_middleware.go +++ b/pkg/infra/httpclient/httpclientprovider/datasource_metrics_middleware.go @@ -43,6 +43,14 @@ var ( }, []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled"}, ) + datasourceResponseGauge = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "plugins", + Name: "datasource_response_size", + Help: "gauge of external data source response sizes returned to Grafana in bytes", + }, []string{"datasource", "datasource_type", "secure_socks_ds_proxy_enabled"}, + ) + datasourceRequestsInFlight = promauto.NewGaugeVec( prometheus.GaugeOpts{ Namespace: "grafana", @@ -102,6 +110,7 @@ func executeMiddleware(next http.RoundTripper, labels prometheus.Labels) http.Ro requestHistogram := datasourceRequestHistogram.MustCurryWith(labels) requestInFlight := datasourceRequestsInFlight.With(labels) responseSizeHistogram := datasourceResponseHistogram.With(labels) + responseSizeGauge := datasourceResponseGauge.With(labels) res, err := promhttp.InstrumentRoundTripperDuration(requestHistogram, promhttp.InstrumentRoundTripperCounter(requestCounter, @@ -114,6 +123,7 @@ func executeMiddleware(next http.RoundTripper, labels prometheus.Labels) http.Ro if res != nil && res.StatusCode != http.StatusSwitchingProtocols { res.Body = sdkhttpclient.CountBytesReader(res.Body, func(bytesRead int64) { responseSizeHistogram.Observe(float64(bytesRead)) + responseSizeGauge.Set(float64(bytesRead)) }) } diff --git a/pkg/middleware/csp.go b/pkg/middleware/csp.go index ebfd54c4450..c8c41a9468f 100644 --- a/pkg/middleware/csp.go +++ b/pkg/middleware/csp.go @@ -31,7 +31,7 @@ func ContentSecurityPolicy(cfg *setting.Cfg, logger log.Logger) func(http.Handle func nonceMiddleware(next http.Handler, logger log.Logger) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { ctx := contexthandler.FromContext(req.Context()) - nonce, err := generateNonce() + nonce, err := GenerateNonce() if err != nil { logger.Error("Failed to generate CSP nonce", "err", err) ctx.JsonApiErr(500, "Failed to generate CSP nonce", err) @@ -68,7 +68,7 @@ func ReplacePolicyVariables(policyTemplate, appURL, nonce string) string { return policy } -func generateNonce() (string, error) { +func GenerateNonce() (string, error) { var buf [16]byte if _, err := io.ReadFull(rand.Reader, buf[:]); err != nil { return "", err diff --git a/pkg/modules/dependencies.go b/pkg/modules/dependencies.go index f18b5f2c15f..d4ed8de2177 100644 --- a/pkg/modules/dependencies.go +++ b/pkg/modules/dependencies.go @@ -5,16 +5,21 @@ const ( All string = "all" Core string = "core" + MemberlistKV string = "memberlistkv" GrafanaAPIServer string = "grafana-apiserver" + StorageRing string = "storage-ring" StorageServer string = "storage-server" ZanzanaServer string = "zanzana-server" InstrumentationServer string = "instrumentation-server" + FrontendServer string = "frontend-server" ) var dependencyMap = map[string][]string{ + StorageRing: {InstrumentationServer, MemberlistKV}, GrafanaAPIServer: {InstrumentationServer}, - StorageServer: {InstrumentationServer}, + StorageServer: {InstrumentationServer, StorageRing}, ZanzanaServer: {InstrumentationServer}, Core: {}, All: {Core}, + FrontendServer: {}, } diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index b7a660f53e6..3c032613b05 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -303,19 +303,15 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows, history bool) (*dashboardRo } if origin_name.String != "" { - // if the reader cannot be found, it may be an orphaned provisioned dashboard - resolvedPath := a.provisioning.GetDashboardProvisionerResolvedPath(origin_name.String) - if resolvedPath != "" { - meta.SetSourceProperties(utils.SourceProperties{ - Path: origin_path.String, - Checksum: origin_hash.String, - TimestampMillis: origin_ts.Int64, - }) - meta.SetManagerProperties(utils.ManagerProperties{ - Kind: utils.ManagerKindClassicFP, // nolint:staticcheck - Identity: origin_name.String, - }) - } + meta.SetSourceProperties(utils.SourceProperties{ + Path: origin_path.String, + Checksum: origin_hash.String, + TimestampMillis: origin_ts.Int64, + }) + meta.SetManagerProperties(utils.ManagerProperties{ + Kind: utils.ManagerKindClassicFP, // nolint:staticcheck + Identity: origin_name.String, + }) } else if plugin_id.String != "" { meta.SetManagerProperties(utils.ManagerProperties{ Kind: utils.ManagerKindPlugin, diff --git a/pkg/registry/apis/folders/folder_storage.go b/pkg/registry/apis/folders/folder_storage.go index 0a66772b651..588681e0311 100644 --- a/pkg/registry/apis/folders/folder_storage.go +++ b/pkg/registry/apis/folders/folder_storage.go @@ -38,6 +38,7 @@ type folderStorage struct { cfg *setting.Cfg features featuremgmt.FeatureToggles folderPermissionsSvc accesscontrol.FolderPermissionsService + acService accesscontrol.Service store grafanarest.Storage } @@ -139,7 +140,7 @@ func (s *folderStorage) setDefaultFolderPermissions(ctx context.Context, orgID i var permissions []accesscontrol.SetResourcePermissionCommand - if user.IsIdentityType(claims.TypeUser) { + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { userID, err := user.GetInternalID() if err != nil { return err @@ -157,5 +158,13 @@ func (s *folderStorage) setDefaultFolderPermissions(ctx context.Context, orgID i }...) } _, err := s.folderPermissionsSvc.SetPermissions(ctx, orgID, uid, permissions...) - return err + if err != nil { + return err + } + + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + s.acService.ClearUserPermissionCache(user) + } + + return nil } diff --git a/pkg/registry/apis/folders/folder_storage_test.go b/pkg/registry/apis/folders/folder_storage_test.go index 73a1243d579..01527bb7eb0 100644 --- a/pkg/registry/apis/folders/folder_storage_test.go +++ b/pkg/registry/apis/folders/folder_storage_test.go @@ -16,6 +16,7 @@ import ( folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -48,6 +49,7 @@ func TestSetDefaultPermissionsWhenCreatingFolder(t *testing.T) { fs := folderStorage{ folderPermissionsSvc: folderPermService, + acService: actest.FakeService{}, store: &fakeStorage{}, cfg: cfg, } diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index b42d8746962..6c28909f63b 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -49,6 +49,7 @@ type FolderAPIBuilder struct { namespacer request.NamespaceMapper folderSvc folder.Service folderPermissionsSvc accesscontrol.FolderPermissionsService + acService accesscontrol.Service storage grafanarest.Storage authorizer authorizer.Authorizer @@ -64,6 +65,7 @@ func RegisterAPIService(cfg *setting.Cfg, folderSvc folder.Service, folderPermissionsSvc accesscontrol.FolderPermissionsService, accessControl accesscontrol.AccessControl, + acService accesscontrol.Service, registerer prometheus.Registerer, unified resource.ResourceClient, ) *FolderAPIBuilder { @@ -73,6 +75,7 @@ func RegisterAPIService(cfg *setting.Cfg, namespacer: request.GetNamespaceMapper(cfg), folderSvc: folderSvc, folderPermissionsSvc: folderPermissionsSvc, + acService: acService, cfg: cfg, authorizer: newLegacyAuthorizer(accessControl), searcher: unified, @@ -155,6 +158,7 @@ func (b *FolderAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.API folderStore := &folderStorage{ tableConverter: resourceInfo.TableConverter(), folderPermissionsSvc: b.folderPermissionsSvc, + acService: b.acService, features: b.features, cfg: b.cfg, } diff --git a/pkg/registry/apis/provisioning/jobs/export/folders_test.go b/pkg/registry/apis/provisioning/jobs/export/folders_test.go index ac2c1aa13e4..bec0214d7ef 100644 --- a/pkg/registry/apis/provisioning/jobs/export/folders_test.go +++ b/pkg/registry/apis/provisioning/jobs/export/folders_test.go @@ -510,3 +510,10 @@ func (m *mockDynamicInterface) List(ctx context.Context, opts metav1.ListOptions func (m *mockDynamicInterface) Delete(ctx context.Context, name string, opts metav1.DeleteOptions, subresources ...string) error { return m.deleteError } + +func (m *mockDynamicInterface) Get(ctx context.Context, name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { + if len(m.items) == 0 { + return nil, fmt.Errorf("no items found") + } + return &m.items[0], nil +} diff --git a/pkg/registry/apis/provisioning/jobs/export/resources.go b/pkg/registry/apis/provisioning/jobs/export/resources.go index a401615c427..c469f4def8c 100644 --- a/pkg/registry/apis/provisioning/jobs/export/resources.go +++ b/pkg/registry/apis/provisioning/jobs/export/resources.go @@ -4,7 +4,9 @@ import ( "context" "errors" "fmt" + "strings" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/client-go/dynamic" @@ -14,6 +16,11 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) +// FIXME: This is used to make sure we save dashboards in the apiVersion they were original saved in +// When requesting v0 or v2 dashboards over the v1 api -- the backend tries (and fails!) to convert values +// The response status indicates the original stored version, so we can then request it in an un-converted form +type conversionShim = func(ctx context.Context, item *unstructured.Unstructured) (*unstructured.Unstructured, error) + func ExportResources(ctx context.Context, options provisioning.ExportJobOptions, clients resources.ResourceClients, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error { progress.SetMessage(ctx, "start resource export") for _, kind := range resources.SupportedProvisioningResources { @@ -28,7 +35,39 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions, return fmt.Errorf("get client for %s: %w", kind.Resource, err) } - if err := exportResource(ctx, options, client, repositoryResources, progress); err != nil { + // When requesting v2 (or v0) dashboards over the v1 api, we want to keep the original apiVersion if conversion fails + var shim conversionShim + if kind.GroupResource() == resources.DashboardResource.GroupResource() { + var v2client dynamic.ResourceInterface + shim = func(ctx context.Context, item *unstructured.Unstructured) (*unstructured.Unstructured, error) { + failed, _, _ := unstructured.NestedBool(item.Object, "status", "conversion", "failed") + if failed { + storedVersion, _, _ := unstructured.NestedString(item.Object, "status", "conversion", "storedVersion") + + // For v2 we need to request the original version + if strings.HasPrefix(storedVersion, "v2") { + if v2client == nil { + v2client, _, err = clients.ForResource(resources.DashboardResourceV2) + if err != nil { + return nil, err + } + } + return v2client.Get(ctx, item.GetName(), metav1.GetOptions{}) + } + + // For v0 we can simply fallback -- the full model is saved, but + if strings.HasPrefix(storedVersion, "v0") { + item.SetAPIVersion(fmt.Sprintf("%s/%s", kind.Group, storedVersion)) + return item, nil + } + + return nil, fmt.Errorf("unsupported dashboard version: %s", storedVersion) + } + return item, nil + } + } + + if err := exportResource(ctx, options, client, shim, repositoryResources, progress); err != nil { return fmt.Errorf("export %s: %w", kind.Resource, err) } } @@ -36,20 +75,32 @@ func ExportResources(ctx context.Context, options provisioning.ExportJobOptions, return nil } -func exportResource(ctx context.Context, options provisioning.ExportJobOptions, client dynamic.ResourceInterface, repositoryResources resources.RepositoryResources, progress jobs.JobProgressRecorder) error { - return resources.ForEach(ctx, client, func(item *unstructured.Unstructured) error { - fileName, err := repositoryResources.WriteResourceFileFromObject(ctx, item, resources.WriteOptions{ - Path: options.Path, - Ref: options.Branch, - }) - +func exportResource(ctx context.Context, + options provisioning.ExportJobOptions, + client dynamic.ResourceInterface, + shim conversionShim, + repositoryResources resources.RepositoryResources, + progress jobs.JobProgressRecorder, +) error { + // FIXME: using k8s list will force evrything into one version -- we really want the original saved version + // this will work well enough for now, but needs to be revisted as we have a bigger mix of active versions + return resources.ForEach(ctx, client, func(item *unstructured.Unstructured) (err error) { gvk := item.GroupVersionKind() result := jobs.JobResourceResult{ Name: item.GetName(), Resource: gvk.Kind, Group: gvk.Group, Action: repository.FileActionCreated, - Path: fileName, + } + + if shim != nil { + item, err = shim(ctx, item) + } + if err == nil { + result.Path, err = repositoryResources.WriteResourceFileFromObject(ctx, item, resources.WriteOptions{ + Path: options.Path, + Ref: options.Branch, + }) } if errors.Is(err, resources.ErrAlreadyInRepository) { diff --git a/pkg/registry/apis/provisioning/jobs/export/resources_test.go b/pkg/registry/apis/provisioning/jobs/export/resources_test.go index d1a75443316..64f7d256866 100644 --- a/pkg/registry/apis/provisioning/jobs/export/resources_test.go +++ b/pkg/registry/apis/provisioning/jobs/export/resources_test.go @@ -7,12 +7,8 @@ import ( mock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - dynamicfake "k8s.io/client-go/dynamic/fake" - k8testing "k8s.io/client-go/testing" provisioningV0 "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/provisioning/jobs" @@ -20,44 +16,35 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" ) -func TestExportResources(t *testing.T) { +func TestExportResources_Dashboards(t *testing.T) { tests := []struct { name string - reactorFunc func(action k8testing.Action) (bool, runtime.Object, error) + mockItems []unstructured.Unstructured expectedError string setupProgress func(progress *jobs.MockJobProgressRecorder) - setupResources func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) + setupResources func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) }{ { name: "successful dashboard export", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - // Return dashboard list - return true, &metav1.PartialObjectMetadataList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "DashboardList", - }, - Items: []metav1.PartialObjectMetadata{ - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-1", - }, - }, - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-2", - }, + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-1", }, }, - }, nil + }, + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-2", + }, + }, + }, }, expectedError: "", setupProgress: func(progress *jobs.MockJobProgressRecorder) { @@ -72,8 +59,8 @@ func TestExportResources(t *testing.T) { progress.On("TooManyErrors").Return(nil) progress.On("TooManyErrors").Return(nil) }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) options := resources.WriteOptions{ Path: "grafana", Ref: "feature/branch", @@ -89,62 +76,38 @@ func TestExportResources(t *testing.T) { }, }, { - name: "client error", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, nil, fmt.Errorf("shouldn't happen") - }, + name: "client error", + mockItems: nil, expectedError: "get client for dashboards: didn't work", setupProgress: func(progress *jobs.MockJobProgressRecorder) { progress.On("SetMessage", mock.Anything, "start resource export").Return() progress.On("SetMessage", mock.Anything, "export dashboards").Return() }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, fmt.Errorf("didn't work")) - }, - }, - { - name: "dashboard list error", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, nil, fmt.Errorf("failed to list dashboards") - }, - expectedError: "export dashboards: error executing list: failed to list dashboards", - setupProgress: func(progress *jobs.MockJobProgressRecorder) { - progress.On("SetMessage", mock.Anything, "start resource export").Return() - progress.On("SetMessage", mock.Anything, "export dashboards").Return() - }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, fmt.Errorf("didn't work")) }, }, { name: "dashboard export with errors", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, &metav1.PartialObjectMetadataList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "DashboardList", - }, - Items: []metav1.PartialObjectMetadata{ - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-1", - }, - }, - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-2", - }, + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-1", }, }, - }, nil + }, + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-2", + }, + }, + }, }, expectedError: "", setupProgress: func(progress *jobs.MockJobProgressRecorder) { @@ -159,8 +122,8 @@ func TestExportResources(t *testing.T) { progress.On("TooManyErrors").Return(nil) progress.On("TooManyErrors").Return(nil) }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) options := resources.WriteOptions{ Path: "grafana", Ref: "feature/branch", @@ -177,24 +140,16 @@ func TestExportResources(t *testing.T) { }, { name: "dashboard export too many errors", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, &metav1.PartialObjectMetadataList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "DashboardList", - }, - Items: []metav1.PartialObjectMetadata{ - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "dashboard-1", - }, + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-1", }, }, - }, nil + }, }, expectedError: "export dashboards: too many errors encountered", setupProgress: func(progress *jobs.MockJobProgressRecorder) { @@ -205,8 +160,8 @@ func TestExportResources(t *testing.T) { })).Return() progress.On("TooManyErrors").Return(fmt.Errorf("too many errors encountered")) }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) options := resources.WriteOptions{ Path: "grafana", Ref: "feature/branch", @@ -219,24 +174,16 @@ func TestExportResources(t *testing.T) { }, { name: "ignores existing dashboards", - reactorFunc: func(action k8testing.Action) (bool, runtime.Object, error) { - return true, &metav1.PartialObjectMetadataList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "DashboardList", - }, - Items: []metav1.PartialObjectMetadata{ - { - TypeMeta: metav1.TypeMeta{ - APIVersion: resources.DashboardResource.GroupVersion().String(), - Kind: "Dashboard", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "existing-dashboard", - }, + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "existing-dashboard", }, }, - }, nil + }, }, expectedError: "", setupProgress: func(progress *jobs.MockJobProgressRecorder) { @@ -247,50 +194,238 @@ func TestExportResources(t *testing.T) { })).Return() progress.On("TooManyErrors").Return(nil) }, - setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, dynamicClient *dynamicfake.FakeDynamicClient, gvk schema.GroupVersionKind) { - resourceClients.On("ForResource", resources.DashboardResource).Return(dynamicClient.Resource(resources.DashboardResource), gvk, nil) + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) options := resources.WriteOptions{ Path: "grafana", Ref: "feature/branch", } - // Return true to indicate the file already exists, and provide the updated path repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { return obj.GetName() == "existing-dashboard" }), options).Return("", resources.ErrAlreadyInRepository) }, }, + { + name: "uses saved dashboard version", + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "existing-dashboard", + }, + "spec": map[string]interface{}{ + "hello": "world", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v0xyz", + }, + }, + }, + }, + }, + expectedError: "", + setupProgress: func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "existing-dashboard" && result.Action == repository.FileActionIgnored + })).Return() + progress.On("TooManyErrors").Return(nil) + }, + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + + repoResources.On("WriteResourceFileFromObject", mock.Anything, mock.MatchedBy(func(obj *unstructured.Unstructured) bool { + // Verify that the object has the expected status.conversion.storedVersion field + status, exists, err := unstructured.NestedMap(obj.Object, "status") + if !exists || err != nil { + return false + } + + conversion, exists, err := unstructured.NestedMap(status, "conversion") + if !exists || err != nil { + return false + } + + storedVersion, exists, err := unstructured.NestedString(conversion, "storedVersion") + if !exists || err != nil { + return false + } + + if storedVersion != "v0xyz" { + return false + } + + return obj.GetName() == "existing-dashboard" + }), options).Return("", fmt.Errorf("XXX")) + }, + }, + { + name: "dashboard with failed conversion but no stored version", + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "dashboard-no-stored-version", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + // No storedVersion field + }, + }, + }, + }, + }, + expectedError: "", + setupProgress: func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "dashboard-no-stored-version" && + result.Action == repository.FileActionIgnored && + result.Error != nil + })).Return() + progress.On("TooManyErrors").Return(nil) + }, + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + // The value is not saved + }, + }, + { + name: "handles v2 dashboard version", + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v2", + }, + }, + }, + }, + }, + expectedError: "", + setupProgress: func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + return result.Name == "v2-dashboard" && result.Action == repository.FileActionCreated + })).Return() + progress.On("TooManyErrors").Return(nil) + }, + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + // Setup v1 client + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + + // Setup v2 client + + // Mock v2 client Get call + v2Dashboard := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "dashboard.grafana.app/v2alpha1", + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard", + }, + "spec": map[string]interface{}{ + "version": 2, + "title": "V2 Dashboard", + }, + }, + } + v2Client := &mockDynamicInterface{items: []unstructured.Unstructured{*v2Dashboard}} + resourceClients.On("ForResource", resources.DashboardResourceV2).Return(v2Client, gvk, nil) + + options := resources.WriteOptions{ + Path: "grafana", + Ref: "feature/branch", + } + repoResources.On("WriteResourceFileFromObject", mock.Anything, v2Dashboard, options).Return("v2-dashboard.json", nil) + }, + }, + { + name: "handles v2 client creation error", + mockItems: []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "apiVersion": resources.DashboardResource.GroupVersion().String(), + "kind": "Dashboard", + "metadata": map[string]interface{}{ + "name": "v2-dashboard-error", + }, + "status": map[string]interface{}{ + "conversion": map[string]interface{}{ + "failed": true, + "storedVersion": "v2", + }, + }, + }, + }, + }, + setupProgress: func(progress *jobs.MockJobProgressRecorder) { + progress.On("SetMessage", mock.Anything, "start resource export").Return() + progress.On("SetMessage", mock.Anything, "export dashboards").Return() + progress.On("Record", mock.Anything, mock.MatchedBy(func(result jobs.JobResourceResult) bool { + if result.Name != "v2-dashboard-error" { + return false + } + if result.Action != repository.FileActionIgnored { + return false + } + if result.Error == nil { + return false + } + + if result.Error.Error() != "v2 client error" { + return false + } + + return true + })).Return() + progress.On("TooManyErrors").Return(nil) + }, + setupResources: func(repoResources *resources.MockRepositoryResources, resourceClients *resources.MockResourceClients, mockClient *mockDynamicInterface, gvk schema.GroupVersionKind) { + resourceClients.On("ForResource", resources.DashboardResource).Return(mockClient, gvk, nil) + resourceClients.On("ForResource", resources.DashboardResourceV2).Return(nil, gvk, fmt.Errorf("v2 client error")) + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - scheme := runtime.NewScheme() - require.NoError(t, metav1.AddMetaToScheme(scheme)) - listGVK := schema.GroupVersionKind{ - Group: resources.DashboardResource.Group, - Version: resources.DashboardResource.Version, - Kind: "DashboardList", + mockClient := &mockDynamicInterface{ + items: tt.mockItems, } - scheme.AddKnownTypeWithName(listGVK, &metav1.PartialObjectMetadataList{}) - scheme.AddKnownTypeWithName(schema.GroupVersionKind{ - Group: resources.DashboardResource.Group, - Version: resources.DashboardResource.Version, - Kind: resources.DashboardResource.Resource, - }, &metav1.PartialObjectMetadata{}) - - fakeDynamicClient := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, map[schema.GroupVersionResource]string{ - resources.DashboardResource: listGVK.Kind, - }) - resourceClients := resources.NewMockResourceClients(t) - fakeDynamicClient.PrependReactor("list", "dashboards", tt.reactorFunc) - mockProgress := jobs.NewMockJobProgressRecorder(t) tt.setupProgress(mockProgress) repoResources := resources.NewMockRepositoryResources(t) - tt.setupResources(repoResources, resourceClients, fakeDynamicClient, listGVK) + tt.setupResources(repoResources, resourceClients, mockClient, schema.GroupVersionKind{ + Group: resources.DashboardResource.Group, + Version: resources.DashboardResource.Version, + Kind: "DashboardList", + }) options := provisioningV0.ExportJobOptions{ Path: "grafana", diff --git a/pkg/registry/apis/provisioning/resources/client.go b/pkg/registry/apis/provisioning/resources/client.go index fcdabcb0705..b27f45b6892 100644 --- a/pkg/registry/apis/provisioning/resources/client.go +++ b/pkg/registry/apis/provisioning/resources/client.go @@ -10,7 +10,8 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" iam "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/services/apiserver" @@ -18,9 +19,10 @@ import ( ) var ( - UserResource = iam.UserResourceInfo.GroupVersionResource() - FolderResource = folders.FolderResourceInfo.GroupVersionResource() - DashboardResource = dashboard.DashboardResourceInfo.GroupVersionResource() + UserResource = iam.UserResourceInfo.GroupVersionResource() + FolderResource = folders.FolderResourceInfo.GroupVersionResource() + DashboardResource = dashboardV1.DashboardResourceInfo.GroupVersionResource() + DashboardResourceV2 = dashboardV2.DashboardResourceInfo.GroupVersionResource() // SupportedProvisioningResources is the list of resources that can fully managed from the UI SupportedProvisioningResources = []schema.GroupVersionResource{FolderResource, DashboardResource} diff --git a/pkg/registry/apps/apps.go b/pkg/registry/apps/apps.go index 40e6d8c830b..9de8798d97e 100644 --- a/pkg/registry/apps/apps.go +++ b/pkg/registry/apps/apps.go @@ -2,6 +2,7 @@ package appregistry import ( "context" + "slices" "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana/pkg/infra/log" @@ -13,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/builder/runner" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" "k8s.io/client-go/rest" ) @@ -33,6 +35,7 @@ func ProvideRegistryServiceSink( playlistAppProvider *playlist.PlaylistAppProvider, investigationAppProvider *investigations.InvestigationsAppProvider, advisorAppProvider *advisor.AdvisorAppProvider, + grafanaCfg *setting.Cfg, ) (*Service, error) { cfgWrapper := func(ctx context.Context) (*rest.Config, error) { cfg, err := restConfigProvider.GetRestConfig(ctx) @@ -55,7 +58,8 @@ func ProvideRegistryServiceSink( logger.Debug("Investigations backend is enabled") providers = append(providers, investigationAppProvider) } - if features.IsEnabledGlobally(featuremgmt.FlagGrafanaAdvisor) { + if features.IsEnabledGlobally(featuremgmt.FlagGrafanaAdvisor) && + !slices.Contains(grafanaCfg.DisablePlugins, "grafana-advisor-app") { providers = append(providers, advisorAppProvider) } apiGroupRunner, err = runner.NewAPIGroupRunner(cfg, providers...) diff --git a/pkg/server/instrumentation_service.go b/pkg/server/instrumentation_service.go index 9a200ec7f38..817064cdd83 100644 --- a/pkg/server/instrumentation_service.go +++ b/pkg/server/instrumentation_service.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "github.com/gorilla/mux" "github.com/grafana/dskit/services" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/setting" @@ -22,14 +23,14 @@ type instrumentationService struct { promGatherer prometheus.Gatherer } -func NewInstrumentationService(log log.Logger, cfg *setting.Cfg, promGatherer prometheus.Gatherer) (*instrumentationService, error) { - s := &instrumentationService{log: log, cfg: cfg, promGatherer: promGatherer} +func (ms *ModuleServer) initInstrumentationServer() (*instrumentationService, error) { + s := &instrumentationService{log: ms.log, cfg: ms.cfg, promGatherer: ms.promGatherer} + s.httpServ, ms.httpServerRouter = s.newInstrumentationServer() s.BasicService = services.NewBasicService(s.start, s.running, s.stop) return s, nil } func (s *instrumentationService) start(ctx context.Context) error { - s.httpServ = s.newInstrumentationServer(ctx) s.errChan = make(chan error) go func() { s.errChan <- s.httpServ.ListenAndServe() @@ -56,17 +57,17 @@ func (s *instrumentationService) stop(failureReason error) error { return nil } -func (s *instrumentationService) newInstrumentationServer(ctx context.Context) *http.Server { - router := http.NewServeMux() +func (s *instrumentationService) newInstrumentationServer() (*http.Server, *mux.Router) { + router := mux.NewRouter() router.Handle("/metrics", promhttp.HandlerFor(s.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true})) + addr := net.JoinHostPort(s.cfg.HTTPAddr, s.cfg.HTTPPort) srv := &http.Server{ // 5s timeout for header reads to avoid Slowloris attacks (https://thetooth.io/blog/slowloris-attack/) ReadHeaderTimeout: 5 * time.Second, - Addr: ":" + s.cfg.HTTPPort, + Addr: addr, Handler: router, - BaseContext: func(_ net.Listener) context.Context { return ctx }, } - return srv + return srv, router } diff --git a/pkg/server/instrumentation_service_test.go b/pkg/server/instrumentation_service_test.go index 1dc8ffa8349..7268d415112 100644 --- a/pkg/server/instrumentation_service_test.go +++ b/pkg/server/instrumentation_service_test.go @@ -18,7 +18,12 @@ import ( func TestRunInstrumentationService(t *testing.T) { cfg := setting.NewCfg() cfg.HTTPPort = "3001" - s, err := NewInstrumentationService(log.New("test-logger"), cfg, prometheus.DefaultGatherer) + ms := ModuleServer{ + log: log.New("test-logger"), + cfg: cfg, + promGatherer: prometheus.DefaultGatherer, + } + s, err := ms.initInstrumentationServer() require.NoError(t, err) ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) diff --git a/pkg/server/memberlist.go b/pkg/server/memberlist.go new file mode 100644 index 00000000000..b80a54fd2f5 --- /dev/null +++ b/pkg/server/memberlist.go @@ -0,0 +1,53 @@ +package server + +import ( + "github.com/grafana/dskit/dns" + "github.com/grafana/dskit/flagext" + "github.com/grafana/dskit/kv" + "github.com/grafana/dskit/kv/codec" + "github.com/grafana/dskit/kv/memberlist" + "github.com/grafana/dskit/ring" + "github.com/grafana/dskit/services" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/client_golang/prometheus" +) + +func (ms *ModuleServer) initMemberlistKV() (services.Service, error) { + logger := log.New("memberlist") + + dnsProviderReg := prometheus.WrapRegistererWithPrefix( + "grafana", + prometheus.WrapRegistererWith( + prometheus.Labels{"component": "memberlist"}, + ms.registerer, + ), + ) + dnsProvider := dns.NewProvider(logger, dnsProviderReg, dns.GolangResolverType) + + KVStore := kv.Config{Store: "memberlist"} + + memberlistKVsvc := memberlist.NewKVInitService(toMemberlistConfig(ms.cfg), logger, dnsProvider, ms.registerer) + KVStore.MemberlistKV = memberlistKVsvc.GetMemberlistKV + + ms.MemberlistKVConfig = KVStore + + return memberlistKVsvc, nil +} + +func toMemberlistConfig(cfg *setting.Cfg) *memberlist.KVConfig { + memberlistKVcfg := &memberlist.KVConfig{} + flagext.DefaultValues(memberlistKVcfg) + memberlistKVcfg.Codecs = []codec.Codec{ + ring.GetCodec(), + } + if cfg.MemberlistBindAddr != "" { + memberlistKVcfg.TCPTransport.BindAddrs = []string{cfg.MemberlistBindAddr} + } + if cfg.MemberlistAdvertiseAddr != "" { + memberlistKVcfg.AdvertiseAddr = cfg.MemberlistAdvertiseAddr + } + memberlistKVcfg.JoinMembers = []string{cfg.MemberlistJoinMember} + + return memberlistKVcfg +} diff --git a/pkg/server/module_server.go b/pkg/server/module_server.go index 11db7c204d3..c3774020ca4 100644 --- a/pkg/server/module_server.go +++ b/pkg/server/module_server.go @@ -9,22 +9,36 @@ import ( "strconv" "sync" + "github.com/gorilla/mux" + "github.com/grafana/dskit/kv" + "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/dskit/services" "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/modules" "github.com/grafana/grafana/pkg/services/authz" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/frontend" + "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/sql" - "github.com/prometheus/client_golang/prometheus" ) // NewModule returns an instance of a ModuleServer, responsible for managing // dskit modules (services). -func NewModule(opts Options, apiOpts api.ServerOptions, features featuremgmt.FeatureToggles, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, promGatherer prometheus.Gatherer) (*ModuleServer, error) { - s, err := newModuleServer(opts, apiOpts, features, cfg, storageMetrics, indexMetrics, promGatherer) +func NewModule(opts Options, + apiOpts api.ServerOptions, + features featuremgmt.FeatureToggles, + cfg *setting.Cfg, + storageMetrics *resource.StorageMetrics, + indexMetrics *resource.BleveIndexMetrics, + reg prometheus.Registerer, + promGatherer prometheus.Gatherer, + license licensing.Licensing, +) (*ModuleServer, error) { + s, err := newModuleServer(opts, apiOpts, features, cfg, storageMetrics, indexMetrics, reg, promGatherer, license) if err != nil { return nil, err } @@ -36,7 +50,7 @@ func NewModule(opts Options, apiOpts api.ServerOptions, features featuremgmt.Fea return s, nil } -func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremgmt.FeatureToggles, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, promGatherer prometheus.Gatherer) (*ModuleServer, error) { +func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremgmt.FeatureToggles, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, reg prometheus.Registerer, promGatherer prometheus.Gatherer, license licensing.Licensing) (*ModuleServer, error) { rootCtx, shutdownFn := context.WithCancel(context.Background()) s := &ModuleServer{ @@ -55,6 +69,8 @@ func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremg storageMetrics: storageMetrics, indexMetrics: indexMetrics, promGatherer: promGatherer, + registerer: reg, + license: license, } return s, nil @@ -78,6 +94,7 @@ type ModuleServer struct { mtx sync.Mutex storageMetrics *resource.StorageMetrics indexMetrics *resource.BleveIndexMetrics + license licensing.Licensing pidFile string version string @@ -85,6 +102,11 @@ type ModuleServer struct { buildBranch string promGatherer prometheus.Gatherer + registerer prometheus.Registerer + + MemberlistKVConfig kv.Config + httpServerRouter *mux.Router + distributor *resource.Distributor } // init initializes the server and its services. @@ -120,12 +142,15 @@ func (s *ModuleServer) Run() error { // only run the instrumentation server module if were not running a module that already contains an http server m.RegisterInvisibleModule(modules.InstrumentationServer, func() (services.Service, error) { - if m.IsModuleEnabled(modules.All) || m.IsModuleEnabled(modules.Core) { + if m.IsModuleEnabled(modules.All) || m.IsModuleEnabled(modules.Core) || m.IsModuleEnabled(modules.FrontendServer) { return services.NewBasicService(nil, nil, nil).WithName(modules.InstrumentationServer), nil } - return NewInstrumentationService(s.log, s.cfg, s.promGatherer) + return s.initInstrumentationServer() }) + m.RegisterModule(modules.MemberlistKV, s.initMemberlistKV) + m.RegisterModule(modules.StorageRing, s.initRing) + m.RegisterModule(modules.Core, func() (services.Service, error) { return NewService(s.cfg, s.opts, s.apiOpts) }) @@ -144,13 +169,17 @@ func (s *ModuleServer) Run() error { if err != nil { return nil, err } - return sql.ProvideUnifiedStorageGrpcService(s.cfg, s.features, nil, s.log, nil, docBuilders, s.storageMetrics, s.indexMetrics) + return sql.ProvideUnifiedStorageGrpcService(s.cfg, s.features, nil, s.log, nil, docBuilders, s.storageMetrics, s.indexMetrics, s.distributor) }) m.RegisterModule(modules.ZanzanaServer, func() (services.Service, error) { return authz.ProvideZanzanaService(s.cfg, s.features) }) + m.RegisterModule(modules.FrontendServer, func() (services.Service, error) { + return frontend.ProvideFrontendService(s.cfg, s.promGatherer, s.license) + }) + m.RegisterModule(modules.All, nil) return m.Run(s.context) diff --git a/pkg/server/ring.go b/pkg/server/ring.go new file mode 100644 index 00000000000..a76e6fc3ef2 --- /dev/null +++ b/pkg/server/ring.go @@ -0,0 +1,222 @@ +package server + +import ( + "context" + "fmt" + "net" + "os" + "strconv" + "time" + + "github.com/grafana/dskit/flagext" + "github.com/grafana/dskit/grpcclient" + "github.com/grafana/dskit/kv" + "github.com/grafana/dskit/netutil" + "github.com/grafana/dskit/ring" + ringclient "github.com/grafana/dskit/ring/client" + "github.com/grafana/dskit/services" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/storage/unified/resource" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "google.golang.org/grpc" + "google.golang.org/grpc/health/grpc_health_v1" +) + +const ringKey = "storage-ring" +const ringName = "unified_storage" +const numTokens = 128 +const heartbeatTimeout = time.Minute + +var metricsPrefix = ringName + "_" + +func (ms *ModuleServer) initRing() (services.Service, error) { + if !ms.cfg.EnableSharding { + return nil, nil + } + + logger := log.New("resource-server-ring") + reg := prometheus.WrapRegistererWithPrefix(metricsPrefix, ms.registerer) + + grpcclientcfg := &grpcclient.Config{} + flagext.DefaultValues(grpcclientcfg) + pool := newClientPool(*grpcclientcfg, logger, reg) + + ringStore, err := kv.NewClient( + ms.MemberlistKVConfig, + ring.GetCodec(), + kv.RegistererWithKVName(reg, ringName), + logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to create KV store client: %s", err) + } + + lifecyclerCfg, err := toLifecyclerConfig(ms.cfg, logger) + if err != nil { + return nil, fmt.Errorf("failed to initialize storage-ring lifecycler config: %s", err) + } + + // Define lifecycler delegates in reverse order (last to be called defined first because they're + // chained via "next delegate"). + delegate := ring.BasicLifecyclerDelegate(ring.NewInstanceRegisterDelegate(ring.JOINING, numTokens)) + delegate = ring.NewLeaveOnStoppingDelegate(delegate, logger) + delegate = ring.NewAutoForgetDelegate(heartbeatTimeout*2, delegate, logger) + + lifecycler, err := ring.NewBasicLifecycler( + lifecyclerCfg, + ringName, + ringKey, + ringStore, + delegate, + logger, + reg, + ) + if err != nil { + return nil, fmt.Errorf("failed to initialize storage-ring lifecycler: %s", err) + } + + storageRing, err := ring.NewWithStoreClientAndStrategy( + toRingConfig(ms.cfg, ms.MemberlistKVConfig), + ringName, + ringKey, + ringStore, + ring.NewIgnoreUnhealthyInstancesReplicationStrategy(), + reg, + logger, + ) + if err != nil { + return nil, fmt.Errorf("failed to initialize storage-ring ring: %s", err) + } + + startFn := func(ctx context.Context) error { + err = storageRing.StartAsync(ctx) + if err != nil { + return fmt.Errorf("failed to start the ring: %s", err) + } + err = lifecycler.StartAsync(ctx) + if err != nil { + return fmt.Errorf("failed to start the lifecycler: %s", err) + } + err = pool.StartAsync(ctx) + if err != nil { + return fmt.Errorf("failed to start the ring client pool: %s", err) + } + + logger.Info("waiting until resource server is JOINING in the ring") + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + if err := ring.WaitInstanceState(ctx, storageRing, lifecycler.GetInstanceID(), ring.JOINING); err != nil { + return fmt.Errorf("error switching to JOINING in the ring: %s", err) + } + logger.Info("resource server is JOINING in the ring") + + if err := lifecycler.ChangeState(ctx, ring.ACTIVE); err != nil { + return fmt.Errorf("error switching to ACTIVE in the ring: %s", err) + } + logger.Info("resource server is ACTIVE in the ring") + + return nil + } + + ms.distributor = &resource.Distributor{ + ClientPool: pool, + Ring: storageRing, + Lifecycler: lifecycler, + } + + ms.httpServerRouter.Path("/ring").Methods("GET", "POST").Handler(storageRing) + + svc := services.NewIdleService(startFn, nil) + + return svc, nil +} + +func toLifecyclerConfig(cfg *setting.Cfg, logger log.Logger) (ring.BasicLifecyclerConfig, error) { + instanceAddr, err := ring.GetInstanceAddr(cfg.MemberlistBindAddr, netutil.PrivateNetworkInterfacesWithFallback([]string{"eth0", "en0"}, logger), logger, true) + if err != nil { + return ring.BasicLifecyclerConfig{}, err + } + + instanceId := cfg.InstanceID + if instanceId == "" { + hostname, err := os.Hostname() + if err != nil { + return ring.BasicLifecyclerConfig{}, err + } + + instanceId = hostname + } + + _, grpcPortStr, err := net.SplitHostPort(cfg.GRPCServer.Address) + if err != nil { + return ring.BasicLifecyclerConfig{}, fmt.Errorf("could not get grpc port from grpc server address: %s", err) + } + + grpcPort, err := strconv.Atoi(grpcPortStr) + if err != nil { + return ring.BasicLifecyclerConfig{}, fmt.Errorf("error converting grpc address port to int: %s", err) + } + + return ring.BasicLifecyclerConfig{ + Addr: fmt.Sprintf("%s:%d", instanceAddr, grpcPort), + ID: instanceId, + HeartbeatPeriod: 15 * time.Second, + HeartbeatTimeout: heartbeatTimeout, + TokensObservePeriod: 0, + NumTokens: numTokens, + }, nil +} + +func toRingConfig(cfg *setting.Cfg, KVStore kv.Config) ring.Config { + rc := ring.Config{} + flagext.DefaultValues(&rc) + + rc.KVStore = KVStore + rc.HeartbeatTimeout = heartbeatTimeout + + rc.ReplicationFactor = 1 + + return rc +} + +func newClientPool(clientCfg grpcclient.Config, log log.Logger, reg prometheus.Registerer) *ringclient.Pool { + poolCfg := ringclient.PoolConfig{ + CheckInterval: 10 * time.Second, + HealthCheckEnabled: true, + HealthCheckTimeout: 10 * time.Second, + } + clientsCount := promauto.With(reg).NewGauge(prometheus.GaugeOpts{ + Name: "resource_server_clients", + Help: "The current number of resource server clients in the pool.", + }) + factoryRequestDuration := promauto.With(reg).NewHistogramVec(prometheus.HistogramOpts{ + Name: "resource_server_client_request_duration_seconds", + Help: "Time spent executing requests to resource server.", + Buckets: prometheus.ExponentialBuckets(0.008, 4, 7), + }, []string{"operation", "status_code"}) + + factory := ringclient.PoolInstFunc(func(inst ring.InstanceDesc) (ringclient.PoolClient, error) { + opts, err := clientCfg.DialOption(grpcclient.Instrument(factoryRequestDuration)) + if err != nil { + return nil, err + } + + conn, err := grpc.NewClient(inst.Addr, opts...) + if err != nil { + return nil, fmt.Errorf("failed to dial resource server %s %s: %s", inst.Id, inst.Addr, err) + } + + // TODO only use this if FlagAppPlatformGrpcClientAuth is not enabled + client := resource.NewLegacyResourceClient(conn) + + return &resource.RingClient{ + Client: client, + HealthClient: grpc_health_v1.NewHealthClient(conn), + Conn: conn, + }, nil + }) + + return ringclient.NewPool(ringName, poolCfg, nil, factory, clientsCount, log) +} diff --git a/pkg/services/annotations/accesscontrol/accesscontrol_test.go b/pkg/services/annotations/accesscontrol/accesscontrol_test.go index 0ee98bda98d..f4ec20ffcfa 100644 --- a/pkg/services/annotations/accesscontrol/accesscontrol_test.go +++ b/pkg/services/annotations/accesscontrol/accesscontrol_test.go @@ -53,7 +53,7 @@ func TestIntegrationAuthorize(t *testing.T) { fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sql, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) require.NoError(t, err) diff --git a/pkg/services/annotations/annotationsimpl/annotations_test.go b/pkg/services/annotations/annotationsimpl/annotations_test.go index 32bc3f51ca3..19b3c1cbe2e 100644 --- a/pkg/services/annotations/annotationsimpl/annotations_test.go +++ b/pkg/services/annotations/annotationsimpl/annotations_test.go @@ -66,7 +66,7 @@ func TestIntegrationAnnotationListingWithRBAC(t *testing.T) { fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sql, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) require.NoError(t, err) @@ -251,7 +251,7 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sql, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashSvc, err := dashboardsservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, features, accesscontrolmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sql, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) diff --git a/pkg/services/apiserver/builder/helper.go b/pkg/services/apiserver/builder/helper.go index 49d399e3914..bfd9040e9c2 100644 --- a/pkg/services/apiserver/builder/helper.go +++ b/pkg/services/apiserver/builder/helper.go @@ -315,6 +315,7 @@ func InstallAPIs( var ( dualWriterPeriodicDataSyncJobEnabled bool + dualWriterMigrationDataSyncDisabled bool dataSyncerInterval = time.Hour dataSyncerRecordsLimit = 1000 ) @@ -323,6 +324,7 @@ func InstallAPIs( if resourceExists { mode = resourceConfig.DualWriterMode dualWriterPeriodicDataSyncJobEnabled = resourceConfig.DualWriterPeriodicDataSyncJobEnabled + dualWriterMigrationDataSyncDisabled = resourceConfig.DualWriterMigrationDataSyncDisabled dataSyncerInterval = resourceConfig.DataSyncerInterval dataSyncerRecordsLimit = resourceConfig.DataSyncerRecordsLimit } @@ -343,6 +345,7 @@ func InstallAPIs( Kind: key, RequestInfo: requestInfo, Mode: mode, + SkipDataSync: dualWriterMigrationDataSyncDisabled, LegacyStorage: legacy, Storage: storage, ServerLockService: serverLock, diff --git a/pkg/services/authz/rbac/mapper.go b/pkg/services/authz/rbac/mapper.go index c930bb92fcb..023bfb6fc7a 100644 --- a/pkg/services/authz/rbac/mapper.go +++ b/pkg/services/authz/rbac/mapper.go @@ -67,6 +67,16 @@ func newMapper() mapper { "securevalues": newResourceTranslation("secret.securevalues", "uid", false), "keepers": newResourceTranslation("secret.keepers", "uid", false), }, + "query.grafana.app": { + "query": translation{ + resource: "datasources", + attribute: "uid", + verbMapping: map[string]string{ + utils.VerbCreate: "datasources:query", + }, + folderSupport: false, + }, + }, } } diff --git a/pkg/services/authz/rbac/service_test.go b/pkg/services/authz/rbac/service_test.go index 62c06f0e910..6a10dfed3c2 100644 --- a/pkg/services/authz/rbac/service_test.go +++ b/pkg/services/authz/rbac/service_test.go @@ -261,6 +261,26 @@ func TestService_checkPermission(t *testing.T) { }, expected: true, }, + { + name: "should return true for datasources if service has permission", + permissions: []accesscontrol.Permission{ + { + Action: "datasources:query", + Scope: "datasources:uid:some_datasource", + Kind: "datasources", + Attribute: "uid", + Identifier: "some_datasource", + }, + }, + check: CheckRequest{ + Action: "datasources:query", + Group: "query.grafana.app", + Resource: "query", + Name: "some_datasource", + Verb: utils.VerbCreate, + }, + expected: true, + }, } for _, tc := range testCases { diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 9466d45ea0d..e1cf5e22ff7 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -36,6 +36,7 @@ import ( "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/slugify" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -76,6 +77,7 @@ var ( const ( k8sDashboardKvNamespace = "dashboard-cleanup" k8sDashboardKvLastResourceVersionKey = "last-resource-version" + provisioningConcurrencyLimit = 10 ) type DashboardServiceImpl struct { @@ -89,6 +91,7 @@ type DashboardServiceImpl struct { folderPermissions accesscontrol.FolderPermissionsService dashboardPermissions accesscontrol.DashboardPermissionsService ac accesscontrol.AccessControl + acService accesscontrol.Service k8sclient client.K8sHandler metrics *dashboardsMetrics publicDashboardService publicdashboards.ServiceWrapper @@ -375,7 +378,7 @@ var _ registry.BackgroundService = (*DashboardServiceImpl)(nil) func ProvideDashboardServiceImpl( cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore folder.FolderStore, features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService, - ac accesscontrol.AccessControl, folderSvc folder.Service, fStore folder.Store, r prometheus.Registerer, + ac accesscontrol.AccessControl, acService accesscontrol.Service, folderSvc folder.Service, r prometheus.Registerer, restConfigProvider apiserver.RestConfigProvider, userService user.Service, quotaService quota.Service, orgService org.Service, publicDashboardService publicdashboards.ServiceWrapper, resourceClient resource.ResourceClient, dual dualwrite.Service, sorter sort.Service, @@ -390,6 +393,7 @@ func ProvideDashboardServiceImpl( features: features, folderPermissions: folderPermissionsService, ac: ac, + acService: acService, folderStore: folderStore, folderService: folderSvc, orgService: orgService, @@ -523,6 +527,7 @@ func (dr *DashboardServiceImpl) GetProvisionedDashboardData(ctx context.Context, results := []*dashboards.DashboardProvisioning{} var mu sync.Mutex g, ctx := errgroup.WithContext(ctx) + g.SetLimit(provisioningConcurrencyLimit) for _, org := range orgs { func(orgID int64) { g.Go(func() error { @@ -749,7 +754,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d var userID int64 if id, err := identity.UserIdentifier(dto.User.GetID()); err == nil { userID = id - } else { + } else if !identity.IsServiceIdentity(ctx) { dr.log.Debug("User does not belong to a user or service account namespace, using 0 as user ID", "id", dto.User.GetID()) } @@ -1009,7 +1014,7 @@ func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C // Only set default permissions if the Folder API Server is disabled. if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { - dr.setDefaultFolderPermissions(ctx, dto, f, true) + dr.setDefaultFolderPermissions(ctx, dto, f) } return f, nil } @@ -1159,8 +1164,11 @@ func (dr *DashboardServiceImpl) UnprovisionDashboard(ctx context.Context, dashbo UpdatedAt: time.Now(), Dashboard: dash.Data, }, nil, true) + if err != nil { + return err + } - return err + return dr.dashboardStore.UnprovisionDashboard(ctx, dashboardId) } return dashboards.ErrDashboardNotFound @@ -1214,16 +1222,15 @@ func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Con if err != nil { return err } - var permissions []accesscontrol.SetResourcePermissionCommand + permissions := []accesscontrol.SetResourcePermissionCommand{} + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ + UserID: uid, Permission: dashboardaccess.PERMISSION_ADMIN.String(), + }) + } + isNested := obj.GetFolder() != "" if !dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesDashboards) { // legacy behavior - permissions = []accesscontrol.SetResourcePermissionCommand{} - if user.IsIdentityType(claims.TypeUser) { - permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ - UserID: uid, Permission: dashboardaccess.PERMISSION_ADMIN.String(), - }) - } - isNested := obj.GetFolder() != "" if !isNested || !dr.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, @@ -1231,14 +1238,14 @@ func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Con }...) } } else { - if obj.GetFolder() != "" { + // Don't set any permissions for nested dashboards + if isNested { return nil } - permissions = []accesscontrol.SetResourcePermissionCommand{ - {UserID: uid, Permission: dashboardaccess.PERMISSION_ADMIN.String()}, + permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_ADMIN.String()}, {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, - } + }...) } svc := dr.getPermissionsService(key.Resource == "folders") if _, err := svc.SetPermissions(ctx, ns.OrgID, obj.GetName(), permissions...); err != nil { @@ -1246,6 +1253,12 @@ func (dr *DashboardServiceImpl) SetDefaultPermissionsAfterCreate(ctx context.Con return err } + // Clear permission cache for the user who created the dashboard, so that new permissions are fetched for their next call + // Required for cases when caller wants to immediately interact with the newly created object + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + dr.acService.ClearUserPermissionCache(user) + } + return nil } @@ -1287,37 +1300,25 @@ func (dr *DashboardServiceImpl) SetDefaultPermissions(ctx context.Context, dto * if _, err := svc.SetPermissions(ctx, dto.OrgID, dash.UID, permissions...); err != nil { dr.log.Error("Could not set default permissions", "dashboard", dash.Title, "error", err) } + + // Clear permission cache for the user who created the dashboard, so that new permissions are fetched for their next call + // Required for cases when caller wants to immediately interact with the newly created object + if !provisioned && dto.User.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { + dr.acService.ClearUserPermissionCache(dto.User) + } } -func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, cmd *folder.CreateFolderCommand, f *folder.Folder, provisioned bool) { - if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) { +func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, cmd *folder.CreateFolderCommand, f *folder.Folder) { + if dr.features.IsEnabledGlobally(featuremgmt.FlagKubernetesClientDashboardsFolders) || !dr.cfg.RBAC.PermissionsOnCreation("folder") || f.ParentUID != "" { return } ctx, span := tracer.Start(ctx, "dashboards.service.setDefaultFolderPermissions") defer span.End() - if !dr.cfg.RBAC.PermissionsOnCreation("folder") { - return - } - - var permissions []accesscontrol.SetResourcePermissionCommand - if !provisioned && cmd.SignedInUser.IsIdentityType(claims.TypeUser) { - userID, err := cmd.SignedInUser.GetInternalID() - if err != nil { - dr.log.Error("Could not make user admin", "folder", cmd.Title, "id", cmd.SignedInUser.GetID()) - } else { - permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ - UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), - }) - } - } - - if f.ParentUID == "" { - permissions = append(permissions, []accesscontrol.SetResourcePermissionCommand{ - {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, - {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, - }...) + permissions := []accesscontrol.SetResourcePermissionCommand{ + {BuiltinRole: string(org.RoleEditor), Permission: dashboardaccess.PERMISSION_EDIT.String()}, + {BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_VIEW.String()}, } if _, err := dr.folderPermissions.SetPermissions(ctx, cmd.OrgID, f.UID, permissions...); err != nil { @@ -2111,6 +2112,9 @@ type dashboardProvisioningWithUID struct { } func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) ([]*dashboardProvisioningWithUID, error) { + ctx, span := tracing.Start(ctx, "searchProvisionedDashboardsThroughK8s") + defer span.End() + if query == nil { return nil, errors.New("query cannot be nil") } @@ -2124,10 +2128,13 @@ func (dr *DashboardServiceImpl) searchProvisionedDashboardsThroughK8s(ctx contex return nil, err } + span.SetAttributes(attribute.Int("hits", len(searchResults.Hits))) + // loop through all hits concurrently to get the repo information (if set due to file provisioning) dashs := make([]*dashboardProvisioningWithUID, 0) var mu sync.Mutex g, ctx := errgroup.WithContext(ctx) + g.SetLimit(provisioningConcurrencyLimit) for _, h := range searchResults.Hits { func(hit dashboardv0.DashboardHit) { g.Go(func() error { diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index eb116482bdb..38c11736a1b 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -23,7 +23,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/publicdashboards" @@ -806,8 +805,8 @@ func permissionScenario(t *testing.T, desc string, fn permissionScenarioFunc) { featuremgmt.WithFeatures(), folderPermissions, ac, + actest.FakeService{}, folderService, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, @@ -902,8 +901,8 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt featuremgmt.WithFeatures(), folderPermissions, ac, + actest.FakeService{}, folderService, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, @@ -976,8 +975,8 @@ func saveTestDashboard(t *testing.T, title string, orgID int64, folderUID string features, accesscontrolmock.NewMockedPermissionsService(), actest.FakeAccessControl{ExpectedEvaluate: true}, + actest.FakeService{}, folderService, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, @@ -1058,8 +1057,8 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da featuremgmt.WithFeatures(), folderPermissions, actest.FakeAccessControl{ExpectedEvaluate: true}, + actest.FakeService{}, folderService, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 2861055a667..a6607d36800 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -1119,6 +1119,7 @@ func TestUnprovisionDashboard(t *testing.T) { }, "spec": map[string]any{}, }} + fakeStore.On("UnprovisionDashboard", mock.Anything, int64(1)).Return(nil).Once() k8sCliMock.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(dash, nil) dashWithoutAnnotations := &unstructured.Unstructured{Object: map[string]any{ "apiVersion": dashboardv0.APIVERSION, @@ -1169,6 +1170,7 @@ func TestUnprovisionDashboard(t *testing.T) { err := service.UnprovisionDashboard(ctx, 1) require.NoError(t, err) k8sCliMock.AssertExpectations(t) + fakeStore.AssertExpectations(t) }) } @@ -1288,8 +1290,9 @@ func TestSetDefaultPermissionsWhenSavingFolderForProvisionedDashboards(t *testin UID: "general", }, }, - ac: actest.FakeAccessControl{ExpectedEvaluate: true}, - log: log.NewNopLogger(), + ac: actest.FakeAccessControl{ExpectedEvaluate: true}, + acService: &actest.FakeService{}, + log: log.NewNopLogger(), } cmd := &folder.CreateFolderCommand{ @@ -2497,6 +2500,7 @@ func TestSetDefaultPermissionsAfterCreate(t *testing.T) { dashboardPermissions: permService, folderPermissions: permService, dashboardPermissionsReady: make(chan struct{}), + acService: &actest.FakeService{}, } service.RegisterDashboardPermissions(permService) diff --git a/pkg/services/dashboardsnapshots/service/service_test.go b/pkg/services/dashboardsnapshots/service/service_test.go index c647528785b..bdfc8b0d8d8 100644 --- a/pkg/services/dashboardsnapshots/service/service_test.go +++ b/pkg/services/dashboardsnapshots/service/service_test.go @@ -14,7 +14,7 @@ import ( "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" - acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/apiserver/client" "github.com/grafana/grafana/pkg/services/dashboards" dashdb "github.com/grafana/grafana/pkg/services/dashboards/database" @@ -22,7 +22,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboardsnapshots" dashsnapdb "github.com/grafana/grafana/pkg/services/dashboardsnapshots/database" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/quota/quotatest" @@ -112,9 +111,9 @@ func TestValidateDashboardExists(t *testing.T) { folderimpl.ProvideDashboardFolderStore(sqlStore), feats, nil, - acmock.New(), + actest.FakeAccessControl{}, + actest.FakeService{}, foldertest.NewFakeService(), - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 087b7327b81..0c70783efcf 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -590,15 +590,6 @@ var ( Stage: FeatureStagePublicPreview, Owner: awsDatasourcesSquad, }, - { - Name: "recoveryThreshold", - Description: "Enables feature recovery threshold (aka hysteresis) for threshold server-side expression", - Stage: FeatureStageGeneralAvailability, - FrontendOnly: false, - Owner: grafanaAlertingSquad, - RequiresRestart: true, - Expression: "true", - }, { Name: "lokiStructuredMetadata", Description: "Enables the loki data source to request structured metadata from the Loki server", @@ -1747,13 +1738,19 @@ var ( FrontendOnly: true, }, { - Name: "pluginsAutoUpdate", Description: "Enables auto-updating of users installed plugins", Stage: FeatureStageExperimental, FrontendOnly: false, Owner: grafanaPluginsPlatformSquad, }, + { + Name: "multiTenantFrontend", + Description: "Register MT frontend", + Stage: FeatureStageExperimental, + FrontendOnly: false, + Owner: grafanaFrontendPlatformSquad, + }, { Name: "alertingListViewV2PreviewToggle", Description: "Enables the alerting list view v2 preview toggle", @@ -1768,6 +1765,16 @@ var ( Owner: grafanaAlertingSquad, Expression: "false", }, + { + Name: "alertingBulkActionsInUI", + Description: "Enables the alerting bulk actions in the UI", + FrontendOnly: true, + Stage: FeatureStageGeneralAvailability, + Owner: grafanaAlertingSquad, + HideFromAdminPage: true, + HideFromDocs: true, + Expression: "true", // enabled by default + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index fe9151fc18c..9f6642437a7 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -77,7 +77,6 @@ queryServiceRewrite,experimental,@grafana/grafana-datasources-core-services,fals queryServiceFromUI,experimental,@grafana/grafana-datasources-core-services,false,false,true queryServiceFromExplore,experimental,@grafana/grafana-datasources-core-services,false,false,true cloudWatchBatchQueries,preview,@grafana/aws-datasources,false,false,false -recoveryThreshold,GA,@grafana/alerting-squad,false,true,false lokiStructuredMetadata,GA,@grafana/observability-logs,false,false,false cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false prometheusCodeModeMetricNamesSearch,experimental,@grafana/oss-big-tent,false,false,true @@ -229,5 +228,7 @@ unifiedNavbars,GA,@grafana/plugins-platform-backend,false,false,true logsPanelControls,preview,@grafana/observability-logs,false,false,true metricsFromProfiles,experimental,@grafana/observability-traces-and-profiling,false,false,true pluginsAutoUpdate,experimental,@grafana/plugins-platform-backend,false,false,false +multiTenantFrontend,experimental,@grafana/grafana-frontend-platform,false,false,false alertingListViewV2PreviewToggle,privatePreview,@grafana/alerting-squad,false,false,true alertRuleUseFiredAtForStartsAt,experimental,@grafana/alerting-squad,false,false,false +alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 3d2d2f8cfcb..4d577a49a4c 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -319,10 +319,6 @@ const ( // Runs CloudWatch metrics queries as separate batches FlagCloudWatchBatchQueries = "cloudWatchBatchQueries" - // FlagRecoveryThreshold - // Enables feature recovery threshold (aka hysteresis) for threshold server-side expression - FlagRecoveryThreshold = "recoveryThreshold" - // FlagLokiStructuredMetadata // Enables the loki data source to request structured metadata from the Loki server FlagLokiStructuredMetadata = "lokiStructuredMetadata" @@ -927,6 +923,10 @@ const ( // Enables auto-updating of users installed plugins FlagPluginsAutoUpdate = "pluginsAutoUpdate" + // FlagMultiTenantFrontend + // Register MT frontend + FlagMultiTenantFrontend = "multiTenantFrontend" + // FlagAlertingListViewV2PreviewToggle // Enables the alerting list view v2 preview toggle FlagAlertingListViewV2PreviewToggle = "alertingListViewV2PreviewToggle" @@ -934,4 +934,8 @@ const ( // FlagAlertRuleUseFiredAtForStartsAt // Use FiredAt for StartsAt when sending alerts to Alertmaanger FlagAlertRuleUseFiredAtForStartsAt = "alertRuleUseFiredAtForStartsAt" + + // FlagAlertingBulkActionsInUI + // Enables the alerting bulk actions in the UI + FlagAlertingBulkActionsInUI = "alertingBulkActionsInUI" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 4a11a5109c7..82f7b6df36b 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -6,7 +6,7 @@ { "metadata": { "name": "ABTestFeatureToggleA", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-13T21:13:13Z" }, "spec": { @@ -20,7 +20,7 @@ { "metadata": { "name": "ABTestFeatureToggleB", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-13T21:13:13Z" }, "spec": { @@ -34,7 +34,7 @@ { "metadata": { "name": "addFieldFromCalculationStatFunctions", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-03T14:39:58Z" }, "spec": { @@ -48,7 +48,7 @@ { "metadata": { "name": "aiGeneratedDashboardChanges", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-05T12:01:31Z" }, "spec": { @@ -61,7 +61,7 @@ { "metadata": { "name": "alertRuleRestore", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-05T14:15:26Z" }, "spec": { @@ -74,8 +74,8 @@ { "metadata": { "name": "alertRuleUseFiredAtForStartsAt", - "resourceVersion": "1745339544057", - "creationTimestamp": "2025-04-22T16:32:24Z" + "resourceVersion": "1745491786560", + "creationTimestamp": "2025-04-24T10:49:46Z" }, "spec": { "description": "Use FiredAt for StartsAt when sending alerts to Alertmaanger", @@ -87,7 +87,7 @@ { "metadata": { "name": "alertingBacktesting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-12-14T14:44:14Z" }, "spec": { @@ -96,10 +96,29 @@ "codeowner": "@grafana/alerting-squad" } }, + { + "metadata": { + "name": "alertingBulkActionsInUI", + "resourceVersion": "1745504721038", + "creationTimestamp": "2025-04-24T14:01:56Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-04-24 14:25:21.03825 +0000 UTC" + } + }, + "spec": { + "description": "Enables the alerting bulk actions in the UI", + "stage": "GA", + "codeowner": "@grafana/alerting-squad", + "frontend": true, + "hideFromAdminPage": true, + "hideFromDocs": true, + "expression": "true" + } + }, { "metadata": { "name": "alertingCentralAlertHistory", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-29T15:01:38Z" }, "spec": { @@ -112,7 +131,7 @@ { "metadata": { "name": "alertingDisableSendAlertsExternal", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-23T12:29:19Z" }, "spec": { @@ -126,7 +145,7 @@ { "metadata": { "name": "alertingFilterV2", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-11T11:29:26Z" }, "spec": { @@ -139,7 +158,7 @@ { "metadata": { "name": "alertingInsights", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-14T12:58:04Z" }, "spec": { @@ -154,7 +173,7 @@ { "metadata": { "name": "alertingJiraIntegration", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-14T12:22:04Z" }, "spec": { @@ -168,7 +187,7 @@ { "metadata": { "name": "alertingListViewV2", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-24T14:40:49Z" }, "spec": { @@ -181,8 +200,8 @@ { "metadata": { "name": "alertingListViewV2PreviewToggle", - "resourceVersion": "1745339544057", - "creationTimestamp": "2025-04-22T16:32:24Z" + "resourceVersion": "1745491786560", + "creationTimestamp": "2025-04-24T10:49:46Z" }, "spec": { "description": "Enables the alerting list view v2 preview toggle", @@ -194,7 +213,7 @@ { "metadata": { "name": "alertingMigrationUI", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-14T16:40:05Z" }, "spec": { @@ -209,7 +228,7 @@ { "metadata": { "name": "alertingNotificationsStepMode", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-22T11:07:45Z" }, "spec": { @@ -223,7 +242,7 @@ { "metadata": { "name": "alertingPrometheusRulesPrimary", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-27T12:27:16Z" }, "spec": { @@ -236,7 +255,7 @@ { "metadata": { "name": "alertingQueryAndExpressionsStepMode", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-26T06:33:14Z" }, "spec": { @@ -250,7 +269,7 @@ { "metadata": { "name": "alertingQueryOptimization", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-10T20:52:58Z" }, "spec": { @@ -263,7 +282,7 @@ { "metadata": { "name": "alertingRulePermanentlyDelete", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-03T11:18:25Z" }, "spec": { @@ -279,7 +298,7 @@ { "metadata": { "name": "alertingRuleRecoverDeleted", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-27T14:39:26Z" }, "spec": { @@ -295,7 +314,7 @@ { "metadata": { "name": "alertingRuleVersionHistoryRestore", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-17T12:25:32Z" }, "spec": { @@ -311,7 +330,7 @@ { "metadata": { "name": "alertingSaveStateCompressed", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-27T17:47:33Z" }, "spec": { @@ -324,7 +343,7 @@ { "metadata": { "name": "alertingSaveStatePeriodic", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-23T16:03:30Z" }, "spec": { @@ -336,7 +355,7 @@ { "metadata": { "name": "alertingSimplifiedRouting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-10T13:14:39Z" }, "spec": { @@ -349,7 +368,7 @@ { "metadata": { "name": "alertingUIOptimizeReducer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-18T10:59:00Z" }, "spec": { @@ -363,7 +382,7 @@ { "metadata": { "name": "alertmanagerRemoteOnly", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -375,7 +394,7 @@ { "metadata": { "name": "alertmanagerRemotePrimary", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -387,7 +406,7 @@ { "metadata": { "name": "alertmanagerRemoteSecondary", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-30T16:27:08Z" }, "spec": { @@ -399,7 +418,7 @@ { "metadata": { "name": "angularDeprecationUI", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-08-29T14:05:47Z" }, "spec": { @@ -413,7 +432,7 @@ { "metadata": { "name": "annotationPermissionUpdate", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-31T13:30:13Z" }, "spec": { @@ -426,7 +445,7 @@ { "metadata": { "name": "appPlatformGrpcClientAuth", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-14T10:47:18Z" }, "spec": { @@ -440,7 +459,7 @@ { "metadata": { "name": "assetSriChecks", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-04T10:56:35Z" }, "spec": { @@ -453,7 +472,7 @@ { "metadata": { "name": "authZGRPCServer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-13T09:41:35Z" }, "spec": { @@ -467,7 +486,7 @@ { "metadata": { "name": "awsAsyncQueryCaching", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-21T15:34:07Z" }, "spec": { @@ -480,7 +499,7 @@ { "metadata": { "name": "awsDatasourcesTempCredentials", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-06T15:06:11Z" }, "spec": { @@ -492,7 +511,7 @@ { "metadata": { "name": "azureMonitorDisableLogLimit", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-24T13:32:09Z" }, "spec": { @@ -505,7 +524,7 @@ { "metadata": { "name": "azureMonitorEnableUserAuth", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-27T14:01:54Z" }, "spec": { @@ -518,7 +537,7 @@ { "metadata": { "name": "azureMonitorLogsBuilderEditor", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-02T14:15:25Z" }, "spec": { @@ -531,7 +550,7 @@ { "metadata": { "name": "azureMonitorPrometheusExemplars", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-06T16:53:17Z" }, "spec": { @@ -544,7 +563,7 @@ { "metadata": { "name": "cachingOptimizeSerializationMemoryUsage", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-12T16:56:49Z" }, "spec": { @@ -556,7 +575,7 @@ { "metadata": { "name": "canvasPanelNesting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-05-31T19:03:34Z" }, "spec": { @@ -570,7 +589,7 @@ { "metadata": { "name": "canvasPanelPanZoom", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-02T19:52:21Z" }, "spec": { @@ -583,7 +602,7 @@ { "metadata": { "name": "cloudRBACRoles", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-10T13:19:01Z" }, "spec": { @@ -599,7 +618,7 @@ { "metadata": { "name": "cloudWatchBatchQueries", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-20T19:09:41Z" }, "spec": { @@ -611,7 +630,7 @@ { "metadata": { "name": "cloudWatchCrossAccountQuerying", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-11-28T11:39:12Z" }, "spec": { @@ -625,7 +644,7 @@ { "metadata": { "name": "cloudWatchNewLabelParsing", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-05T15:57:56Z" }, "spec": { @@ -638,7 +657,7 @@ { "metadata": { "name": "cloudWatchRoundUpEndTime", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-27T15:10:28Z" }, "spec": { @@ -651,7 +670,7 @@ { "metadata": { "name": "configurableSchedulerTick", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-26T16:44:12Z" }, "spec": { @@ -665,7 +684,7 @@ { "metadata": { "name": "correlations", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-09-16T13:14:27Z" }, "spec": { @@ -679,7 +698,7 @@ { "metadata": { "name": "crashDetection", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-12T15:07:27Z" }, "spec": { @@ -692,7 +711,7 @@ { "metadata": { "name": "dashboardDisableSchemaValidationV1", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -704,7 +723,7 @@ { "metadata": { "name": "dashboardDisableSchemaValidationV2", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -716,7 +735,7 @@ { "metadata": { "name": "dashboardNewLayouts", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-23T08:55:45Z" }, "spec": { @@ -729,7 +748,7 @@ { "metadata": { "name": "dashboardScene", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-13T08:51:21Z" }, "spec": { @@ -743,7 +762,7 @@ { "metadata": { "name": "dashboardSceneForViewers", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-02T19:02:25Z" }, "spec": { @@ -757,7 +776,7 @@ { "metadata": { "name": "dashboardSceneSolo", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-11T08:08:47Z" }, "spec": { @@ -771,7 +790,7 @@ { "metadata": { "name": "dashboardSchemaValidationLogging", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-11T16:52:46Z" }, "spec": { @@ -783,7 +802,7 @@ { "metadata": { "name": "dashgpt", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-08-30T20:22:05Z" }, "spec": { @@ -797,7 +816,7 @@ { "metadata": { "name": "dataplaneAggregator", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-09T08:41:07Z" }, "spec": { @@ -810,7 +829,7 @@ { "metadata": { "name": "dataplaneFrontendFallback", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-04-07T21:13:19Z" }, "spec": { @@ -825,7 +844,7 @@ { "metadata": { "name": "datasourceAPIServers", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-19T08:28:27Z" }, "spec": { @@ -838,7 +857,7 @@ { "metadata": { "name": "datasourceConnectionsTab", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-21T17:39:48Z" }, "spec": { @@ -851,7 +870,7 @@ { "metadata": { "name": "datasourceQueryTypes", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-23T16:46:28Z" }, "spec": { @@ -864,7 +883,7 @@ { "metadata": { "name": "disableClassicHTTPHistogram", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-18T19:37:44Z" }, "spec": { @@ -878,7 +897,7 @@ { "metadata": { "name": "disableEnvelopeEncryption", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-05-24T08:34:47Z" }, "spec": { @@ -892,7 +911,7 @@ { "metadata": { "name": "disableNumericMetricsSortingInExpressions", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-16T14:52:47Z" }, "spec": { @@ -905,7 +924,7 @@ { "metadata": { "name": "disableSSEDataplane", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-04-12T16:24:34Z" }, "spec": { @@ -917,7 +936,7 @@ { "metadata": { "name": "editPanelCSVDragAndDrop", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-01-24T09:43:44Z" }, "spec": { @@ -930,7 +949,7 @@ { "metadata": { "name": "elasticsearchCrossClusterSearch", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-12T22:20:04Z" }, "spec": { @@ -942,7 +961,7 @@ { "metadata": { "name": "elasticsearchImprovedParsing", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-15T17:05:54Z" }, "spec": { @@ -954,7 +973,7 @@ { "metadata": { "name": "enableDatagridEditing", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-04-24T14:46:31Z" }, "spec": { @@ -967,7 +986,7 @@ { "metadata": { "name": "enableExtensionsAdminPage", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-05T15:55:10Z" }, "spec": { @@ -980,7 +999,7 @@ { "metadata": { "name": "enableNativeHTTPHistogram", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-03T18:23:55Z" }, "spec": { @@ -994,7 +1013,7 @@ { "metadata": { "name": "enableSCIM", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-07T14:38:46Z" }, "spec": { @@ -1006,7 +1025,7 @@ { "metadata": { "name": "enableScopesInMetricsExplore", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-06T13:11:33Z" }, "spec": { @@ -1020,7 +1039,7 @@ { "metadata": { "name": "exploreLogsAggregatedMetrics", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1033,7 +1052,7 @@ { "metadata": { "name": "exploreLogsLimitedTimeRange", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1046,7 +1065,7 @@ { "metadata": { "name": "exploreLogsShardSplitting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-29T13:55:59Z" }, "spec": { @@ -1059,7 +1078,7 @@ { "metadata": { "name": "exploreMetricsRelatedLogs", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-05T16:28:43Z" }, "spec": { @@ -1072,7 +1091,7 @@ { "metadata": { "name": "expressionParser", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-17T00:59:11Z" }, "spec": { @@ -1085,7 +1104,7 @@ { "metadata": { "name": "extensionSidebar", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-03T10:16:35Z" }, "spec": { @@ -1098,7 +1117,7 @@ { "metadata": { "name": "externalCorePlugins", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-22T08:50:13Z" }, "spec": { @@ -1111,7 +1130,7 @@ { "metadata": { "name": "externalServiceAccounts", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-28T07:26:37Z" }, "spec": { @@ -1124,7 +1143,7 @@ { "metadata": { "name": "extraThemes", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-05-10T13:37:04Z" }, "spec": { @@ -1137,7 +1156,7 @@ { "metadata": { "name": "extractFieldsNameDeduplication", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-02T15:47:42Z" }, "spec": { @@ -1150,7 +1169,7 @@ { "metadata": { "name": "failWrongDSUID", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-20T10:56:39Z" }, "spec": { @@ -1163,7 +1182,7 @@ { "metadata": { "name": "faroDatasourceSelector", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-05-05T00:35:10Z" }, "spec": { @@ -1176,7 +1195,7 @@ { "metadata": { "name": "featureHighlights", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-02-03T11:53:23Z" }, "spec": { @@ -1190,7 +1209,7 @@ { "metadata": { "name": "featureToggleAdminPage", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-18T20:43:32Z" }, "spec": { @@ -1204,7 +1223,7 @@ { "metadata": { "name": "feedbackButton", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-02T17:08:15Z" }, "spec": { @@ -1217,7 +1236,7 @@ { "metadata": { "name": "fetchRulesUsingPost", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-29T12:17:44Z" }, "spec": { @@ -1231,7 +1250,7 @@ { "metadata": { "name": "formatString", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-13T18:17:12Z" }, "spec": { @@ -1245,7 +1264,7 @@ { "metadata": { "name": "grafanaAPIServerEnsureKubectlAccess", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-12-06T20:21:21Z" }, "spec": { @@ -1259,7 +1278,7 @@ { "metadata": { "name": "grafanaAPIServerWithExperimentalAPIs", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-06T18:55:22Z" }, "spec": { @@ -1273,7 +1292,7 @@ { "metadata": { "name": "grafanaAdvisor", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-20T10:08:00Z" }, "spec": { @@ -1285,7 +1304,7 @@ { "metadata": { "name": "grafanaManagedRecordingRules", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-22T17:53:16Z" }, "spec": { @@ -1299,7 +1318,7 @@ { "metadata": { "name": "grafanaManagedRecordingRulesDatasources", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-07T13:30:40Z" }, "spec": { @@ -1313,7 +1332,7 @@ { "metadata": { "name": "grafanaconThemes", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-06T11:08:04Z" }, "spec": { @@ -1329,7 +1348,7 @@ { "metadata": { "name": "groupAttributeSync", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-09T15:29:43Z" }, "spec": { @@ -1342,7 +1361,7 @@ { "metadata": { "name": "groupByVariable", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-14T17:18:04Z" }, "spec": { @@ -1356,7 +1375,7 @@ { "metadata": { "name": "groupToNestedTableTransformation", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-07T14:28:26Z" }, "spec": { @@ -1370,7 +1389,7 @@ { "metadata": { "name": "grpcServer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-09-26T20:25:34Z" }, "spec": { @@ -1383,7 +1402,7 @@ { "metadata": { "name": "homeSetupGuide", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-25T17:20:04Z" }, "spec": { @@ -1396,7 +1415,7 @@ { "metadata": { "name": "improvedExternalSessionHandling", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-17T10:54:39Z" }, "spec": { @@ -1409,7 +1428,7 @@ { "metadata": { "name": "improvedExternalSessionHandlingSAML", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-09T17:02:49Z" }, "spec": { @@ -1422,7 +1441,7 @@ { "metadata": { "name": "individualCookiePreferences", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-02-21T10:19:07Z" }, "spec": { @@ -1434,7 +1453,7 @@ { "metadata": { "name": "infinityRunQueriesInParallel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-14T12:54:04Z" }, "spec": { @@ -1446,7 +1465,7 @@ { "metadata": { "name": "influxdbBackendMigration", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-02-09T18:26:16Z", "deletionTimestamp": "2023-01-17T14:11:26Z" }, @@ -1461,7 +1480,7 @@ { "metadata": { "name": "influxdbRunQueriesInParallel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-01T10:58:24Z" }, "spec": { @@ -1473,7 +1492,7 @@ { "metadata": { "name": "influxqlStreamingParser", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-29T17:29:35Z" }, "spec": { @@ -1485,7 +1504,7 @@ { "metadata": { "name": "investigationsBackend", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-18T08:31:03Z" }, "spec": { @@ -1498,7 +1517,7 @@ { "metadata": { "name": "inviteUserExperimental", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-07T19:09:59Z" }, "spec": { @@ -1513,7 +1532,7 @@ { "metadata": { "name": "jaegerBackendMigration", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-15T14:40:20Z" }, "spec": { @@ -1525,7 +1544,7 @@ { "metadata": { "name": "jitterAlertRulesWithinGroups", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-18T18:48:11Z" }, "spec": { @@ -1539,7 +1558,7 @@ { "metadata": { "name": "k8SFolderCounts", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-27T17:10:44Z" }, "spec": { @@ -1552,7 +1571,7 @@ { "metadata": { "name": "k8SFolderMove", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-27T17:10:44Z" }, "spec": { @@ -1565,7 +1584,7 @@ { "metadata": { "name": "kubernetesAggregator", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-12T20:59:35Z" }, "spec": { @@ -1578,7 +1597,7 @@ { "metadata": { "name": "kubernetesClientDashboardsFolders", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-18T23:11:26Z" }, "spec": { @@ -1591,7 +1610,7 @@ { "metadata": { "name": "kubernetesDashboards", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-05T14:34:23Z" }, "spec": { @@ -1604,7 +1623,7 @@ { "metadata": { "name": "kubernetesFeatureToggles", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-18T05:32:44Z" }, "spec": { @@ -1618,7 +1637,7 @@ { "metadata": { "name": "kubernetesSnapshots", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-12-05T22:31:49Z" }, "spec": { @@ -1631,7 +1650,7 @@ { "metadata": { "name": "libraryPanelRBAC", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-11T23:30:50Z" }, "spec": { @@ -1644,7 +1663,7 @@ { "metadata": { "name": "localeFormatPreference", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-31T13:59:07Z" }, "spec": { @@ -1656,7 +1675,7 @@ { "metadata": { "name": "localizationForPlugins", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-31T04:38:38Z" }, "spec": { @@ -1668,7 +1687,7 @@ { "metadata": { "name": "logQLScope", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-11T11:53:24Z" }, "spec": { @@ -1683,7 +1702,7 @@ { "metadata": { "name": "logRequestsInstrumentedAsUnknown", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-06-10T08:56:55Z" }, "spec": { @@ -1695,7 +1714,7 @@ { "metadata": { "name": "logRowsPopoverMenu", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-16T09:48:10Z" }, "spec": { @@ -1709,7 +1728,7 @@ { "metadata": { "name": "logsContextDatasourceUi", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-01-27T14:12:01Z" }, "spec": { @@ -1724,7 +1743,7 @@ { "metadata": { "name": "logsExploreTableDefaultVisualization", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-02T15:28:15Z" }, "spec": { @@ -1737,7 +1756,7 @@ { "metadata": { "name": "logsExploreTableVisualisation", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-12T13:52:42Z" }, "spec": { @@ -1751,7 +1770,7 @@ { "metadata": { "name": "logsInfiniteScrolling", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-09T10:54:03Z" }, "spec": { @@ -1765,7 +1784,7 @@ { "metadata": { "name": "logsPanelControls", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-07T14:38:55Z" }, "spec": { @@ -1779,7 +1798,7 @@ { "metadata": { "name": "lokiExperimentalStreaming", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-19T10:03:51Z" }, "spec": { @@ -1791,7 +1810,7 @@ { "metadata": { "name": "lokiLabelNamesQueryApi", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-13T14:31:41Z" }, "spec": { @@ -1804,7 +1823,7 @@ { "metadata": { "name": "lokiLogsDataplane", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-13T07:58:00Z" }, "spec": { @@ -1816,7 +1835,7 @@ { "metadata": { "name": "lokiPredefinedOperations", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-02T10:52:36Z" }, "spec": { @@ -1829,7 +1848,7 @@ { "metadata": { "name": "lokiQueryHints", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-12-18T20:43:16Z" }, "spec": { @@ -1843,7 +1862,7 @@ { "metadata": { "name": "lokiQuerySplitting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-02-09T17:27:02Z" }, "spec": { @@ -1858,7 +1877,7 @@ { "metadata": { "name": "lokiQuerySplittingConfig", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-03-20T15:51:36Z" }, "spec": { @@ -1871,7 +1890,7 @@ { "metadata": { "name": "lokiRunQueriesInParallel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-19T09:34:01Z" }, "spec": { @@ -1883,7 +1902,7 @@ { "metadata": { "name": "lokiSendDashboardPanelNames", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-22T19:30:43Z" }, "spec": { @@ -1895,7 +1914,7 @@ { "metadata": { "name": "lokiShardSplitting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-23T11:21:03Z" }, "spec": { @@ -1908,7 +1927,7 @@ { "metadata": { "name": "lokiStructuredMetadata", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-16T16:06:14Z" }, "spec": { @@ -1921,7 +1940,7 @@ { "metadata": { "name": "managedDualWriter", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-19T14:50:39Z" }, "spec": { @@ -1935,7 +1954,7 @@ { "metadata": { "name": "metricsFromProfiles", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-09T10:55:28Z" }, "spec": { @@ -1948,7 +1967,7 @@ { "metadata": { "name": "mlExpressions", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-13T17:37:50Z" }, "spec": { @@ -1957,10 +1976,22 @@ "codeowner": "@grafana/alerting-squad" } }, + { + "metadata": { + "name": "multiTenantFrontend", + "resourceVersion": "1745438197175", + "creationTimestamp": "2025-04-23T19:56:37Z" + }, + "spec": { + "description": "Register MT frontend", + "stage": "experimental", + "codeowner": "@grafana/grafana-frontend-platform" + } + }, { "metadata": { "name": "multiTenantTempCredentials", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-02T20:25:50Z" }, "spec": { @@ -1970,10 +2001,23 @@ "hideFromDocs": true } }, + { + "metadata": { + "name": "multitenantFrontend", + "resourceVersion": "1745438122785", + "creationTimestamp": "2025-04-23T19:55:22Z", + "deletionTimestamp": "2025-04-23T19:56:37Z" + }, + "spec": { + "description": "Register MT frontend", + "stage": "experimental", + "codeowner": "@grafana/grafana-frontend-platform" + } + }, { "metadata": { "name": "mysqlAnsiQuotes", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-10-12T11:43:35Z" }, "spec": { @@ -1985,7 +2029,7 @@ { "metadata": { "name": "nestedFolders", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-10-26T14:15:14Z" }, "spec": { @@ -1998,7 +2042,7 @@ { "metadata": { "name": "newDashboardSharingComponent", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-03T15:02:18Z" }, "spec": { @@ -2012,7 +2056,7 @@ { "metadata": { "name": "newDashboardWithFiltersAndGroupBy", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-04T11:25:21Z" }, "spec": { @@ -2026,7 +2070,7 @@ { "metadata": { "name": "newFiltersUI", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-30T12:48:13Z" }, "spec": { @@ -2039,7 +2083,7 @@ { "metadata": { "name": "newFolderPicker", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-15T11:43:19Z" }, "spec": { @@ -2052,7 +2096,7 @@ { "metadata": { "name": "newLogsPanel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-04T17:40:17Z" }, "spec": { @@ -2065,7 +2109,7 @@ { "metadata": { "name": "newPDFRendering", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-08T12:09:34Z" }, "spec": { @@ -2078,7 +2122,7 @@ { "metadata": { "name": "newShareReportDrawer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-17T19:05:46Z" }, "spec": { @@ -2092,7 +2136,7 @@ { "metadata": { "name": "oauthRequireSubClaim", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-25T13:22:24Z" }, "spec": { @@ -2106,7 +2150,7 @@ { "metadata": { "name": "onPremToCloudMigrations", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-22T16:09:08Z" }, "spec": { @@ -2119,7 +2163,7 @@ { "metadata": { "name": "panelFilterVariable", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-03T12:15:54Z" }, "spec": { @@ -2133,7 +2177,7 @@ { "metadata": { "name": "panelMonitoring", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-10-09T05:19:08Z" }, "spec": { @@ -2147,7 +2191,7 @@ { "metadata": { "name": "panelTitleSearch", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-02-15T18:26:03Z" }, "spec": { @@ -2160,7 +2204,7 @@ { "metadata": { "name": "passwordlessMagicLinkAuthentication", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-14T13:50:55Z" }, "spec": { @@ -2174,7 +2218,7 @@ { "metadata": { "name": "pdfTables", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-06T13:39:22Z" }, "spec": { @@ -2186,7 +2230,7 @@ { "metadata": { "name": "permissionsFilterRemoveSubquery", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-08-02T07:39:25Z" }, "spec": { @@ -2198,7 +2242,7 @@ { "metadata": { "name": "pinNavItems", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-10T11:40:03Z" }, "spec": { @@ -2211,7 +2255,7 @@ { "metadata": { "name": "playlistsReconciler", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-20T03:09:31Z" }, "spec": { @@ -2224,7 +2268,7 @@ { "metadata": { "name": "pluginProxyPreserveTrailingSlash", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-05T11:36:14Z" }, "spec": { @@ -2237,8 +2281,8 @@ { "metadata": { "name": "pluginsAutoUpdate", - "resourceVersion": "1745339544057", - "creationTimestamp": "2025-04-22T16:32:24Z" + "resourceVersion": "1745491786560", + "creationTimestamp": "2025-04-24T10:49:46Z" }, "spec": { "description": "Enables auto-updating of users installed plugins", @@ -2249,7 +2293,7 @@ { "metadata": { "name": "pluginsCDNSyncLoader", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-07T10:07:08Z" }, "spec": { @@ -2261,7 +2305,7 @@ { "metadata": { "name": "pluginsDetailsRightPanel", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-08-13T09:55:30Z" }, "spec": { @@ -2275,7 +2319,7 @@ { "metadata": { "name": "pluginsFrontendSandbox", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-05T08:51:36Z" }, "spec": { @@ -2287,7 +2331,7 @@ { "metadata": { "name": "pluginsSkipHostEnvVars", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-15T17:09:14Z" }, "spec": { @@ -2299,7 +2343,7 @@ { "metadata": { "name": "pluginsSriChecks", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-04T12:55:09Z" }, "spec": { @@ -2312,7 +2356,7 @@ { "metadata": { "name": "preinstallAutoUpdate", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-07T12:14:25Z" }, "spec": { @@ -2325,7 +2369,7 @@ { "metadata": { "name": "preserveDashboardStateWhenNavigating", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-27T12:28:06Z" }, "spec": { @@ -2339,7 +2383,7 @@ { "metadata": { "name": "promQLScope", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-01-29T20:22:17Z" }, "spec": { @@ -2354,7 +2398,7 @@ { "metadata": { "name": "prometheusAzureOverrideAudience", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-05-30T15:43:32Z", "deletionTimestamp": "2023-07-16T21:30:14Z" }, @@ -2368,7 +2412,7 @@ { "metadata": { "name": "prometheusCodeModeMetricNamesSearch", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-04-04T20:38:23Z" }, "spec": { @@ -2381,7 +2425,7 @@ { "metadata": { "name": "prometheusSpecialCharsInLabelValues", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-18T21:31:08Z" }, "spec": { @@ -2391,24 +2435,10 @@ "frontend": true } }, - { - "metadata": { - "name": "prometheusUsesCombobox", - "resourceVersion": "1743693517832", - "creationTimestamp": "2024-10-23T11:18:33Z", - "deletionTimestamp": "2025-04-11T19:25:28Z" - }, - "spec": { - "description": "Use new **Combobox** component for Prometheus query editor", - "stage": "GA", - "codeowner": "@grafana/oss-big-tent", - "expression": "true" - } - }, { "metadata": { "name": "provisioning", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-22T09:03:50Z" }, "spec": { @@ -2421,7 +2451,7 @@ { "metadata": { "name": "publicDashboardsEmailSharing", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-01-03T19:45:15Z" }, "spec": { @@ -2435,7 +2465,7 @@ { "metadata": { "name": "publicDashboardsScene", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-22T14:48:21Z" }, "spec": { @@ -2449,7 +2479,7 @@ { "metadata": { "name": "queryLibrary", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-10-07T18:31:45Z", "deletionTimestamp": "2023-03-20T16:00:14Z" }, @@ -2462,11 +2492,8 @@ { "metadata": { "name": "queryService", - "resourceVersion": "1745480536808", - "creationTimestamp": "2024-04-19T09:26:21Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-24 07:42:16.80869 +0000 UTC" - } + "resourceVersion": "1745491786560", + "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { "description": "Register /apis/query.grafana.app/ -- will eventually replace /api/ds/query", @@ -2478,7 +2505,7 @@ { "metadata": { "name": "queryServiceFromExplore", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-02T10:00:33Z" }, "spec": { @@ -2491,11 +2518,8 @@ { "metadata": { "name": "queryServiceFromUI", - "resourceVersion": "1745480536808", - "creationTimestamp": "2024-04-19T09:26:21Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-24 07:42:16.80869 +0000 UTC" - } + "resourceVersion": "1745491786560", + "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { "description": "Routes requests to the new query service", @@ -2507,11 +2531,8 @@ { "metadata": { "name": "queryServiceRewrite", - "resourceVersion": "1745480536808", - "creationTimestamp": "2024-04-19T09:26:21Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-24 07:42:16.80869 +0000 UTC" - } + "resourceVersion": "1745491786560", + "creationTimestamp": "2024-04-19T09:26:21Z" }, "spec": { "description": "Rewrite requests targeting /ds/query to the query service", @@ -2523,7 +2544,7 @@ { "metadata": { "name": "recordedQueriesMulti", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-14T12:34:22Z" }, "spec": { @@ -2533,24 +2554,10 @@ "expression": "true" } }, - { - "metadata": { - "name": "recoveryThreshold", - "resourceVersion": "1745339544057", - "creationTimestamp": "2023-10-10T14:51:50Z" - }, - "spec": { - "description": "Enables feature recovery threshold (aka hysteresis) for threshold server-side expression", - "stage": "GA", - "codeowner": "@grafana/alerting-squad", - "requiresRestart": true, - "expression": "true" - } - }, { "metadata": { "name": "refactorVariablesTimeRange", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-06T13:12:09Z" }, "spec": { @@ -2563,7 +2570,7 @@ { "metadata": { "name": "regressionTransformation", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-24T14:49:16Z" }, "spec": { @@ -2576,7 +2583,7 @@ { "metadata": { "name": "reloadDashboardsOnParamsChange", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-25T12:56:54Z" }, "spec": { @@ -2590,7 +2597,7 @@ { "metadata": { "name": "renderAuthJWT", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-04-03T16:53:38Z" }, "spec": { @@ -2603,7 +2610,7 @@ { "metadata": { "name": "rendererDisableAppPluginsPreload", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-02-24T14:43:06Z" }, "spec": { @@ -2618,7 +2625,7 @@ { "metadata": { "name": "reportingRetries", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-08-31T07:47:47Z" }, "spec": { @@ -2631,7 +2638,7 @@ { "metadata": { "name": "reportingUseRawTimeRange", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-14T20:08:03Z" }, "spec": { @@ -2644,7 +2651,7 @@ { "metadata": { "name": "rolePickerDrawer", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-26T12:51:38Z" }, "spec": { @@ -2656,7 +2663,7 @@ { "metadata": { "name": "scopeApi", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-11-27T07:58:25Z" }, "spec": { @@ -2670,7 +2677,7 @@ { "metadata": { "name": "scopeFilters", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-05T15:41:19Z" }, "spec": { @@ -2684,7 +2691,7 @@ { "metadata": { "name": "scopeSearchAllLevels", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-14T07:42:16Z" }, "spec": { @@ -2698,7 +2705,7 @@ { "metadata": { "name": "secretsManagementAppPlatform", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-19T09:25:14Z" }, "spec": { @@ -2710,7 +2717,7 @@ { "metadata": { "name": "showDashboardValidationWarnings", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-10-14T13:51:05Z" }, "spec": { @@ -2722,7 +2729,7 @@ { "metadata": { "name": "sqlDatasourceDatabaseSelection", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-06-06T16:28:52Z" }, "spec": { @@ -2736,7 +2743,7 @@ { "metadata": { "name": "sqlExpressions", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-02-27T21:16:00Z" }, "spec": { @@ -2748,7 +2755,7 @@ { "metadata": { "name": "sseGroupByDatasource", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-07T20:02:07Z" }, "spec": { @@ -2760,7 +2767,7 @@ { "metadata": { "name": "ssoSettingsApi", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-11-08T09:50:01Z" }, "spec": { @@ -2774,7 +2781,7 @@ { "metadata": { "name": "ssoSettingsLDAP", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-18T11:31:27Z" }, "spec": { @@ -2788,7 +2795,7 @@ { "metadata": { "name": "ssoSettingsSAML", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-03-14T11:04:45Z" }, "spec": { @@ -2802,7 +2809,7 @@ { "metadata": { "name": "storage", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2022-03-17T17:19:23Z" }, "spec": { @@ -2814,7 +2821,7 @@ { "metadata": { "name": "tableNextGen", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-26T03:57:57Z" }, "spec": { @@ -2826,7 +2833,7 @@ { "metadata": { "name": "tableSharedCrosshair", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-12-13T09:33:14Z" }, "spec": { @@ -2839,7 +2846,7 @@ { "metadata": { "name": "teamHttpHeadersMimir", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-13T10:42:47Z" }, "spec": { @@ -2852,7 +2859,7 @@ { "metadata": { "name": "templateVariablesUsesCombobox", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-31T09:53:13Z" }, "spec": { @@ -2865,7 +2872,7 @@ { "metadata": { "name": "timeRangeProvider", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-22T10:52:33Z" }, "spec": { @@ -2877,7 +2884,7 @@ { "metadata": { "name": "tlsMemcached", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-05-09T19:12:08Z" }, "spec": { @@ -2890,7 +2897,7 @@ { "metadata": { "name": "transformationsRedesign", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-07-12T16:35:49Z" }, "spec": { @@ -2905,7 +2912,7 @@ { "metadata": { "name": "unifiedHistory", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-13T10:41:18Z" }, "spec": { @@ -2918,7 +2925,7 @@ { "metadata": { "name": "unifiedNavbars", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-09T12:51:22Z" }, "spec": { @@ -2932,7 +2939,7 @@ { "metadata": { "name": "unifiedRequestLog", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-03-31T13:38:09Z" }, "spec": { @@ -2946,7 +2953,7 @@ { "metadata": { "name": "unifiedStorageBigObjectsSupport", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-10-17T10:18:29Z" }, "spec": { @@ -2958,7 +2965,7 @@ { "metadata": { "name": "unifiedStorageGrpcConnectionPool", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-21T13:24:54Z" }, "spec": { @@ -2972,11 +2979,8 @@ { "metadata": { "name": "unifiedStorageHistoryPruner", - "resourceVersion": "1745478115689", - "creationTimestamp": "2025-03-17T10:36:38Z", - "annotations": { - "grafana.app/updatedTimestamp": "2025-04-24 07:01:55.689179 +0000 UTC" - } + "resourceVersion": "1745491786560", + "creationTimestamp": "2025-03-17T10:36:38Z" }, "spec": { "description": "Enables the unified storage history pruner", @@ -2990,7 +2994,7 @@ { "metadata": { "name": "unifiedStorageSearch", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-30T19:46:14Z" }, "spec": { @@ -3004,7 +3008,7 @@ { "metadata": { "name": "unifiedStorageSearchPermissionFiltering", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-01-22T11:38:37Z" }, "spec": { @@ -3019,7 +3023,7 @@ { "metadata": { "name": "unifiedStorageSearchSprinkles", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-18T17:00:54Z" }, "spec": { @@ -3033,7 +3037,7 @@ { "metadata": { "name": "unifiedStorageSearchUI", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-12-19T18:21:48Z" }, "spec": { @@ -3047,7 +3051,7 @@ { "metadata": { "name": "useScopesNavigationEndpoint", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-03-31T15:20:00Z" }, "spec": { @@ -3062,7 +3066,7 @@ { "metadata": { "name": "useSessionStorageForRedirection", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-09-23T09:31:23Z" }, "spec": { @@ -3075,7 +3079,7 @@ { "metadata": { "name": "wargamesTesting", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2023-09-13T18:32:01Z" }, "spec": { @@ -3087,7 +3091,7 @@ { "metadata": { "name": "xrayApplicationSignals", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2025-04-01T14:42:02Z" }, "spec": { @@ -3102,7 +3106,7 @@ { "metadata": { "name": "zanzana", - "resourceVersion": "1745339544057", + "resourceVersion": "1745491786560", "creationTimestamp": "2024-06-19T13:59:47Z" }, "spec": { diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 389d939be1e..ed286327c02 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -768,7 +768,7 @@ func (s *Service) CreateLegacy(ctx context.Context, cmd *folder.CreateFolderComm var userID int64 if id, err := identity.UserIdentifier(cmd.SignedInUser.GetID()); err == nil { userID = id - } else { + } else if !identity.IsServiceIdentity(ctx) { s.log.Warn("User does not belong to a user or service account namespace, using 0 as user ID", "id", cmd.SignedInUser.GetID()) } @@ -918,7 +918,7 @@ func (s *Service) legacyUpdate(ctx context.Context, cmd *folder.UpdateFolderComm var userID int64 if id, err := identity.UserIdentifier(cmd.SignedInUser.GetID()); err == nil { userID = id - } else { + } else if !identity.IsServiceIdentity(ctx) { s.log.Warn("User does not belong to a user or service account namespace, using 0 as user ID", "id", cmd.SignedInUser.GetID()) } @@ -1441,7 +1441,7 @@ func (s *Service) buildSaveDashboardCommand(ctx context.Context, dto *dashboards var userID int64 if id, err := identity.UserIdentifier(dto.User.GetID()); err == nil { userID = id - } else { + } else if !identity.IsServiceIdentity(ctx) { s.log.Warn("User does not belong to a user or service account namespace, using 0 as user ID", "id", dto.User.GetID()) } diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 15f48dbcce4..8665b079bcc 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -443,7 +443,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { }) publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) - dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, serviceWithFlagOn, nestedFolderStore, nil, + dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOn, folderPermissions, ac, actest.FakeService{}, serviceWithFlagOn, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), @@ -533,7 +533,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuresFlagOff, - folderPermissions, ac, serviceWithFlagOff, nestedFolderStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + folderPermissions, ac, actest.FakeService{}, serviceWithFlagOff, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), ) @@ -679,8 +679,8 @@ func TestIntegrationNestedFolderService(t *testing.T) { tc.service.store = nestedFolderStore publicDashboardFakeService.On("DeleteByDashboardUIDs", mock.Anything, mock.Anything, mock.Anything).Return(nil) - dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, tc.service, - tc.service.store, nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, + dashSrv, err := dashboardservice.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, tc.featuresFlag, folderPermissions, ac, actest.FakeService{}, tc.service, + nil, client.MockTestRestConfig{}, nil, quotaService, nil, publicDashboardFakeService, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), @@ -1465,8 +1465,8 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { featuresFlagOn, acmock.NewMockedPermissionsService(), actest.FakeAccessControl{}, + actest.FakeService{}, serviceWithFlagOn, - nestedFolderStore, nil, client.MockTestRestConfig{}, nil, diff --git a/pkg/services/frontend/frontend_service.go b/pkg/services/frontend/frontend_service.go new file mode 100644 index 00000000000..81b67859ec2 --- /dev/null +++ b/pkg/services/frontend/frontend_service.go @@ -0,0 +1,88 @@ +package frontend + +import ( + "context" + "net" + "net/http" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + + "github.com/grafana/dskit/services" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/licensing" + "github.com/grafana/grafana/pkg/setting" +) + +type frontendService struct { + *services.BasicService + cfg *setting.Cfg + httpServ *http.Server + log log.Logger + errChan chan error + promGatherer prometheus.Gatherer + + index *IndexProvider +} + +func ProvideFrontendService(cfg *setting.Cfg, promGatherer prometheus.Gatherer, license licensing.Licensing) (*frontendService, error) { + index, err := NewIndexProvider(cfg, license) + if err != nil { + return nil, err + } + + s := &frontendService{ + cfg: cfg, + log: log.New("frontend-server"), + promGatherer: promGatherer, + index: index, + } + s.BasicService = services.NewBasicService(s.start, s.running, s.stop) + return s, nil +} + +func (s *frontendService) start(ctx context.Context) error { + s.httpServ = s.newFrontendServer(ctx) + s.errChan = make(chan error) + go func() { + s.errChan <- s.httpServ.ListenAndServe() + }() + return nil +} + +func (s *frontendService) running(ctx context.Context) error { + select { + case <-ctx.Done(): + return nil + case err := <-s.errChan: + return err + } +} + +func (s *frontendService) stop(failureReason error) error { + s.log.Info("stopping frontend server", "reason", failureReason) + if err := s.httpServ.Shutdown(context.Background()); err != nil { + s.log.Error("failed to shutdown frontend server", "error", err) + return err + } + return nil +} + +func (s *frontendService) newFrontendServer(ctx context.Context) *http.Server { + s.log.Info("starting frontend server", "addr", ":"+s.cfg.HTTPPort) + + router := http.NewServeMux() + router.Handle("/metrics", promhttp.HandlerFor(s.promGatherer, promhttp.HandlerOpts{EnableOpenMetrics: true})) + router.HandleFunc("/", s.index.HandleRequest) + + server := &http.Server{ + // 5s timeout for header reads to avoid Slowloris attacks (https://thetooth.io/blog/slowloris-attack/) + ReadHeaderTimeout: 5 * time.Second, + Addr: ":" + s.cfg.HTTPPort, + Handler: router, + BaseContext: func(_ net.Listener) context.Context { return ctx }, + } + + return server +} diff --git a/pkg/services/frontend/index.go b/pkg/services/frontend/index.go new file mode 100644 index 00000000000..4f02bbe1127 --- /dev/null +++ b/pkg/services/frontend/index.go @@ -0,0 +1,108 @@ +package frontend + +import ( + "context" + "embed" + "errors" + "fmt" + "html/template" + "net/http" + "syscall" + + "github.com/grafana/grafana-app-sdk/logging" + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/api/webassets" + "github.com/grafana/grafana/pkg/middleware" + "github.com/grafana/grafana/pkg/services/licensing" + "github.com/grafana/grafana/pkg/setting" +) + +type IndexProvider struct { + log logging.Logger + index *template.Template + data IndexViewData +} + +type IndexViewData struct { + CSPContent string + CSPEnabled bool + IsDevelopmentEnv bool + + AppSubUrl string + BuildVersion string + BuildCommit string + AppTitle string + + Assets *dtos.EntryPointAssets // Includes CDN info + + // Nonce is a cryptographic identifier for use with Content Security Policy. + Nonce string +} + +// Templates setup. +var ( + //go:embed *.html + templatesFS embed.FS + + // templates + htmlTemplates = template.Must(template.New("html").Delims("[[", "]]").ParseFS(templatesFS, `*.html`)) +) + +func NewIndexProvider(cfg *setting.Cfg, license licensing.Licensing) (*IndexProvider, error) { + assets, err := webassets.GetWebAssets(context.Background(), cfg, license) + if err != nil { + return nil, err + } + t := htmlTemplates.Lookup("index.html") + if t == nil { + return nil, fmt.Errorf("missing index template") + } + + return &IndexProvider{ + log: logging.DefaultLogger.With("logger", "index-provider"), + index: t, + data: IndexViewData{ + AppTitle: "Grafana", + AppSubUrl: cfg.AppSubURL, // Based on the request? + BuildVersion: cfg.BuildVersion, + BuildCommit: cfg.BuildCommit, + Assets: assets, + + CSPEnabled: cfg.CSPEnabled, + CSPContent: cfg.CSPTemplate, + + IsDevelopmentEnv: cfg.Env == setting.Dev, + }, + }, nil +} + +func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http.Request) { + if request.Method != "GET" { + writer.WriteHeader(http.StatusMethodNotAllowed) + return + } + + nonce, err := middleware.GenerateNonce() + if err != nil { + p.log.Error("error creating nonce", "err", err) + writer.WriteHeader(500) + return + } + + // TODO -- restructure so the static stuff is under one variable and the rest is dynamic + data := p.data // copy everything + data.Nonce = nonce + + if data.CSPEnabled { + data.CSPContent = middleware.ReplacePolicyVariables(p.data.CSPContent, p.data.AppSubUrl, data.Nonce) + } + + writer.Header().Set("Content-Type", "text/html; charset=UTF-8") + writer.WriteHeader(200) + if err := p.index.Execute(writer, &data); err != nil { + if errors.Is(err, syscall.EPIPE) { // Client has stopped listening. + return + } + panic(fmt.Sprintf("Error rendering index\n %s", err.Error())) + } +} diff --git a/pkg/services/frontend/index.html b/pkg/services/frontend/index.html new file mode 100644 index 00000000000..6778c160ca7 --- /dev/null +++ b/pkg/services/frontend/index.html @@ -0,0 +1,33 @@ + + + + [[ if and .CSPEnabled .IsDevelopmentEnv ]] + + + [[ end ]] + + + + + + [[.AppTitle]] + + + + + + [[range $asset := .Assets.CSSFiles]] + + [[end]] + + + + + + +

Grafana Frontend Server ([[.BuildVersion]])

+

This is a simple static HTML page served by the Grafana frontend server module.

+ + diff --git a/pkg/services/libraryelements/api.go b/pkg/services/libraryelements/api.go index 09efc428c11..a26336e63c1 100644 --- a/pkg/services/libraryelements/api.go +++ b/pkg/services/libraryelements/api.go @@ -81,7 +81,7 @@ func (l *LibraryElementService) createHandler(c *contextmodel.ReqContext) respon element, err := l.createLibraryElement(c.Req.Context(), c.SignedInUser, cmd) if err != nil { - return toLibraryElementError(err, "Failed to create library element") + return l.toLibraryElementError(err, "Failed to create library element") } metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryElements).Inc() @@ -118,7 +118,7 @@ func (l *LibraryElementService) createHandler(c *contextmodel.ReqContext) respon func (l *LibraryElementService) deleteHandler(c *contextmodel.ReqContext) response.Response { id, err := l.deleteLibraryElement(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":uid"]) if err != nil { - return toLibraryElementError(err, "Failed to delete library element") + return l.toLibraryElementError(err, "Failed to delete library element") } return response.JSON(http.StatusOK, model.DeleteLibraryElementResponse{ @@ -148,7 +148,7 @@ func (l *LibraryElementService) getHandler(c *contextmodel.ReqContext) response. }, ) if err != nil { - return toLibraryElementError(err, "Failed to get library element") + return l.toLibraryElementError(err, "Failed to get library element") } if l.features.IsEnabled(ctx, featuremgmt.FlagLibraryPanelRBAC) { @@ -189,13 +189,13 @@ func (l *LibraryElementService) getAllHandler(c *contextmodel.ReqContext) respon } elementsResult, err := l.getAllLibraryElements(c.Req.Context(), c.SignedInUser, query) if err != nil { - return toLibraryElementError(err, "Failed to get library elements") + return l.toLibraryElementError(err, "Failed to get library elements") } if l.features.IsEnabled(c.Req.Context(), featuremgmt.FlagLibraryPanelRBAC) { filteredPanels, err := l.filterLibraryPanelsByPermission(c, elementsResult.Elements) if err != nil { - return toLibraryElementError(err, "Failed to evaluate permissions") + return l.toLibraryElementError(err, "Failed to evaluate permissions") } elementsResult.Elements = filteredPanels } @@ -241,7 +241,7 @@ func (l *LibraryElementService) patchHandler(c *contextmodel.ReqContext) respons element, err := l.patchLibraryElement(c.Req.Context(), c.SignedInUser, cmd, web.Params(c.Req)[":uid"]) if err != nil { - return toLibraryElementError(err, "Failed to update library element") + return l.toLibraryElementError(err, "Failed to update library element") } metrics.MFolderIDsServiceCount.WithLabelValues(metrics.LibraryElements).Inc() @@ -276,7 +276,7 @@ func (l *LibraryElementService) patchHandler(c *contextmodel.ReqContext) respons func (l *LibraryElementService) getConnectionsHandler(c *contextmodel.ReqContext) response.Response { connections, err := l.getConnections(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":uid"]) if err != nil { - return toLibraryElementError(err, "Failed to get connections") + return l.toLibraryElementError(err, "Failed to get connections") } return response.JSON(http.StatusOK, model.LibraryElementConnectionsResponse{Result: connections}) @@ -296,13 +296,13 @@ func (l *LibraryElementService) getConnectionsHandler(c *contextmodel.ReqContext func (l *LibraryElementService) getByNameHandler(c *contextmodel.ReqContext) response.Response { elements, err := l.getLibraryElementsByName(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":name"]) if err != nil { - return toLibraryElementError(err, "Failed to get library element") + return l.toLibraryElementError(err, "Failed to get library element") } if l.features.IsEnabled(c.Req.Context(), featuremgmt.FlagLibraryPanelRBAC) { filteredElements, err := l.filterLibraryPanelsByPermission(c, elements) if err != nil { - return toLibraryElementError(err, err.Error()) + return l.toLibraryElementError(err, err.Error()) } return response.JSON(http.StatusOK, model.LibraryElementArrayResponse{Result: filteredElements}) @@ -326,7 +326,7 @@ func (l *LibraryElementService) filterLibraryPanelsByPermission(c *contextmodel. return filteredPanels, nil } -func toLibraryElementError(err error, message string) response.Response { +func (l *LibraryElementService) toLibraryElementError(err error, message string) response.Response { if errors.Is(err, model.ErrLibraryElementAlreadyExists) { return response.Error(http.StatusBadRequest, model.ErrLibraryElementAlreadyExists.Error(), err) } @@ -354,6 +354,8 @@ func toLibraryElementError(err error, message string) response.Response { if errors.Is(err, model.ErrLibraryElementUIDTooLong) { return response.Error(http.StatusBadRequest, model.ErrLibraryElementUIDTooLong.Error(), err) } + // Log errors that cause internal server error status code. + l.log.Error(message, "error", err) return response.ErrOrFallback(http.StatusInternalServerError, message, err) } diff --git a/pkg/services/libraryelements/libraryelements_create_test.go b/pkg/services/libraryelements/libraryelements_create_test.go index 45e7a797b5b..5a66d170e83 100644 --- a/pkg/services/libraryelements/libraryelements_create_test.go +++ b/pkg/services/libraryelements/libraryelements_create_test.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/util" ) -func TestCreateLibraryElement(t *testing.T) { +func TestIntegration_CreateLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to create a library panel that already exists, it should fail", func(t *testing.T, sc scenarioContext) { // nolint:staticcheck diff --git a/pkg/services/libraryelements/libraryelements_delete_test.go b/pkg/services/libraryelements/libraryelements_delete_test.go index 527cc1bd37d..5605313b4b6 100644 --- a/pkg/services/libraryelements/libraryelements_delete_test.go +++ b/pkg/services/libraryelements/libraryelements_delete_test.go @@ -4,15 +4,16 @@ import ( "encoding/json" "testing" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/web" - "github.com/stretchr/testify/require" ) -func TestDeleteLibraryElement(t *testing.T) { +func TestIntegration_DeleteLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to delete a library panel that does not exist, it should fail", func(t *testing.T, sc scenarioContext) { resp := sc.service.deleteHandler(sc.reqContext) diff --git a/pkg/services/libraryelements/libraryelements_get_all_test.go b/pkg/services/libraryelements/libraryelements_get_all_test.go index 501696c4791..5b57eac9de9 100644 --- a/pkg/services/libraryelements/libraryelements_get_all_test.go +++ b/pkg/services/libraryelements/libraryelements_get_all_test.go @@ -14,7 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/search/sort" ) -func TestGetAllLibraryElements(t *testing.T) { +func TestIntegration_GetAllLibraryElements(t *testing.T) { testScenario(t, "When an admin tries to get all library panels and none exists, it should return none", func(t *testing.T, sc scenarioContext) { resp := sc.service.getAllHandler(sc.reqContext) @@ -964,13 +964,14 @@ func TestGetAllLibraryElements(t *testing.T) { require.NoError(t, err) sc.reqContext.Req.Form.Add("perPage", "1") sc.reqContext.Req.Form.Add("page", "1") - sc.reqContext.Req.Form.Add("searchString", "description") + sc.reqContext.Req.Form.Add("searchString", "DeScRiPtIoN") // mixed case to test case-insensitive search. resp = sc.service.getAllHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) var result libraryElementsSearch err = json.Unmarshal(resp.Body(), &result) require.NoError(t, err) + require.Equal(t, int64(1), result.Result.TotalCount) var expected = libraryElementsSearch{ Result: libraryElementsSearchResult{ TotalCount: 1, @@ -1039,13 +1040,14 @@ func TestGetAllLibraryElements(t *testing.T) { err := sc.reqContext.Req.ParseForm() require.NoError(t, err) - sc.reqContext.Req.Form.Add("searchString", "Library Panel") + sc.reqContext.Req.Form.Add("searchString", "library PANEL") // mixed-case to test case-insensitive search. resp = sc.service.getAllHandler(sc.reqContext) require.Equal(t, 200, resp.Status()) var result libraryElementsSearch err = json.Unmarshal(resp.Body(), &result) require.NoError(t, err) + require.Equal(t, int64(2), result.Result.TotalCount) var expected = libraryElementsSearch{ Result: libraryElementsSearchResult{ TotalCount: 2, diff --git a/pkg/services/libraryelements/libraryelements_get_test.go b/pkg/services/libraryelements/libraryelements_get_test.go index 5820e2bded7..42827e3f13a 100644 --- a/pkg/services/libraryelements/libraryelements_get_test.go +++ b/pkg/services/libraryelements/libraryelements_get_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/kinds/librarypanel" @@ -13,10 +15,9 @@ import ( "github.com/grafana/grafana/pkg/services/libraryelements/model" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/web" - "github.com/stretchr/testify/require" ) -func TestGetLibraryElement(t *testing.T) { +func TestIntegration_GetLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get a library panel that does not exist, it should fail", func(t *testing.T, sc scenarioContext) { // by uid diff --git a/pkg/services/libraryelements/libraryelements_patch_test.go b/pkg/services/libraryelements/libraryelements_patch_test.go index 19cdb8c248a..3a7c962722f 100644 --- a/pkg/services/libraryelements/libraryelements_patch_test.go +++ b/pkg/services/libraryelements/libraryelements_patch_test.go @@ -8,11 +8,12 @@ import ( "github.com/grafana/grafana/pkg/util" "github.com/google/go-cmp/cmp" - "github.com/grafana/grafana/pkg/web" "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/web" ) -func TestPatchLibraryElement(t *testing.T) { +func TestIntegration_PatchLibraryElement(t *testing.T) { scenarioWithPanel(t, "When an admin tries to patch a library panel that does not exist, it should fail", func(t *testing.T, sc scenarioContext) { cmd := model.PatchLibraryElementCommand{Kind: int64(model.PanelElement), Version: 1} diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 95dc2f48dfe..faffe25225e 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -62,7 +62,7 @@ func TestMain(m *testing.M) { testsuite.Run(m) } -func TestDeleteLibraryPanelsInFolder(t *testing.T) { +func TestIntegration_DeleteLibraryPanelsInFolder(t *testing.T) { scenarioWithPanel(t, "When an admin tries to delete a folder that contains connected library elements, it should fail", func(t *testing.T, sc scenarioContext) { dashJSON := map[string]any{ @@ -137,7 +137,7 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) { }) } -func TestGetLibraryPanelConnections(t *testing.T) { +func TestIntegration_GetLibraryPanelConnections(t *testing.T) { scenarioWithPanel(t, "When an admin tries to get connections of library panel, it should succeed and return correct result", func(t *testing.T, sc scenarioContext) { dashJSON := map[string]any{ @@ -358,8 +358,8 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash service, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, features, folderPermissions, ac, + actest.FakeService{}, folderSvc, - folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, @@ -461,8 +461,7 @@ func scenarioWithPanel(t *testing.T, desc string, fn func(t *testing.T, sc scena nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashboardService, svcErr := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, - features, folderPermissions, ac, - folderSvc, fStore, + features, folderPermissions, ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), @@ -536,8 +535,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo require.NoError(t, err) dashService, dashSvcErr := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, - features, folderPermissions, ac, - folderSvc, fStore, + features, folderPermissions, ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), @@ -551,6 +549,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo SQLStore: sqlStore, folderService: folderSvc, dashboardsService: dashService, + log: log.NewNopLogger(), } // deliberate difference between signed in user and user in db to make it crystal clear diff --git a/pkg/services/libraryelements/writers.go b/pkg/services/libraryelements/writers.go index 9629b96b66a..2c83ebbf4f6 100644 --- a/pkg/services/libraryelements/writers.go +++ b/pkg/services/libraryelements/writers.go @@ -60,8 +60,11 @@ func writeTypeFilterSQL(typeFilter []string, builder *db.SQLBuilder) { func writeSearchStringSQL(query model.SearchLibraryElementsQuery, sqlStore db.DB, builder *db.SQLBuilder) { if len(strings.TrimSpace(query.SearchString)) > 0 { - builder.Write(" AND (le.name "+sqlStore.GetDialect().LikeStr()+" ?", "%"+query.SearchString+"%") - builder.Write(" OR le.description "+sqlStore.GetDialect().LikeStr()+" ?)", "%"+query.SearchString+"%") + sql, param := sqlStore.GetDialect().LikeOperator("le.name", true, query.SearchString, true) + builder.Write(" AND ("+sql, param) + + sql, param = sqlStore.GetDialect().LikeOperator("le.description", true, query.SearchString, true) + builder.Write(" OR "+sql+")", param) } } @@ -147,7 +150,7 @@ func (f *FolderFilter) writeFolderFilterSQL(includeGeneral bool, builder *db.SQL if !includeGeneral && isGeneralFolder(folderID) { continue } - params = append(params, filter) + params = append(params, folderID) } if len(params) > 0 { sql.WriteString(` AND le.folder_id IN (?` + strings.Repeat(",?", len(params)-1) + ")") diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 4a111d204a8..35910fce92b 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -739,8 +739,7 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash dashPermissionService.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) service, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, folderStore, - features, acmock.NewMockedPermissionsService(), ac, - foldertest.NewFakeService(), folder.NewFakeStore(), + features, acmock.NewMockedPermissionsService(), ac, actest.FakeService{}, foldertest.NewFakeService(), nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) @@ -838,8 +837,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo folderSvc.ExpectedFolder = &folder.Folder{ID: 1} dashService, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashStore, folderStore, - features, acmock.NewMockedPermissionsService(), ac, - folderSvc, folder.NewFakeStore(), + features, acmock.NewMockedPermissionsService(), ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 97d7ab1112b..5af7d6dbeb7 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -19,6 +19,7 @@ const ( WeightDrilldown WeightAlerting WeightAlertsAndIncidents + WeightAIAndML WeightTestingAndSynthetics WeightMonitoring WeightCloudServiceProviders diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index 37ece2ee8fe..9b482ce58ec 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -262,9 +262,6 @@ func (s *ServiceImpl) addPluginToSection(c *contextmodel.ReqContext, treeRoot *n alertsAndIncidentsChildren = append(alertsAndIncidentsChildren, alertingNode) treeRoot.RemoveSection(alertingNode) } - if appLink.Id == "plugin-page-grafana-irm-app" { - appLink.IsNew = true - } alertsAndIncidentsChildren = append(alertsAndIncidentsChildren, appLink) treeRoot.AddSection(&navtree.NavLink{ Text: "Alerts & IRM", diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index fdc345a8026..0a1682b026c 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/plugins" ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apikey" "github.com/grafana/grafana/pkg/services/authn" @@ -17,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/navtree" "github.com/grafana/grafana/pkg/services/org" + pc "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" pref "github.com/grafana/grafana/pkg/services/preference" @@ -161,6 +163,10 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere if alertingSection := s.buildAlertNavLinks(c); alertingSection != nil { treeRoot.AddSection(alertingSection) } + + if aimlSection := s.buildAIMLNavLinks(c); aimlSection != nil { + treeRoot.AddSection(aimlSection) + } } if connectionsSection := s.buildDataConnectionsNavLink(c); connectionsSection != nil { @@ -416,6 +422,80 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt return dashboardChildNavs } +func (s *ServiceImpl) buildAIMLNavLinks(c *contextmodel.ReqContext) *navtree.NavLink { + hasAccess := ac.HasAccess(s.accessControl, c) + + pss, err := s.pluginSettings.GetPluginSettings(c.Req.Context(), &pluginsettings.GetArgs{OrgID: c.GetOrgID()}) + if err != nil { + s.log.Error("Failed to get plugin settings", "error", err) + return nil + } + + // Check if ML plugin is enabled + isMLPluginEnabled := false + for _, plugin := range s.pluginStore.Plugins(c.Req.Context(), plugins.TypeApp) { + if plugin.ID == "grafana-ml-app" { + // Check if plugin is enabled in settings + if plugin.AutoEnabled { + isMLPluginEnabled = true + break + } + for _, ps := range pss { + if ps.PluginID == plugin.ID && ps.Enabled { + isMLPluginEnabled = true + break + } + } + break + } + } + + // Return nil if plugin is not enabled + if !isMLPluginEnabled { + return nil + } + + // Check if user has access to the plugin + if !hasAccess(ac.EvalPermission(pc.ActionAppAccess, "grafana-ml-app")) { + return nil + } + + var aimlChildNavs []*navtree.NavLink + + aimlChildNavs = append(aimlChildNavs, &navtree.NavLink{ + Text: "Metric forecasting", + SubTitle: "Create a forecast", + Id: "ai-ml-metric-forecast", + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/metric-forecast", + }) + + aimlChildNavs = append(aimlChildNavs, &navtree.NavLink{ + Text: "Outlier detection", + SubTitle: "Create an outlier detector", + Id: "ai-ml-outlier-detection", + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/outlier-detector", + }) + + aimlChildNavs = append(aimlChildNavs, &navtree.NavLink{ + Text: "Sift investigations", + SubTitle: "View and create investigations", + Id: "ai-ml-sift-investigations", + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/investigations", + }) + + var aimlNav = navtree.NavLink{ + Text: "AI & Machine Learning", + SubTitle: "Explore AI and machine learning features", + Id: "ai-ml-home", + Icon: "gf-ml-alt", + Children: aimlChildNavs, + SortWeight: navtree.WeightAIAndML, + Url: s.cfg.AppSubURL + "/a/grafana-ml-app/home", + } + + return &aimlNav +} + func (s *ServiceImpl) buildAlertNavLinks(c *contextmodel.ReqContext) *navtree.NavLink { hasAccess := ac.HasAccess(s.accessControl, c) var alertChildNavs []*navtree.NavLink diff --git a/pkg/services/ngalert/eval/eval_test.go b/pkg/services/ngalert/eval/eval_test.go index 80da967dbb8..9f82f958b03 100644 --- a/pkg/services/ngalert/eval/eval_test.go +++ b/pkg/services/ngalert/eval/eval_test.go @@ -710,7 +710,7 @@ func TestCreate_HysteresisCommand(t *testing.T) { cache: cacheService, pluginsStore: store, }) - evaluator := NewEvaluatorFactory(setting.UnifiedAlertingSettings{}, cacheService, expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil, featuremgmt.WithFeatures(featuremgmt.FlagRecoveryThreshold), nil, tracing.InitializeTracerForTest())) + evaluator := NewEvaluatorFactory(setting.UnifiedAlertingSettings{}, cacheService, expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil, featuremgmt.WithFeatures(), nil, tracing.InitializeTracerForTest())) evalCtx := NewContextWithPreviousResults(context.Background(), u, testCase.reader) eval, err := evaluator.Create(evalCtx, condition) diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index fb5ecad3970..3f95cfd8b62 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -153,6 +153,10 @@ const ( // MigratedMessageAnnotation is created during legacy migration to store the migrated alert message. MigratedMessageAnnotation = "message" + // ConvertedPrometheusRuleLabel is a label that indicates that the alert rule was converted from a Prometheus rule + // using the import APIs. + ConvertedPrometheusRuleLabel = "__converted_prometheus_rule__" + // AutogeneratedRouteLabel a label name used to distinguish alerts that are supposed to be handled by the autogenerated policy. Only expected value is `true`. AutogeneratedRouteLabel = "__grafana_autogenerated__" // AutogeneratedRouteReceiverNameLabel a label name that contains the name of the receiver that should be used to send notifications for the alert. diff --git a/pkg/services/ngalert/prom/convert.go b/pkg/services/ngalert/prom/convert.go index df0adedbff5..e4ab9ce0a60 100644 --- a/pkg/services/ngalert/prom/convert.go +++ b/pkg/services/ngalert/prom/convert.go @@ -231,10 +231,13 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom title = rule.Alert } - labels := make(map[string]string, len(rule.Labels)+len(promGroup.Labels)) + labels := make(map[string]string, len(rule.Labels)+len(promGroup.Labels)+1) maps.Copy(labels, promGroup.Labels) maps.Copy(labels, rule.Labels) + // Add a special label to indicate that this rule was converted from a Prometheus rule. + labels[models.ConvertedPrometheusRuleLabel] = "true" + originalRuleDefinition, err := yaml.Marshal(rule) if err != nil { return models.AlertRule{}, fmt.Errorf("failed to marshal original rule definition: %w", err) diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index 9fed80776e7..1d4668d2b27 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -322,6 +322,7 @@ func TestPrometheusRulesToGrafana(t *testing.T) { expectedLabels := make(map[string]string, len(promRule.Labels)+len(tc.promGroup.Labels)) maps.Copy(expectedLabels, tc.promGroup.Labels) maps.Copy(expectedLabels, promRule.Labels) + expectedLabels = withInternalLabel(expectedLabels) uidData := fmt.Sprintf("%d|%s|%s|%d", tc.orgID, tc.namespace, tc.promGroup.Name, j) u := uuid.NewSHA1(uuid.NameSpaceOID, []byte(uidData)) @@ -550,11 +551,11 @@ func TestPrometheusRulesToGrafana_GroupLabels(t *testing.T) { // Check that the labels are merged and the rule label takes precedence require.Equal( t, - map[string]string{ + withInternalLabel(map[string]string{ "group_label": "group_value", "rule_label": "rule_value", "common_label": "rule_value", - }, + }), grafanaGroup.Rules[0].Labels, ) }) @@ -586,11 +587,11 @@ func TestPrometheusRulesToGrafana_GroupLabels(t *testing.T) { // Check that the labels are merged and the rule label takes precedence require.Equal( t, - map[string]string{ + withInternalLabel(map[string]string{ "group_label": "group_value", "rule_label": "rule_value", "common_label": "rule_value", - }, + }), grafanaGroup.Rules[0].Labels, ) }) @@ -614,8 +615,7 @@ func TestPrometheusRulesToGrafana_GroupLabels(t *testing.T) { grafanaGroup, err := converter.PrometheusRulesToGrafana(1, "namespace", promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) - - require.Equal(t, promGroup.Labels, grafanaGroup.Rules[0].Labels) + require.Equal(t, withInternalLabel(promGroup.Labels), grafanaGroup.Rules[0].Labels) }) t.Run("rule and group with nil labels", func(t *testing.T) { @@ -633,7 +633,7 @@ func TestPrometheusRulesToGrafana_GroupLabels(t *testing.T) { grafanaGroup, err := converter.PrometheusRulesToGrafana(1, "namespace", promGroup) require.NoError(t, err) require.Len(t, grafanaGroup.Rules, 1) - require.Empty(t, grafanaGroup.Rules[0].Labels) + require.Equal(t, withInternalLabel(map[string]string{}), grafanaGroup.Rules[0].Labels) }) } @@ -847,3 +847,12 @@ func TestQueryModelContainsRequiredParameters(t *testing.T) { require.True(t, isNumber, "maxDataPoints should be a number") } } + +func withInternalLabel(l map[string]string) map[string]string { + result := map[string]string{ + models.ConvertedPrometheusRuleLabel: "true", + } + maps.Copy(result, l) + + return result +} diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index 2d9f1ad4af7..8b632e3942f 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -82,6 +82,7 @@ func TestProcessTicks(t *testing.T) { RecordingRulesCfg: rrSet, Tracer: testTracer, Log: log.New("ngalert.scheduler"), + FeatureToggles: featuremgmt.WithFeatures(), } managerCfg := state.ManagerCfg{ Metrics: testMetrics.GetStateMetrics(), diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 433b417c545..de6a9974ee4 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -453,14 +453,7 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, user *ngmodels.UserUID, if _, err := sess.Insert(&ruleVersions); err != nil { return fmt.Errorf("failed to create new rule versions: %w", err) } - - for _, rule := range ruleVersions { - // delete old versions of alert rule - _, err = st.deleteOldAlertRuleVersions(ctx, rule.RuleUID, rule.RuleOrgID, st.Cfg.RuleVersionRecordLimit) - if err != nil { - st.Logger.Warn("Failed to delete old alert rule versions", "org", rule.RuleOrgID, "rule", rule.RuleUID, "error", err) - } - } + st.deleteOldAlertRuleVersions(ctx, sess, ruleVersions) } if len(keys) > 0 { _ = st.Bus.Publish(ctx, &RuleChangeEvent{ @@ -471,50 +464,31 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, user *ngmodels.UserUID, }) } -func (st DBstore) deleteOldAlertRuleVersions(ctx context.Context, ruleUID string, orgID int64, limit int) (int64, error) { - if limit < 0 { - return 0, fmt.Errorf("failed to delete old alert rule versions: limit is set to '%d' but needs to be > 0", limit) +func (st DBstore) deleteOldAlertRuleVersions(ctx context.Context, sess *db.Session, versions []alertRuleVersion) { + if st.Cfg.RuleVersionRecordLimit < 1 { + return } - - if limit < 1 { - return 0, nil - } - - var affectedRows int64 - err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error { - highest := &alertRuleVersion{} - ok, err := sess.Table("alert_rule_version").Desc("id").Where("rule_org_id = ?", orgID).Where("rule_uid = ?", ruleUID).Limit(1, limit).Get(highest) - if err != nil { - return err + logger := st.Logger.FromContext(ctx) + for _, rv := range versions { + deleteTo := rv.Version - int64(st.Cfg.RuleVersionRecordLimit) + // if the last version is less that retention, do nothing + if deleteTo <= 1 { + continue } - if !ok { - // No alert rule versions past the limit exist. Nothing to clean up. - affectedRows = 0 - return nil - } - - res, err := sess.Exec(` - DELETE FROM - alert_rule_version - WHERE - rule_org_id = ? AND rule_uid = ? - AND - id <= ? - `, orgID, ruleUID, highest.ID) + logger := logger.New("org_id", rv.RuleOrgID, "rule_uid", rv.RuleUID, "version", rv.Version, "limit", st.Cfg.RulesPerRuleGroupLimit) + res, err := sess.Exec(`DELETE FROM alert_rule_version WHERE rule_guid = ? AND version <= ?`, rv.RuleGUID, deleteTo) if err != nil { - return err + logger.Error("Failed to delete old alert rule versions", "error", err) + return } rows, err := res.RowsAffected() if err != nil { - return err + rows = -1 } - affectedRows = rows - if affectedRows > 0 { - st.Logger.Info("Deleted old alert_rule_version(s)", "org", orgID, "limit", limit, "delete_count", affectedRows) + if rows != 0 { + logger.Info("Deleted old alert_rule_version(s)", "deleted", rows) } - return nil - }) - return affectedRows, err + } } // preventIntermediateUniqueConstraintViolations prevents unique constraint violations caused by an intermediate update. diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index efe5772934d..2c2ffd83ff8 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -1625,23 +1625,20 @@ func TestIntegration_AlertRuleVersionsCleanup(t *testing.T) { t.Skip("skipping integration test") } usr := models.UserUID("test") - cfg := setting.NewCfg() - cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{ + cfg := setting.UnifiedAlertingSettings{ BaseInterval: time.Duration(rand.Int63n(100)+1) * time.Second, } sqlStore := db.InitTestDB(t) - folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + folderService := setupFolderService(t, sqlStore, setting.NewCfg(), featuremgmt.WithFeatures()) b := &fakeBus{} - store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, b) + generator := models.RuleGen - generator = generator.With(generator.WithIntervalMatching(store.Cfg.BaseInterval), generator.WithUniqueOrgID()) + generator = generator.With(generator.WithIntervalMatching(cfg.BaseInterval), generator.WithUniqueOrgID()) t.Run("when calling the cleanup with fewer records than the limit all records should stay", func(t *testing.T) { - alertingCfgSnapshot := cfg.UnifiedAlerting - defer func() { - cfg.UnifiedAlerting = alertingCfgSnapshot - }() - cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{BaseInterval: alertingCfgSnapshot.BaseInterval, RuleVersionRecordLimit: 10} + cfg := setting.UnifiedAlertingSettings{BaseInterval: cfg.BaseInterval, RuleVersionRecordLimit: 10} + store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg, b) + rule := createRule(t, store, generator) firstNewRule := models.CopyRule(rule) firstNewRule.Title = util.GenerateShortUID() @@ -1685,43 +1682,27 @@ func TestIntegration_AlertRuleVersionsCleanup(t *testing.T) { }) t.Run("only oldest records surpassing the limit should be deleted", func(t *testing.T) { - alertingCfgSnapshot := cfg.UnifiedAlerting - defer func() { - cfg.UnifiedAlerting = alertingCfgSnapshot - }() - cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{BaseInterval: alertingCfgSnapshot.BaseInterval, RuleVersionRecordLimit: 1} + cfg := setting.UnifiedAlertingSettings{BaseInterval: cfg.BaseInterval, RuleVersionRecordLimit: 2} + store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg, b) rule := createRule(t, store, generator) - oldRule := models.CopyRule(rule) - oldRule.Title = "old-record" - err := store.UpdateAlertRules(context.Background(), &usr, []models.UpdateRule{{ - Existing: rule, - New: *oldRule, - }}) // first entry in `rule_version_history` table happens here - require.NoError(t, err) - rule.Version = rule.Version + 1 - middleRule := models.CopyRule(rule) - middleRule.Title = "middle-record" - err = store.UpdateAlertRules(context.Background(), &usr, []models.UpdateRule{{ - Existing: rule, - New: *middleRule, - }}) // second entry in `rule_version_history` table happens here - require.NoError(t, err) + for i := 0; i < 4; i++ { + r, err := store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: rule.UID}) + require.NoError(t, err) + rn := models.CopyRule(r) + rn.Title = util.GenerateShortUID() + err = store.UpdateAlertRules(context.Background(), &models.AlertingUserUID, []models.UpdateRule{ + { + Existing: r, + New: *rn, + }, + }) + require.NoError(t, err) + } - rule.Version = rule.Version + 1 - newerRule := models.CopyRule(rule) - newerRule.Title = "newer-record" - err = store.UpdateAlertRules(context.Background(), &usr, []models.UpdateRule{{ - Existing: rule, - New: *newerRule, - }}) // second entry in `rule_version_history` table happens here + rule, err := store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: rule.UID}) require.NoError(t, err) - // only the `old-record` should be deleted since limit is set to 1 and there are total 2 records - rowsAffected, err := store.deleteOldAlertRuleVersions(context.Background(), rule.UID, rule.OrgID, 1) - require.NoError(t, err) - require.Equal(t, int64(2), rowsAffected) - err = sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { var alertRuleVersions []*alertRuleVersion err := sess.Table(alertRuleVersion{}).Desc("id").Where("rule_org_id = ? and rule_uid = ?", rule.OrgID, rule.UID).Find(&alertRuleVersions) @@ -1729,22 +1710,13 @@ func TestIntegration_AlertRuleVersionsCleanup(t *testing.T) { return err } require.NoError(t, err) - assert.Len(t, alertRuleVersions, 1) - assert.Equal(t, "newer-record", alertRuleVersions[0].Title) + assert.Len(t, alertRuleVersions, 2) + assert.Equal(t, rule.Title, alertRuleVersions[0].Title) + assert.Equal(t, rule.Version, alertRuleVersions[0].Version) return err }) require.NoError(t, err) }) - - t.Run("limit set to 0 should not fail", func(t *testing.T) { - count, err := store.deleteOldAlertRuleVersions(context.Background(), "", 1, 0) - require.NoError(t, err) - require.Equal(t, int64(0), count) - }) - t.Run("limit set to negative should fail", func(t *testing.T) { - _, err := store.deleteOldAlertRuleVersions(context.Background(), "", 1, -1) - require.Error(t, err) - }) } func TestIntegration_ListAlertRules(t *testing.T) { diff --git a/pkg/services/ngalert/testutil/testutil.go b/pkg/services/ngalert/testutil/testutil.go index e59a676126c..4618de2ea9d 100644 --- a/pkg/services/ngalert/testutil/testutil.go +++ b/pkg/services/ngalert/testutil/testutil.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/apiserver/client" @@ -67,7 +68,7 @@ func SetupDashboardService(tb testing.TB, sqlStore db.DB, fs *folderimpl.Dashboa dashboardService, err := dashboardservice.ProvideDashboardServiceImpl( cfg, dashboardStore, fs, features, folderPermissions, ac, - foldertest.NewFakeService(), folder.NewFakeStore(), + &actest.FakeService{}, foldertest.NewFakeService(), nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), diff --git a/pkg/services/plugindashboards/service/dashboard_updater.go b/pkg/services/plugindashboards/service/dashboard_updater.go index efcc49c0545..14b67a2589c 100644 --- a/pkg/services/plugindashboards/service/dashboard_updater.go +++ b/pkg/services/plugindashboards/service/dashboard_updater.go @@ -139,6 +139,7 @@ func (du *DashboardUpdater) syncPluginDashboards(ctx context.Context, plugin plu func (du *DashboardUpdater) handlePluginStateChanged(ctx context.Context, event *pluginsettings.PluginStateChangedEvent) error { du.logger.Info("Plugin state changed", "pluginId", event.PluginId, "enabled", event.Enabled) + ctx, _ = identity.WithServiceIdentity(ctx, event.OrgId) if event.Enabled { p, exists := du.pluginStore.Plugin(ctx, event.PluginId) diff --git a/pkg/services/publicdashboards/api/query_test.go b/pkg/services/publicdashboards/api/query_test.go index e0215125c4b..905e7e2cb4f 100644 --- a/pkg/services/publicdashboards/api/query_test.go +++ b/pkg/services/publicdashboards/api/query_test.go @@ -26,6 +26,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/serverlock" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/apiserver/client" @@ -36,7 +37,6 @@ import ( "github.com/grafana/grafana/pkg/services/datasources/guardian" datasourcesService "github.com/grafana/grafana/pkg/services/datasources/service" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/licensing/licensingtest" @@ -324,14 +324,14 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T) // create public dashboard store := publicdashboardsStore.ProvideStore(db, cfg, featuremgmt.WithFeatures()) cfg.PublicDashboardsEnabled = true - ac := acmock.New() + ac := actest.FakeAccessControl{} ws := publicdashboardsService.ProvideServiceWrapper(store) folderStore := folderimpl.ProvideDashboardFolderStore(db) dashPermissionService := acmock.NewMockedPermissionsService() dashService, err := service.ProvideDashboardServiceImpl( cfg, dashboardStoreService, folderStore, - featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), ac, - foldertest.NewFakeService(), folder.NewFakeStore(), nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, + featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), ac, actest.FakeService{}, + foldertest.NewFakeService(), nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(db, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore(), diff --git a/pkg/services/publicdashboards/service/service_test.go b/pkg/services/publicdashboards/service/service_test.go index ac3db3d394c..9ae9d27bd16 100644 --- a/pkg/services/publicdashboards/service/service_test.go +++ b/pkg/services/publicdashboards/service/service_test.go @@ -1403,7 +1403,7 @@ func TestPublicDashboardServiceImpl_ListPublicDashboards(t *testing.T) { fStore, ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, testDB, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) - dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + dashboardService, err := dashsvc.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), folderPermissions, ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotatest.New(false, nil), nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(testDB, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) require.NoError(t, err) diff --git a/pkg/services/quota/quotaimpl/quota_test.go b/pkg/services/quota/quotaimpl/quota_test.go index 571e47f7aa1..6fca4b44c0a 100644 --- a/pkg/services/quota/quotaimpl/quota_test.go +++ b/pkg/services/quota/quotaimpl/quota_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" + "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/apikey" @@ -504,7 +505,7 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe fStore, acmock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, nil, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig) dashService, err := dashService.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), - ac, folderSvc, fStore, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), + ac, actest.FakeService{}, folderSvc, nil, client.MockTestRestConfig{}, nil, quotaService, nil, nil, nil, dualwrite.ProvideTestService(), sort.ProvideService(), serverlock.ProvideService(sqlStore, tracing.InitializeTracerForTest()), kvstore.NewFakeKVStore()) require.NoError(t, err) diff --git a/pkg/services/rendering/http_mode.go b/pkg/services/rendering/http_mode.go index 705f9672240..ea15eb389e5 100644 --- a/pkg/services/rendering/http_mode.go +++ b/pkg/services/rendering/http_mode.go @@ -31,6 +31,7 @@ var netClient = &http.Client{ } const authTokenHeader = "X-Auth-Token" //#nosec G101 -- This is a false positive +const rateLimiterHeader = "X-Tenant-ID" var ( remoteVersionFetchInterval time.Duration = time.Second * 15 @@ -160,6 +161,7 @@ func (rs *RenderingService) doRequest(ctx context.Context, u *url.URL, headers m } req.Header.Set(authTokenHeader, rs.Cfg.RendererAuthToken) + req.Header.Set(rateLimiterHeader, rs.domain) req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", rs.Cfg.BuildVersion)) for k, v := range headers { req.Header[k] = v @@ -180,6 +182,10 @@ func (rs *RenderingService) doRequest(ctx context.Context, u *url.URL, headers m return nil, fmt.Errorf("failed to send request to remote rendering service: %w", err) } + if resp.StatusCode == http.StatusTooManyRequests { + return nil, ErrTooManyRequests + } + return resp, nil } diff --git a/pkg/services/rendering/interface.go b/pkg/services/rendering/interface.go index 9b5b2f17cdc..a54d2b92560 100644 --- a/pkg/services/rendering/interface.go +++ b/pkg/services/rendering/interface.go @@ -14,6 +14,7 @@ var ErrTimeout = errors.New("timeout error - you can set timeout in seconds with var ErrConcurrentLimitReached = errors.New("rendering concurrent limit reached") var ErrRenderUnavailable = errors.New("rendering plugin not available") var ErrServerTimeout = errutil.NewBase(errutil.StatusUnknown, "rendering.serverTimeout", errutil.WithPublicMessage("error trying to connect to image-renderer service")) +var ErrTooManyRequests = errutil.NewBase(errutil.StatusTooManyRequests, "rendering.tooManyRequests", errutil.WithPublicMessage("trying to send too many requests to image-renderer service")) type RenderType string diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 4a77bfdbf04..bafdcc1b2dc 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -547,6 +547,11 @@ type Cfg struct { IndexMaxBatchSize int IndexFileThreshold int IndexMinCount int + EnableSharding bool + MemberlistBindAddr string + MemberlistAdvertiseAddr string + MemberlistJoinMember string + InstanceID string SprinklesApiServer string SprinklesApiServerPageLimit int CACertPath string @@ -556,6 +561,7 @@ type Cfg struct { type UnifiedStorageConfig struct { DualWriterMode rest.DualWriterMode DualWriterPeriodicDataSyncJobEnabled bool + DualWriterMigrationDataSyncDisabled bool // DataSyncerInterval defines how often the data syncer should run for a resource on the grafana instance. DataSyncerInterval time.Duration // DataSyncerRecordsLimit defines how many records will be processed at max during a sync invocation. diff --git a/pkg/setting/setting_unified_storage.go b/pkg/setting/setting_unified_storage.go index 5bc9fcf2ec1..45ddfb4b3f9 100644 --- a/pkg/setting/setting_unified_storage.go +++ b/pkg/setting/setting_unified_storage.go @@ -30,6 +30,9 @@ func (cfg *Cfg) setUnifiedStorageConfig() { // parse dualWriter periodic data syncer config dualWriterPeriodicDataSyncJobEnabled := section.Key("dualWriterPeriodicDataSyncJobEnabled").MustBool(false) + // parse dualWriter migration data sync disabled from resource section + dualWriterMigrationDataSyncDisabled := section.Key("dualWriterMigrationDataSyncDisabled").MustBool(false) + // parse dataSyncerRecordsLimit from resource section dataSyncerRecordsLimit := section.Key("dataSyncerRecordsLimit").MustInt(1000) @@ -39,6 +42,7 @@ func (cfg *Cfg) setUnifiedStorageConfig() { storageConfig[resourceName] = UnifiedStorageConfig{ DualWriterMode: rest.DualWriterMode(dualWriterMode), DualWriterPeriodicDataSyncJobEnabled: dualWriterPeriodicDataSyncJobEnabled, + DualWriterMigrationDataSyncDisabled: dualWriterMigrationDataSyncDisabled, DataSyncerRecordsLimit: dataSyncerRecordsLimit, DataSyncerInterval: dataSyncerInterval, } @@ -50,6 +54,11 @@ func (cfg *Cfg) setUnifiedStorageConfig() { cfg.IndexPath = section.Key("index_path").String() cfg.IndexWorkers = section.Key("index_workers").MustInt(10) cfg.IndexMaxBatchSize = section.Key("index_max_batch_size").MustInt(100) + cfg.EnableSharding = section.Key("enable_sharding").MustBool(false) + cfg.MemberlistBindAddr = section.Key("memberlist_bind_addr").String() + cfg.MemberlistAdvertiseAddr = section.Key("memberlist_advertise_addr").String() + cfg.MemberlistJoinMember = section.Key("memberlist_join_member").String() + cfg.InstanceID = section.Key("instance_id").String() cfg.IndexFileThreshold = section.Key("index_file_threshold").MustInt(10) cfg.IndexMinCount = section.Key("index_min_count").MustInt(1) cfg.SprinklesApiServer = section.Key("sprinkles_api_server").String() diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index 9b10cca536e..e6dcc828956 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -49,7 +49,9 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/apache/arrow-go/v18 v18.2.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect @@ -107,6 +109,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.6.9 // indirect @@ -130,9 +133,20 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/hashicorp/consul/api v1.30.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v1.1.5 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/memberlist v0.5.0 // indirect + github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jhump/protoreflect v1.15.1 // indirect @@ -149,6 +163,9 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/miekg/dns v1.1.62 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -172,6 +189,7 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/objx v0.5.2 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 61fae46133a..34fe320d513 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -58,12 +58,14 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/apache/arrow-go/v18 v18.2.0 h1:QhWqpgZMKfWOniGPhbUxrHohWnooGURqL2R2Gg4SO1Q= github.com/apache/arrow-go/v18 v18.2.0/go.mod h1:Ic/01WSwGJWRrdAZcxjBZ5hbApNJ28K96jGYaxzzGUc= github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E= @@ -309,12 +311,26 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/hashicorp/consul/api v1.30.0 h1:ArHVMMILb1nQv8vZSGIwwQd2gtc+oSQZ6CalyiyH2XQ= +github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -379,11 +395,14 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -413,6 +432,7 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -455,6 +475,7 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= diff --git a/pkg/storage/unified/client.go b/pkg/storage/unified/client.go index d5b785b542f..2df1f962f3c 100644 --- a/pkg/storage/unified/client.go +++ b/pkg/storage/unified/client.go @@ -158,7 +158,7 @@ func newClient(opts options.StorageOptions, if err != nil { return nil, err } - server, err := sql.NewResourceServer(db, cfg, tracer, reg, authzc, searchOptions, storageMetrics, indexMetrics, features) + server, err := sql.NewResourceServer(db, cfg, tracer, reg, authzc, searchOptions, storageMetrics, indexMetrics, features, nil) if err != nil { return nil, err } diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 2073290530d..8b12c1164c9 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -57,7 +57,9 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/apache/arrow-go/v18 v18.2.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/aws/aws-sdk-go-v2 v1.36.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect @@ -86,6 +88,8 @@ require ( github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476 // indirect github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/elazarl/goproxy v1.7.2 // indirect @@ -110,6 +114,8 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.3 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect @@ -123,8 +129,20 @@ require ( github.com/grafana/otel-profiling-go v0.5.1 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect + github.com/hashicorp/consul/api v1.30.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-msgpack v1.1.5 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru v1.0.2 // indirect + github.com/hashicorp/memberlist v0.5.0 // indirect + github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/jhump/protoreflect v1.15.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -141,6 +159,9 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/miekg/dns v1.1.62 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -164,6 +185,7 @@ require ( github.com/redis/go-redis/v9 v9.7.3 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect @@ -173,6 +195,9 @@ require ( github.com/urfave/cli v1.22.16 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect + go.etcd.io/etcd/api/v3 v3.5.16 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect + go.etcd.io/etcd/client/v3 v3.5.16 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect @@ -188,6 +213,8 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.37.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/mod v0.24.0 // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index acaf9f37dbc..bc64d461361 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -51,6 +51,7 @@ github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2 github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= @@ -62,12 +63,24 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/apache/arrow-go/v18 v18.2.0 h1:QhWqpgZMKfWOniGPhbUxrHohWnooGURqL2R2Gg4SO1Q= github.com/apache/arrow-go/v18 v18.2.0/go.mod h1:Ic/01WSwGJWRrdAZcxjBZ5hbApNJ28K96jGYaxzzGUc= github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.36.1 h1:iTDl5U6oAhkNPba0e1t1hrwAo02ZMqbrGq4k5JBWM5E= @@ -108,19 +121,25 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.33.12 h1:fqg6c1KVrc3SYWma/egWue5rKI4 github.com/aws/aws-sdk-go-v2/service/sts v1.33.12/go.mod h1:7Yn+p66q/jt38qMoVfNvjbm3D89mGBnkwDcijgtih8w= github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476 h1:VnjHsRXCRti7Av7E+j4DCha3kf68echfDzQ+wD11SBU= github.com/chromedp/cdproto v0.0.0-20240810084448-b931b754e476/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -128,6 +147,10 @@ github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySe github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= @@ -158,6 +181,8 @@ github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJP github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= @@ -176,8 +201,12 @@ github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaE github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= github.com/go-jose/go-jose/v3 v3.0.4/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -194,13 +223,16 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.4.1 h1:1Yx4Myt7BxzvUr5ldGSbwYiZG6t9wGBZ+8/fX3Wvtq0= github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg= @@ -214,6 +246,7 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8J github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -228,6 +261,9 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= @@ -291,12 +327,54 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1 h1:KcFzXwzM/kGhIRHvc8jdix github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.1/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/hashicorp/consul/api v1.30.0 h1:ArHVMMILb1nQv8vZSGIwwQd2gtc+oSQZ6CalyiyH2XQ= +github.com/hashicorp/consul/api v1.30.0/go.mod h1:B2uGchvaXVW2JhFoS8nqTxMD5PBykr4ebY4JWHTTeLM= +github.com/hashicorp/consul/sdk v0.16.1 h1:V8TxTnImoPD5cj0U9Spl0TUxcytjcbbJeADFF07KdHg= +github.com/hashicorp/consul/sdk v0.16.1/go.mod h1:fSXvwxB2hmh1FMZCNl6PwX0Q/1wdWtHJcZ7Ea5tns0s= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= +github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= +github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= @@ -311,6 +389,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= @@ -318,6 +398,7 @@ github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:W github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= @@ -329,6 +410,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -346,10 +429,16 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -357,23 +446,37 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= +github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= @@ -389,6 +492,9 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -399,6 +505,8 @@ github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= @@ -406,13 +514,26 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA= @@ -427,7 +548,12 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= @@ -436,11 +562,14 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -451,6 +580,7 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= @@ -476,6 +606,12 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= +go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= +go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= +go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= +go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= +go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -518,10 +654,16 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= gocloud.dev v0.40.0 h1:f8LgP+4WDqOG/RXoUcyLpeIAGOcAbZrZbDQCUee10ng= gocloud.dev v0.40.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -556,15 +698,19 @@ golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -577,31 +723,45 @@ golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -622,6 +782,7 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -637,7 +798,9 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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= @@ -696,6 +859,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -705,7 +869,10 @@ gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBl gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index 64b5a20a22e..b31d90558d7 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "hash/fnv" "log/slog" "net/http" "sync" @@ -13,12 +14,16 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" + "google.golang.org/grpc" + "google.golang.org/grpc/health/grpc_health_v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" claims "github.com/grafana/authlib/types" - + "github.com/grafana/dskit/ring" + ringclient "github.com/grafana/dskit/ring/client" + userutils "github.com/grafana/dskit/user" "github.com/grafana/grafana/pkg/apimachinery/utils" ) @@ -190,6 +195,8 @@ type ResourceServerOptions struct { storageMetrics *StorageMetrics IndexMetrics *BleveIndexMetrics + + Distributor *Distributor } func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { @@ -255,6 +262,12 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { cancel: cancel, storageMetrics: opts.storageMetrics, indexMetrics: opts.IndexMetrics, + reg: opts.Reg, + } + + if opts.Distributor != nil { + s.shardingEnabled = true + s.distributor = *opts.Distributor } if opts.Search.Resources != nil { @@ -299,6 +312,34 @@ type server struct { // init checking once sync.Once initErr error + + shardingEnabled bool + distributor Distributor + reg prometheus.Registerer +} + +type Distributor struct { + ClientPool *ringclient.Pool + Ring *ring.Ring + Lifecycler *ring.BasicLifecycler +} + +type RingClient struct { + Client ResourceClient + grpc_health_v1.HealthClient + Conn *grpc.ClientConn +} + +func (c *RingClient) Close() error { + return c.Conn.Close() +} + +func (c *RingClient) String() string { + return c.RemoteAddress() +} + +func (c *RingClient) RemoteAddress() string { + return c.Conn.Target() } // Init implements ResourceServer. @@ -329,6 +370,39 @@ func (s *server) Init(ctx context.Context) error { return s.initErr } +var ringOp = ring.NewOp([]ring.InstanceState{ring.ACTIVE}, func(s ring.InstanceState) bool { + return s != ring.ACTIVE +}) + +func (s *server) getClientToDistributeRequest(namespace string) *RingClient { + ringHasher := fnv.New32a() + _, err := ringHasher.Write([]byte(namespace)) + if err != nil { + s.log.Error("Error hashing namespace. Will not distribute request", "err", err) + return nil + } + + rs, err := s.distributor.Ring.Get(ringHasher.Sum32(), ringOp, nil, nil, nil) + + if err != nil { + s.log.Error("Error getting replication set. Will not distribute request", "err", err) + return nil + } + + if rs.Instances[0].Id != s.distributor.Lifecycler.GetInstanceID() { + s.log.Info("distributing request", "instanceId", rs.Instances[0].Id) + + ins, err := s.distributor.ClientPool.GetClientForInstance(rs.Instances[0]) + if err != nil { + s.log.Error("Error getting client. Will not distribute request", "err", err) + return nil + } + return ins.(*RingClient) + } + + return nil +} + func (s *server) Stop(ctx context.Context) error { s.initErr = fmt.Errorf("service is stopping") @@ -1029,6 +1103,14 @@ func (s *server) Search(ctx context.Context, req *ResourceSearchRequest) (*Resou if s.search == nil { return nil, fmt.Errorf("search index not configured") } + + if s.shardingEnabled { + client := s.getClientToDistributeRequest(req.Options.Key.Namespace) + if client != nil { + return client.Client.Search(userutils.InjectOrgID(ctx, "1"), req) + } + } + return s.search.Search(ctx, req) } @@ -1037,6 +1119,14 @@ func (s *server) GetStats(ctx context.Context, req *ResourceStatsRequest) (*Reso if err := s.Init(ctx); err != nil { return nil, err } + + if s.shardingEnabled { + client := s.getClientToDistributeRequest(req.Namespace) + if client != nil { + return client.Client.GetStats(userutils.InjectOrgID(ctx, "1"), req) + } + } + if s.search == nil { // If the backend implements "GetStats", we can use it srv, ok := s.backend.(ResourceIndexServer) diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go index e5e1e845a9f..31e84f5a048 100644 --- a/pkg/storage/unified/sql/server.go +++ b/pkg/storage/unified/sql/server.go @@ -21,7 +21,7 @@ import ( func NewResourceServer(db infraDB.DB, cfg *setting.Cfg, tracer trace.Tracer, reg prometheus.Registerer, ac types.AccessClient, searchOptions resource.SearchOptions, storageMetrics *resource.StorageMetrics, - indexMetrics *resource.BleveIndexMetrics, features featuremgmt.FeatureToggles) (resource.ResourceServer, error) { + indexMetrics *resource.BleveIndexMetrics, features featuremgmt.FeatureToggles, distributor *resource.Distributor) (resource.ResourceServer, error) { apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver") opts := resource.ResourceServerOptions{ Tracer: tracer, @@ -68,6 +68,7 @@ func NewResourceServer(db infraDB.DB, cfg *setting.Cfg, opts.Lifecycle = store opts.Search = searchOptions opts.IndexMetrics = indexMetrics + opts.Distributor = distributor rs, err := resource.NewResourceServer(opts) if err != nil { diff --git a/pkg/storage/unified/sql/service.go b/pkg/storage/unified/sql/service.go index 1c25af788c0..764fa046f99 100644 --- a/pkg/storage/unified/sql/service.go +++ b/pkg/storage/unified/sql/service.go @@ -60,6 +60,8 @@ type service struct { indexMetrics *resource.BleveIndexMetrics docBuilders resource.DocumentBuilderSupplier + + distributor *resource.Distributor } func ProvideUnifiedStorageGrpcService( @@ -71,6 +73,7 @@ func ProvideUnifiedStorageGrpcService( docBuilders resource.DocumentBuilderSupplier, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, + distributor *resource.Distributor, ) (UnifiedStorageGrpcService, error) { tracer := otel.Tracer("unified-storage") @@ -98,6 +101,7 @@ func ProvideUnifiedStorageGrpcService( docBuilders: docBuilders, storageMetrics: storageMetrics, indexMetrics: indexMetrics, + distributor: distributor, } // This will be used when running as a dskit service @@ -117,7 +121,7 @@ func (s *service) start(ctx context.Context) error { return err } - server, err := NewResourceServer(s.db, s.cfg, s.tracing, s.reg, authzClient, searchOptions, s.storageMetrics, s.indexMetrics, s.features) + server, err := NewResourceServer(s.db, s.cfg, s.tracing, s.reg, authzClient, searchOptions, s.storageMetrics, s.indexMetrics, s.features, s.distributor) if err != nil { return err } diff --git a/pkg/storage/unified/sql/test/integration_test.go b/pkg/storage/unified/sql/test/integration_test.go index deb31b10fd9..a9c2f3a82d4 100644 --- a/pkg/storage/unified/sql/test/integration_test.go +++ b/pkg/storage/unified/sql/test/integration_test.go @@ -113,7 +113,7 @@ func TestClientServer(t *testing.T) { features := featuremgmt.WithFeatures() - svc, err := sql.ProvideUnifiedStorageGrpcService(cfg, features, dbstore, nil, prometheus.NewPedanticRegistry(), nil, nil, nil) + svc, err := sql.ProvideUnifiedStorageGrpcService(cfg, features, dbstore, nil, prometheus.NewPedanticRegistry(), nil, nil, nil, nil) require.NoError(t, err) var client resource.ResourceStoreClient diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index 4dec75a1021..c7e47d14677 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -4297,7 +4297,7 @@ func TestIntegrationHysteresisRule(t *testing.T) { DisableAnonymous: true, AppModeProduction: true, NGAlertSchedulerBaseInterval: 1 * time.Second, - EnableFeatureToggles: []string{featuremgmt.FlagConfigurableSchedulerTick, featuremgmt.FlagRecoveryThreshold}, + EnableFeatureToggles: []string{featuremgmt.FlagConfigurableSchedulerTick}, }) grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) diff --git a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json index 77707413bb4..296e6a4e6bf 100644 --- a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v2alpha1.json @@ -1087,6 +1087,9 @@ "type": "string", "default": "" }, + "origin": { + "type": "string" + }, "value": { "type": "string", "default": "" @@ -1104,9 +1107,6 @@ "type": "string", "default": "" } - }, - "origin": { - "type": "string" } } }, @@ -1215,7 +1215,7 @@ "type": "array", "items": { "type": "integer", - "format": "byte", + "format": "int64", "default": 0 } } @@ -4588,4 +4588,4 @@ } } } -} +} \ No newline at end of file diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v0.yaml b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v0.yaml new file mode 100644 index 00000000000..2d4cf82b060 --- /dev/null +++ b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v0.yaml @@ -0,0 +1,8 @@ +apiVersion: dashboard.grafana.app/v0alpha1 +kind: Dashboard +metadata: + name: test-v0 +spec: + title: Test dashboard. Created at v0 + uid: test-v0 # will be removed by mutation hook + version: 1234567 # will be removed by mutation hook diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v1.yaml b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v1.yaml new file mode 100644 index 00000000000..18dee5c28d9 --- /dev/null +++ b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v1.yaml @@ -0,0 +1,9 @@ +apiVersion: dashboard.grafana.app/v1beta1 +kind: Dashboard +metadata: + name: test-v1 +spec: + title: Test dashboard. Created at v1 + uid: test-v1 # will be removed by mutation hook + version: 1234567 # will be removed by mutation hook + schemaVersion: 41 diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml new file mode 100644 index 00000000000..540b9dcc919 --- /dev/null +++ b/pkg/tests/apis/provisioning/exportunifiedtorepository/dashboard-test-v2.yaml @@ -0,0 +1,10 @@ +apiVersion: dashboard.grafana.app/v2alpha1 +kind: Dashboard +metadata: + name: test-v2 +spec: + title: Test dashboard. Created at v2 + layout: + kind: GridLayout + spec: + items: [] diff --git a/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json b/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json deleted file mode 100644 index b2008d618b0..00000000000 --- a/pkg/tests/apis/provisioning/exportunifiedtorepository/root_dashboard.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "apiVersion": "dashboard.grafana.app/v1beta1", - "kind": "Dashboard", - "metadata": { - "name": "root_dashboard", - "namespace": "default" - }, - "spec": { - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": { - "type": "grafana", - "uid": "-- Grafana --" - }, - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations \u0026 Alerts", - "type": "dashboard" - } - ] - }, - "description": "This is on the root level of the repository.", - "editable": true, - "fiscalYearStartMonth": 0, - "graphTooltip": 0, - "links": [], - "panels": [ - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-BlPu", - "seriesBy": "last" - }, - "custom": { - "axisBorderShow": true, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisGridShow": true, - "axisLabel": "Y axis on the right side", - "axisPlacement": "right", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "points", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 7, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "dashed+area" - } - }, - "decimals": 3, - "fieldMinMax": false, - "links": [ - { - "oneClick": true, - "targetBlank": true, - "title": "Test test", - "url": "https://github.com/grafana/grafana" - } - ], - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "transparent" - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "kvoltamp" - }, - "overrides": [] - }, - "gridPos": { - "h": 24, - "w": 22, - "x": 0, - "y": 0 - }, - "id": 1, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "hideZeros": false, - "mode": "multi", - "sort": "none" - } - }, - "pluginVersion": "11.6.0-pre", - "targets": [ - { - "datasource": { - "type": "datasource", - "uid": "grafana" - }, - "queryType": "randomWalk", - "refId": "A" - } - ], - "title": "Test panel", - "type": "timeseries" - } - ], - "preload": false, - "schemaVersion": 41, - "tags": [ - "grafana", - "dashboard", - "root", - "test" - ], - "templating": { - "list": [] - }, - "time": { - "from": "now-6h", - "to": "now" - }, - "timepicker": {}, - "timezone": "Pacific/Kiritimati", - "title": "Test dashboard on root level", - "uid": "", - "version": 0, - "weekStart": "saturday" - } -} diff --git a/pkg/tests/apis/provisioning/helper_test.go b/pkg/tests/apis/provisioning/helper_test.go index e7226447fd1..bf63e2358fc 100644 --- a/pkg/tests/apis/provisioning/helper_test.go +++ b/pkg/tests/apis/provisioning/helper_test.go @@ -23,7 +23,9 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" - dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashboardV0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" + dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1" folder "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" @@ -46,7 +48,9 @@ type provisioningTestHelper struct { Repositories *apis.K8sResourceClient Jobs *apis.K8sResourceClient Folders *apis.K8sResourceClient - Dashboards *apis.K8sResourceClient + DashboardsV0 *apis.K8sResourceClient + DashboardsV1 *apis.K8sResourceClient + DashboardsV2 *apis.K8sResourceClient AdminREST *rest.RESTClient EditorREST *rest.RESTClient ViewerREST *rest.RESTClient @@ -250,10 +254,20 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper Namespace: "default", // actually org1 GVR: folder.FolderResourceInfo.GroupVersionResource(), }) - dashboards := helper.GetResourceClient(apis.ResourceClientArgs{ + dashboardsV0 := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, Namespace: "default", // actually org1 - GVR: dashboard.DashboardResourceInfo.GroupVersionResource(), + GVR: dashboardV0.DashboardResourceInfo.GroupVersionResource(), + }) + dashboardsV1 := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: "default", // actually org1 + GVR: dashboardV1.DashboardResourceInfo.GroupVersionResource(), + }) + dashboardsV2 := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: "default", // actually org1 + GVR: dashboardV2.DashboardResourceInfo.GroupVersionResource(), }) // Repo client, but less guard rails. Useful for subresources. We'll need this later... @@ -276,7 +290,7 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper return nil } - require.NoError(t, deleteAll(dashboards), "deleting all dashboards") + require.NoError(t, deleteAll(dashboardsV1), "deleting all dashboards") // v0+v1+v2 require.NoError(t, deleteAll(folders), "deleting all folders") require.NoError(t, deleteAll(repositories), "deleting all repositories") @@ -290,7 +304,9 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper ViewerREST: viewerClient, Jobs: jobs, Folders: folders, - Dashboards: dashboards, + DashboardsV0: dashboardsV0, + DashboardsV1: dashboardsV1, + DashboardsV2: dashboardsV2, } } diff --git a/pkg/tests/apis/provisioning/provisioning_test.go b/pkg/tests/apis/provisioning/provisioning_test.go index b70137b823a..22ec10b7073 100644 --- a/pkg/tests/apis/provisioning/provisioning_test.go +++ b/pkg/tests/apis/provisioning/provisioning_test.go @@ -147,7 +147,7 @@ func TestIntegrationProvisioning_FailInvalidSchema(t *testing.T) { require.Equal(t, status.Message, "Dry run failed: Dashboard.dashboard.grafana.app \"invalid-schema-uid\" is invalid: [spec.panels.0.repeatDirection: Invalid value: conflicting values \"h\" and \"this is not an allowed value\", spec.panels.0.repeatDirection: Invalid value: conflicting values \"v\" and \"this is not an allowed value\"]") const invalidSchemaUid = "invalid-schema-uid" - _, err = helper.Dashboards.Resource.Get(ctx, invalidSchemaUid, metav1.GetOptions{}) + _, err = helper.DashboardsV1.Resource.Get(ctx, invalidSchemaUid, metav1.GetOptions{}) require.Error(t, err, "invalid dashboard shouldn't exist") require.True(t, apierrors.IsNotFound(err)) @@ -195,7 +195,7 @@ func TestIntegrationProvisioning_FailInvalidSchema(t *testing.T) { require.Equal(t, job.Status.Errors[0], "Dashboard.dashboard.grafana.app \"invalid-schema-uid\" is invalid: [spec.panels.0.repeatDirection: Invalid value: conflicting values \"h\" and \"this is not an allowed value\", spec.panels.0.repeatDirection: Invalid value: conflicting values \"v\" and \"this is not an allowed value\"]") }, time.Second*10, time.Millisecond*10, "Expected provisioning job to conclude with the status failed") - _, err = helper.Dashboards.Resource.Get(ctx, invalidSchemaUid, metav1.GetOptions{}) + _, err = helper.DashboardsV1.Resource.Get(ctx, invalidSchemaUid, metav1.GetOptions{}) require.Error(t, err, "invalid dashboard shouldn't have been created") require.True(t, apierrors.IsNotFound(err)) @@ -272,7 +272,7 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) { // By now, we should have synced, meaning we have data to read in the local Grafana instance! - found, err := helper.Dashboards.Resource.List(ctx, metav1.ListOptions{}) + found, err := helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err, "can list values") names := []string{} @@ -286,7 +286,7 @@ func TestIntegrationProvisioning_CreatingGitHubRepository(t *testing.T) { require.NoError(t, err, "should delete values") assert.EventuallyWithT(t, func(collect *assert.CollectT) { - found, err := helper.Dashboards.Resource.List(ctx, metav1.ListOptions{}) + found, err := helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err, "can list values") require.Equal(collect, 0, len(found.Items), "expected dashboards to be deleted") }, time.Second*10, time.Millisecond*10, "Expected dashboards to be deleted") @@ -396,7 +396,7 @@ func TestIntegrationProvisioning_RunLocalRepository(t *testing.T) { require.Equal(t, allPanels, name, "save the name from the request") // Get the file from the grafana database - obj, err := helper.Dashboards.Resource.Get(ctx, allPanels, metav1.GetOptions{}) + obj, err := helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) require.NoError(t, err, "the value should be saved in grafana") val, _, _ := unstructured.NestedString(obj.Object, "metadata", "annotations", utils.AnnoKeyManagerKind) require.Equal(t, string(utils.ManagerKindRepo), val, "should have repo annotations") @@ -539,33 +539,33 @@ func TestIntegrationProvisioning_ImportAllPanelsFromLocalRepository(t *testing.T // But the dashboard shouldn't exist yet const allPanels = "n1jR8vnnz" - _, err = helper.Dashboards.Resource.Get(ctx, allPanels, metav1.GetOptions{}) + _, err = helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) require.Error(t, err, "no all-panels dashboard should exist") require.True(t, apierrors.IsNotFound(err)) // Now, we import it, such that it may exist helper.SyncAndWait(t, repo, nil) - _, err = helper.Dashboards.Resource.List(ctx, metav1.ListOptions{}) + _, err = helper.DashboardsV1.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err, "can list values") - obj, err = helper.Dashboards.Resource.Get(ctx, allPanels, metav1.GetOptions{}) + obj, err = helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) require.NoError(t, err, "all-panels dashboard should exist") require.Equal(t, repo, obj.GetAnnotations()[utils.AnnoKeyManagerIdentity]) // Try writing the value directly err = unstructured.SetNestedField(obj.Object, []any{"aaa", "bbb"}, "spec", "tags") require.NoError(t, err, "set tags") - obj, err = helper.Dashboards.Resource.Update(ctx, obj, metav1.UpdateOptions{}) + obj, err = helper.DashboardsV1.Resource.Update(ctx, obj, metav1.UpdateOptions{}) require.NoError(t, err) v, _, _ := unstructured.NestedString(obj.Object, "metadata", "annotations", utils.AnnoKeyUpdatedBy) require.Equal(t, "access-policy:provisioning", v) // Should not be able to directly delete the managed resource - err = helper.Dashboards.Resource.Delete(ctx, allPanels, metav1.DeleteOptions{}) + err = helper.DashboardsV1.Resource.Delete(ctx, allPanels, metav1.DeleteOptions{}) require.NoError(t, err, "user can delete") - _, err = helper.Dashboards.Resource.Get(ctx, allPanels, metav1.GetOptions{}) + _, err = helper.DashboardsV1.Resource.Get(ctx, allPanels, metav1.GetOptions{}) require.Error(t, err, "should delete the internal resource") require.True(t, apierrors.IsNotFound(err)) } @@ -578,10 +578,18 @@ func TestProvisioning_ExportUnifiedToRepository(t *testing.T) { helper := runGrafana(t) ctx := context.Background() - // Set up dashboards first, then the repository, and finally export. - dashboard := helper.LoadYAMLOrJSONFile("exportunifiedtorepository/root_dashboard.json") - _, err := helper.Dashboards.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) - require.NoError(t, err, "should be able to create prerequisite dashboard") + // Write dashboards at + dashboard := helper.LoadYAMLOrJSONFile("exportunifiedtorepository/dashboard-test-v0.yaml") + _, err := helper.DashboardsV0.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) + require.NoError(t, err, "should be able to create v0 dashboard") + + dashboard = helper.LoadYAMLOrJSONFile("exportunifiedtorepository/dashboard-test-v1.yaml") + _, err = helper.DashboardsV1.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) + require.NoError(t, err, "should be able to create v1 dashboard") + + dashboard = helper.LoadYAMLOrJSONFile("exportunifiedtorepository/dashboard-test-v2.yaml") + _, err = helper.DashboardsV2.Resource.Create(ctx, dashboard, metav1.CreateOptions{}) + require.NoError(t, err, "should be able to create v2 dashboard") // Now for the repository. const repo = "local-repository" @@ -608,7 +616,38 @@ func TestProvisioning_ExportUnifiedToRepository(t *testing.T) { // And time to assert. helper.AwaitJobs(t, repo) - fpath := filepath.Join(helper.ProvisioningPath, slugify.Slugify(mustNestedString(dashboard.Object, "spec", "title"))+".json") - _, err = os.Stat(fpath) - require.NoError(t, err, "exported file was not created at path %s", fpath) + type props struct { + title string + apiVersion string + name string + } + + // Check that each file was exported with its stored version + for _, test := range []props{ + {title: "Test dashboard. Created at v0", apiVersion: "dashboard.grafana.app/v0alpha1", name: "test-v0"}, + {title: "Test dashboard. Created at v1", apiVersion: "dashboard.grafana.app/v1beta1", name: "test-v1"}, + {title: "Test dashboard. Created at v2", apiVersion: "dashboard.grafana.app/v2alpha1", name: "test-v2"}, + } { + fpath := filepath.Join(helper.ProvisioningPath, slugify.Slugify(test.title)+".json") + //nolint:gosec // we are ok with reading files in testdata + body, err := os.ReadFile(fpath) + require.NoError(t, err, "exported file was not created at path %s", fpath) + obj := map[string]any{} + err = json.Unmarshal(body, &obj) + require.NoError(t, err, "exported file not json %s", fpath) + + val, _, err := unstructured.NestedString(obj, "apiVersion") + require.NoError(t, err) + require.Equal(t, test.apiVersion, val) + + val, _, err = unstructured.NestedString(obj, "spec", "title") + require.NoError(t, err) + require.Equal(t, test.title, val) + + val, _, err = unstructured.NestedString(obj, "metadata", "name") + require.NoError(t, err) + require.Equal(t, test.name, val) + + require.Nil(t, obj["status"], "should not have a status element") + } } diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index 12fd9fc6ca5..468d4e87968 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -104,7 +104,7 @@ func StartGrafanaEnv(t *testing.T, grafDir, cfgPath string) (string, *server.Tes var storage sql.UnifiedStorageGrpcService if runstore { storage, err = sql.ProvideUnifiedStorageGrpcService(env.Cfg, env.FeatureToggles, env.SQLStore, - env.Cfg.Logger, prometheus.NewPedanticRegistry(), nil, nil, nil) + env.Cfg.Logger, prometheus.NewPedanticRegistry(), nil, nil, nil, nil) require.NoError(t, err) ctx := context.Background() err = storage.StartAsync(ctx) diff --git a/public/app/api/README.md b/public/app/api/README.md index eafebb5e614..67bf87ae19a 100644 --- a/public/app/api/README.md +++ b/public/app/api/README.md @@ -7,7 +7,7 @@ To show the steps to follow, we are going to work on adding an API client to cre First, check if the `group` and the `version` are already present in [openapi_test.go](/pkg/tests/apis/openapi_test.go). If so, move on to the next step.
If you need to add a new block, you can check for the right `group` and `version` in the backend API call that you want to replicate in the frontend. -```jsx +```go { Group: "dashboard.grafana.app", Version: "v0alpha1", @@ -20,136 +20,6 @@ Afterwards, you need to run the `TestIntegrationOpenAPIs` test. Note that it wil > Note: You don’t need to follow these two steps if the `group` you’re working with is already in the `openapi_test.go` file. -
+### 2. Run the API generator script -### 2. Create the API definition - -In the [`/public/app/api/clients`](/public/app/api/clients) folder, create a new folder and `baseAPI.ts` file for your group. This file should have the following content: - -```jsx -import { createApi } from '@reduxjs/toolkit/query/react'; - -import { createBaseQuery } from 'app/api/createBaseQuery'; -import { getAPIBaseURL } from 'app/api/utils'; - -export const BASE_URL = getAPIBaseURL('dashboard.grafana.app', 'v0alpha1'); - -export const api = createApi({ - reducerPath: 'dashboardAPI', - baseQuery: createBaseQuery({ - baseURL: BASE_URL, - }), - endpoints: () => ({}), -}); -``` - -This is the API definition for the specific group you're working with, where `getAPIBaseURL` should have the proper `group` and `version` as parameters. The `reducerPath` needs to be unique. The convention is to use `API`: `dashboard` will be `dashboardAPI`, `iam` will be `iamAPI` and so on. - -### 3. Add your new client to the generation script - -Open [generate-rtk-apis.ts](/scripts/generate-rtk-apis.ts) and add the following information: - -| Data | Descritpion | -| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| outputFile name | File that will be created after running the API Client Generation script. It is the key of the object. | -| apiFile | File with the group's API definition. | -| schemaFile | File with the schema that was automatically created in the second step. Although it is in openapi_snapshots, you should link the one saved in `data/openapi`. | -| filterEndpoints | The `operationId` of the particular route you want to work with. You can check the available operationIds in the specific group's spec file. As seen in the `migrate-to-cloud` one, it is an array | -|  tag | Must be set to `true`, to automatically attach tags to endpoints. This is needed for proper cache invalidation. See more info in the [official documentation](https://redux-toolkit.js.org/rtk-query/usage/automated-refetching#:~:text=RTK%20Query%20uses,an%20active%20subscription.).  | - -
- -> More info in [Redux Toolkit](https://redux-toolkit.js.org/rtk-query/usage/code-generation#simple-usage) - -In our example, the information added will be: - -```jsx -'../public/app/api/clients/dashboard/endpoints.gen.ts': { - apiFile: '../public/app/api/clients/dashboard/baseAPI.ts', - schemaFile: '../data/openapi/dashboard.grafana.app-v0alpha1.json', - filterEndpoints: ['createDashboard', 'updateDashboard'], - tag: true, -}, -``` - -### 4. Run the API client generation script - -Then, we are ready to run the script to create the API client: - -```jsx -yarn generate-apis -``` - -This will create an `endpoints.gen.ts` file in the path specified in the previous step. - -### 5. Create the index file for your hooks - -In the same `api` folder where the `endpoints.gen.ts` file has been saved, you have to create an index file from which you can import the types and hooks needed. By doing this, we selectively export hooks/types from `endpoints.gen.ts`. - -In our case, the dashboard index will be like: - -```jsx -import { generatedAPI } from './endpoints.gen'; - -export const dashboardAPI = generatedAPI; -export const { useCreateDashboardMutation, useUpdateDashboardMutation} = dashboardAPI; -// eslint-disable-next-line no-barrel-files/no-barrel-files -export { type Dashboard } from './endpoints.gen'; - -``` - -There are some use cases where the hook will not work out of the box, and that is a clue to see if it needs to be modified. The hooks can be tweaked by using `enhanceEndpoints`. - -```jsx -export const dashboardsAPI = generatedApi.enhanceEndpoints({ - endpoints: { - // Need to mutate the generated query to set the Content-Type header correctly - updateDashboard: (endpointDefinition) => { - const originalQuery = endpointDefinition.query; - if (originalQuery) { - endpointDefinition.query = (requestOptions) => ({ - ...originalQuery(requestOptions), - headers: { - 'Content-Type': 'application/merge-patch+json', - }, - }); - } - }, - }, -}); -``` - -### 6. Add reducers and middleware to the Redux store - -Last but not least, you need to add the middleware and reducers to the store. - -In Grafana, the reducers are added to [`root.ts`](/public/app/core/reducers/root.ts): - -```jsx - import { dashboardAPI } from ''; - const rootReducers = { - ..., - [dashboardAPI.reducerPath]: dashboardAPI.reducer, - }; -``` - -And the middleware is added to [`configureStore.ts`](/public/app/store/configureStore.ts): - -```jsx -import { dashboardAPI } from ''; -export function configureStore(initialState?: Partial) { - const store = reduxConfigureStore({ - reducer: createRootReducer(), - middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ thunk: true, serializableCheck: false, immutableCheck: false }).concat( - ..., - dashboardAPI.middleware - ), - ..., - }); -``` - -You have available the official documentation in [RTK Query](https://redux-toolkit.js.org/tutorials/rtk-query#add-the-service-to-your-store) - -After this step is done, it is time to use your hooks across Grafana. -Enjoy coding! +Run `yarn generate:api-client` and follow the prompts. See [API Client Generator](./generator/README.md) for details. diff --git a/public/app/api/generator/README.md b/public/app/api/generator/README.md new file mode 100644 index 00000000000..fb54b8011d4 --- /dev/null +++ b/public/app/api/generator/README.md @@ -0,0 +1,52 @@ +# RTK Query API Client Generator + +This generator automates the process of creating RTK Query API clients for Grafana's API groups. It replaces the manual steps outlined in the [main API documentation](../README.md). + +## Usage + +```bash +yarn generate:api-client +``` + +The CLI will prompt for: + +1. **Enterprise or OSS API** - Whether this is an Enterprise or OSS API. This affects paths and build commands. +2. **API group name** - The basic name for the API (e.g., `dashboard`) +3. **API group** - The full API group name (defaults to `.grafana.app`) +4. **API version** - The API version (e.g., `v0alpha1`) +5. **Reducer path** - The Redux reducer path (defaults to `API`). This will also be used as the API's named export. +6. **Endpoints** - Optional comma-separated list of endpoints to include (e.g., `createDashboard,updateDashboard`). If not provided, all endpoints will be included. + +## What It Does + +The generator automates the following: + +1. Creates the `baseAPI.ts` file for the API group +2. Updates the appropriate generate script to include the API client + - `scripts/generate-rtk-apis.ts` for OSS APIs + - `local/generate-enterprise-apis.ts` for Enterprise APIs +3. Creates the `index.ts` file with proper exports +4. For OSS APIs only: Registers Redux reducers and middleware in the store. For Enterprise this needs to be done manually +5. Formats all generated files using Prettier and ESLint +6. Automatically runs the appropriate command to generate endpoints from the OpenAPI schema + +## Limitations + +- The generator is optimized for Kubernetes-style APIs, as it requires Kubernetes resource details. For legacy APIs, manual adjustments may be needed. +- It expects processed OpenAPI specifications to exist in the `openapi_snapshots` directory + +## Troubleshooting + +### Missing OpenAPI Schema + +If an error about a missing OpenAPI schema appears, check that: + +1. The API group and version exist in the backend +2. The `TestIntegrationOpenAPIs` test has been run to generate the schema (step 1 in the [main API documentation](../README.md)). +3. The schema file exists at `data/openapi/-.json` + +### Validation Errors + +- API group must include `.grafana.app` +- Version must be in format `v0alpha1`, `v1beta2`, etc. +- Reducer path must end with `API` diff --git a/public/app/api/generator/helpers.ts b/public/app/api/generator/helpers.ts new file mode 100644 index 00000000000..71b7447a11f --- /dev/null +++ b/public/app/api/generator/helpers.ts @@ -0,0 +1,115 @@ +import { execSync } from 'child_process'; +import path from 'path'; + +type PlopActionFunction = ( + answers: Record, + config?: Record +) => string | Promise; + +// Helper to remove quotes from operation IDs +export const removeQuotes = (str: string | unknown) => { + if (typeof str !== 'string') { + return str; + } + return str.replace(/^['"](.*)['"]$/, '$1'); +}; + +export const formatEndpoints = () => (endpointsInput: string | string[]) => { + if (Array.isArray(endpointsInput)) { + return endpointsInput.map((op) => `'${removeQuotes(op)}'`).join(', '); + } + + // Handle string input (comma-separated) + if (typeof endpointsInput === 'string') { + const endpointsArray = endpointsInput + .split(',') + .map((id) => id.trim()) + .filter(Boolean); + + return endpointsArray.map((op) => `'${removeQuotes(op)}'`).join(', '); + } + + return ''; +}; + +// List of created or modified files +export const getFilesToFormat = (groupName: string, isEnterprise = false) => { + const apiClientBasePath = isEnterprise ? 'public/app/extensions/api/clients' : 'public/app/api/clients'; + const generateScriptPath = isEnterprise ? 'local/generate-enterprise-apis.ts' : 'scripts/generate-rtk-apis.ts'; + + return [ + `${apiClientBasePath}/${groupName}/baseAPI.ts`, + `${apiClientBasePath}/${groupName}/index.ts`, + generateScriptPath, + ...(isEnterprise ? [] : [`public/app/core/reducers/root.ts`, `public/app/store/configureStore.ts`]), + ]; +}; + +export const runGenerateApis = + (basePath: string): PlopActionFunction => + (answers, config) => { + try { + const isEnterprise = answers.isEnterprise || (config && config.isEnterprise); + + let command; + if (isEnterprise) { + command = 'yarn process-specs && npx rtk-query-codegen-openapi ./local/generate-enterprise-apis.ts'; + } else { + command = 'yarn generate-apis'; + } + + console.log(`⏳ Running ${command} to generate endpoints...`); + execSync(command, { stdio: 'inherit', cwd: basePath }); + return '✅ API endpoints generated successfully!'; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error('❌ Failed to generate API endpoints:', errorMessage); + return '❌ Failed to generate API endpoints. See error above.'; + } + }; + +export const formatFiles = + (basePath: string): PlopActionFunction => + (_, config) => { + if (!config || !Array.isArray(config.files)) { + console.error('Invalid config passed to formatFiles action'); + return '❌ Formatting failed: Invalid configuration'; + } + + const filesToFormat = config.files.map((file: string) => path.join(basePath, file)); + + try { + const filesList = filesToFormat.map((file: string) => `"${file}"`).join(' '); + + console.log('🧹 Running ESLint on generated/modified files...'); + try { + execSync(`yarn eslint --fix ${filesList}`, { cwd: basePath }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.warn(`⚠️ Warning: ESLint encountered issues: ${errorMessage}`); + } + + console.log('🧹 Running Prettier on generated/modified files...'); + try { + // '--ignore-path' is necessary so the gitignored files ('local/' folder) can still be formatted + execSync(`yarn prettier --write ${filesList} --ignore-path=./.prettierignore`, { cwd: basePath }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.warn(`⚠️ Warning: Prettier encountered issues: ${errorMessage}`); + } + + return '✅ Files linted and formatted successfully!'; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + console.error('⚠️ Warning: Formatting operations failed:', errorMessage); + return '⚠️ Warning: Formatting operations failed.'; + } + }; + +export const validateGroup = (group: string) => { + return group && group.includes('.grafana.app') ? true : 'Group should be in format: name.grafana.app'; +}; + +export const validateVersion = (version: string) => { + return version && /^v\d+[a-z]*\d+$/.test(version) ? true : 'Version should be in format: v0alpha1, v1beta2, etc.'; +}; diff --git a/public/app/api/generator/plopfile.ts b/public/app/api/generator/plopfile.ts new file mode 100644 index 00000000000..921089c8c5a --- /dev/null +++ b/public/app/api/generator/plopfile.ts @@ -0,0 +1,165 @@ +import path from 'path'; +import type { NodePlopAPI, PlopGeneratorConfig } from 'plop'; + +import { + formatEndpoints, + validateGroup, + validateVersion, + getFilesToFormat, + runGenerateApis, + formatFiles, + // The file extension is necessary to make the imports + // work with the '--experimental-strip-types' flag + // @ts-ignore +} from './helpers.ts'; +// @ts-ignore +import { type ActionConfig, type PlopData, isPlopData } from './types.ts'; + +export default function plopGenerator(plop: NodePlopAPI) { + // Grafana root path + const basePath = path.resolve(import.meta.dirname, '../../../..'); + + // Register custom action types + plop.setActionType('runGenerateApis', runGenerateApis(basePath)); + plop.setActionType('formatFiles', formatFiles(basePath)); + + // Used in templates to format endpoints + plop.setHelper('formatEndpoints', formatEndpoints()); + + const generateRtkApiActions = (data: PlopData) => { + const { reducerPath, groupName, isEnterprise } = data; + + const apiClientBasePath = isEnterprise ? 'public/app/extensions/api/clients' : 'public/app/api/clients'; + const generateScriptPath = isEnterprise ? 'local/generate-enterprise-apis.ts' : 'scripts/generate-rtk-apis.ts'; + + // Using app path, so the imports work on any file level + const clientImportPath = isEnterprise ? '../extensions/api/clients' : 'app/api/clients'; + + const apiPathPrefix = isEnterprise ? '../public/app/extensions/api/clients' : '../public/app/api/clients'; + + const templateData = { + ...data, + apiPathPrefix, + }; + + // Base actions that are always added + const actions: ActionConfig[] = [ + { + type: 'add', + path: path.join(basePath, `${apiClientBasePath}/${groupName}/baseAPI.ts`), + templateFile: './templates/baseAPI.ts.hbs', + }, + { + type: 'modify', + path: path.join(basePath, generateScriptPath), + pattern: '// PLOP_INJECT_API_CLIENT - Used by the API client generator', + templateFile: './templates/config-entry.hbs', + data: templateData, + }, + { + type: 'add', + path: path.join(basePath, `${apiClientBasePath}/${groupName}/index.ts`), + templateFile: './templates/index.ts.hbs', + }, + ]; + + // Only add redux reducer and middleware for OSS clients + if (!isEnterprise) { + actions.push( + { + type: 'modify', + path: path.join(basePath, 'public/app/core/reducers/root.ts'), + pattern: '// PLOP_INJECT_IMPORT', + template: `import { ${reducerPath} } from '${clientImportPath}/${groupName}';\n// PLOP_INJECT_IMPORT`, + }, + { + type: 'modify', + path: path.join(basePath, 'public/app/core/reducers/root.ts'), + pattern: '// PLOP_INJECT_REDUCER', + template: `[${reducerPath}.reducerPath]: ${reducerPath}.reducer,\n // PLOP_INJECT_REDUCER`, + }, + { + type: 'modify', + path: path.join(basePath, 'public/app/store/configureStore.ts'), + pattern: '// PLOP_INJECT_IMPORT', + template: `import { ${reducerPath} } from '${clientImportPath}/${groupName}';\n// PLOP_INJECT_IMPORT`, + }, + { + type: 'modify', + path: path.join(basePath, 'public/app/store/configureStore.ts'), + pattern: '// PLOP_INJECT_MIDDLEWARE', + template: `${reducerPath}.middleware,\n // PLOP_INJECT_MIDDLEWARE`, + } + ); + } + + // Add formatting and generation actions + actions.push( + { + type: 'formatFiles', + files: getFilesToFormat(groupName, isEnterprise), + }, + { + type: 'runGenerateApis', + isEnterprise, + } + ); + + return actions; + }; + + const generator: PlopGeneratorConfig = { + description: 'Generate RTK Query API client for a Grafana API group', + prompts: [ + { + type: 'confirm', + name: 'isEnterprise', + message: 'Is this a Grafana Enterprise API?', + default: false, + }, + { + type: 'input', + name: 'groupName', + message: 'API group name (e.g. dashboard):', + validate: (input: string) => (input?.trim() ? true : 'Group name is required'), + }, + { + type: 'input', + name: 'group', + message: 'API group (e.g. dashboard.grafana.app):', + default: (answers: { groupName?: string }) => `${answers.groupName}.grafana.app`, + validate: validateGroup, + }, + { + type: 'input', + name: 'version', + message: 'API version (e.g. v0alpha1):', + default: 'v0alpha1', + validate: validateVersion, + }, + { + type: 'input', + name: 'reducerPath', + message: 'Reducer path (e.g. dashboardAPI):', + default: (answers: { groupName?: string }) => `${answers.groupName}API`, + validate: (input: string) => + input?.endsWith('API') ? true : 'Reducer path should end with "API" (e.g. dashboardAPI)', + }, + { + type: 'input', + name: 'endpoints', + message: 'Endpoints to include (comma-separated, optional):', + validate: () => true, + }, + ], + actions: function (data) { + if (!isPlopData(data)) { + throw new Error('Invalid data format received from prompts'); + } + + return generateRtkApiActions(data); + }, + }; + + plop.setGenerator('rtk-api-client', generator); +} diff --git a/public/app/api/generator/templates/baseAPI.ts.hbs b/public/app/api/generator/templates/baseAPI.ts.hbs new file mode 100644 index 00000000000..a5fd29cf488 --- /dev/null +++ b/public/app/api/generator/templates/baseAPI.ts.hbs @@ -0,0 +1,14 @@ +import { createApi } from '@reduxjs/toolkit/query/react'; + +import { createBaseQuery } from 'app/api/createBaseQuery'; +import { getAPIBaseURL } from 'app/api/utils'; + +export const BASE_URL = getAPIBaseURL('{{group}}', '{{version}}'); + +export const api = createApi({ + reducerPath: '{{reducerPath}}', + baseQuery: createBaseQuery({ + baseURL: BASE_URL, + }), + endpoints: () => ({}), +}); diff --git a/public/app/api/generator/templates/config-entry.hbs b/public/app/api/generator/templates/config-entry.hbs new file mode 100644 index 00000000000..53c0eaf673e --- /dev/null +++ b/public/app/api/generator/templates/config-entry.hbs @@ -0,0 +1,9 @@ +'{{apiPathPrefix}}/{{groupName}}/endpoints.gen.ts': { + apiFile: '{{apiPathPrefix}}/{{groupName}}/baseAPI.ts', + schemaFile: '../data/openapi/{{group}}-{{version}}.json', + {{#if endpoints}} + filterEndpoints: [{{{formatEndpoints endpoints}}}], + {{/if}} + tag: true, +}, +// PLOP_INJECT_API_CLIENT - Used by the API client generator diff --git a/public/app/api/generator/templates/index.ts.hbs b/public/app/api/generator/templates/index.ts.hbs new file mode 100644 index 00000000000..de9c3c66cf8 --- /dev/null +++ b/public/app/api/generator/templates/index.ts.hbs @@ -0,0 +1,3 @@ +import { generatedAPI } from './endpoints.gen'; + +export const {{reducerPath}} = generatedAPI.enhanceEndpoints({}); diff --git a/public/app/api/generator/types.ts b/public/app/api/generator/types.ts new file mode 100644 index 00000000000..0eb73b6ab90 --- /dev/null +++ b/public/app/api/generator/types.ts @@ -0,0 +1,27 @@ +import type { AddActionConfig, ModifyActionConfig } from 'plop'; + +export interface FormatFilesActionConfig { + type: 'formatFiles'; + files: string[]; +} + +export interface RunGenerateApisActionConfig { + type: 'runGenerateApis'; + isEnterprise: boolean; +} + +// Union type of all possible action configs +export type ActionConfig = AddActionConfig | ModifyActionConfig | FormatFilesActionConfig | RunGenerateApisActionConfig; + +export interface PlopData { + groupName: string; + group: string; + version: string; + reducerPath: string; + endpoints: string; + isEnterprise: boolean; +} + +export function isPlopData(data: unknown): data is PlopData { + return typeof data === 'object' && data !== null; +} diff --git a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx index df23d0f2898..f39c05766fc 100644 --- a/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx +++ b/public/app/core/components/AppChrome/ExtensionSidebar/ExtensionSidebarProvider.tsx @@ -2,7 +2,7 @@ import { createContext, ReactNode, useCallback, useContext, useEffect, useState import { useLocalStorage } from 'react-use'; import { store, type ExtensionInfo } from '@grafana/data'; -import { config, getAppEvents, usePluginLinks, locationService } from '@grafana/runtime'; +import { config, getAppEvents, reportInteraction, usePluginLinks, locationService } from '@grafana/runtime'; import { ExtensionPointPluginMeta, getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils'; import { OpenExtensionSidebarEvent } from 'app/types/events'; @@ -173,6 +173,19 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo // update the stored docked component id when it changes useEffect(() => { + const componentMeta = getComponentMetaFromComponentId(dockedComponentId ?? ''); + const storedComponentId = store.get(EXTENSION_SIDEBAR_DOCKED_LOCAL_STORAGE_KEY); + const storedComponentMeta = getComponentMetaFromComponentId(storedComponentId ?? ''); + const opened = dockedComponentId !== undefined; + // we either want to track opened events, or closed events when we have a previous component + if (opened || storedComponentMeta) { + reportInteraction('grafana_extension_sidebar_changed', { + opened: opened, + componentTitle: (opened ? componentMeta : storedComponentMeta)?.componentTitle, + pluginId: (opened ? componentMeta : storedComponentMeta)?.pluginId, + fromLocalstorage: storedComponentId === dockedComponentId, + }); + } if (dockedComponentId) { store.set(EXTENSION_SIDEBAR_DOCKED_LOCAL_STORAGE_KEY, dockedComponentId); } else { diff --git a/public/app/core/components/Select/DashboardPicker.tsx b/public/app/core/components/Select/DashboardPicker.tsx index efe594ad208..dfbb2458ade 100644 --- a/public/app/core/components/Select/DashboardPicker.tsx +++ b/public/app/core/components/Select/DashboardPicker.tsx @@ -1,5 +1,5 @@ import debounce from 'debounce-promise'; -import { useCallback, useEffect, useState } from 'react'; +import { forwardRef, useCallback, useEffect, useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { AsyncSelectProps, AsyncSelect } from '@grafana/ui'; @@ -38,72 +38,69 @@ async function findDashboards(query = '') { const getDashboards = debounce(findDashboards, 250, { leading: true }); // TODO: this component should provide a way to apply different filters to the search APIs -export const DashboardPicker = ({ - value, - onChange, - placeholder = 'Select dashboard', - noOptionsMessage = 'No dashboards found', - ...props -}: Props) => { - const [current, setCurrent] = useState>(); +export const DashboardPicker = forwardRef( + ({ value, onChange, placeholder = 'Select dashboard', noOptionsMessage = 'No dashboards found', ...props }, ref) => { + const [current, setCurrent] = useState>(); - // This is required because the async select does not match the raw uid value - // We can not use a simple Select because the dashboard search should not return *everything* - useEffect(() => { - if (!value || value === current?.value?.uid) { - return; - } + // This is required because the async select does not match the raw uid value + // We can not use a simple Select because the dashboard search should not return *everything* + useEffect(() => { + if (!value || value === current?.value?.uid) { + return; + } - (async () => { - // value was manually changed from outside or we are rendering for the first time. - // We need to fetch dashboard information. - const dto = await getDashboardAPI().getDashboardDTO(value, undefined); + (async () => { + // value was manually changed from outside or we are rendering for the first time. + // We need to fetch dashboard information. + const dto = await getDashboardAPI().getDashboardDTO(value, undefined); - if (isDashboardV2Resource(dto)) { - setCurrent({ - value: { - uid: dto.metadata.name, - title: dto.spec.title, - folderTitle: dto.metadata.annotations?.[AnnoKeyFolderTitle], - folderUid: dto.metadata.annotations?.[AnnoKeyFolder], - }, - label: formatLabel(dto.metadata.annotations?.[AnnoKeyFolder], dto.spec.title), - }); - } else { - if (dto.dashboard) { + if (isDashboardV2Resource(dto)) { setCurrent({ value: { - uid: dto.dashboard.uid, - title: dto.dashboard.title, - folderTitle: dto.meta.folderTitle, - folderUid: dto.meta.folderUid, + uid: dto.metadata.name, + title: dto.spec.title, + folderTitle: dto.metadata.annotations?.[AnnoKeyFolderTitle], + folderUid: dto.metadata.annotations?.[AnnoKeyFolder], }, - label: formatLabel(dto.meta?.folderTitle, dto.dashboard.title), + label: formatLabel(dto.metadata.annotations?.[AnnoKeyFolder], dto.spec.title), }); + } else { + if (dto.dashboard) { + setCurrent({ + value: { + uid: dto.dashboard.uid, + title: dto.dashboard.title, + folderTitle: dto.meta.folderTitle, + folderUid: dto.meta.folderUid, + }, + label: formatLabel(dto.meta?.folderTitle, dto.dashboard.title), + }); + } } - } - })(); - // we don't need to rerun this effect every time `current` changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value]); + })(); + // we don't need to rerun this effect every time `current` changes + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]); - const onPicked = useCallback( - (sel: SelectableValue) => { - setCurrent(sel); - onChange?.(sel?.value); - }, - [onChange, setCurrent] - ); + const onPicked = useCallback( + (sel: SelectableValue) => { + setCurrent(sel); + onChange?.(sel?.value); + }, + [onChange, setCurrent] + ); - return ( - - ); -}; + return ( + + ); + } +); diff --git a/public/app/core/reducers/root.ts b/public/app/core/reducers/root.ts index 6b307d25529..99877d65c90 100644 --- a/public/app/core/reducers/root.ts +++ b/public/app/core/reducers/root.ts @@ -35,6 +35,8 @@ import { provisioningAPI } from '../../api/clients/provisioning'; import { alertingApi } from '../../features/alerting/unified/api/alertingApi'; import { userPreferencesAPI } from '../../features/preferences/api'; import { cleanUpAction } from '../actions/cleanUp'; +// Used by the API client generator +// PLOP_INJECT_IMPORT const rootReducers = { ...sharedReducers, @@ -69,6 +71,8 @@ const rootReducers = { [provisioningAPI.reducerPath]: provisioningAPI.reducer, [folderAPI.reducerPath]: folderAPI.reducer, [advisorAPI.reducerPath]: advisorAPI.reducer, + // PLOP_INJECT_REDUCER + // Used by the API client generator }; const addedReducers = {}; diff --git a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx index ff57ca0527a..94642688d1e 100644 --- a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.test.tsx @@ -1,7 +1,10 @@ import { render, screen, userEvent, within } from 'test/test-utils'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; -import { setMuteTimingsListError } from 'app/features/alerting/unified/mocks/server/configure'; +import { + setMuteTimingsListError, + setTimeIntervalsListEmpty, +} from 'app/features/alerting/unified/mocks/server/configure'; import { setAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers'; import { captureRequests } from 'app/features/alerting/unified/mocks/server/events'; import { AccessControlAction } from 'app/types'; @@ -89,6 +92,8 @@ describe('MuteTimingsTable', () => { expect(await screen.findByTestId('dynamic-table')).toBeInTheDocument(); expect(await screen.findByText('Provisioned')).toBeInTheDocument(); + expect(screen.queryByText(/no mute timings configured/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/you haven't created any mute timings yet/i)).not.toBeInTheDocument(); }); it('shows error when mute timings cannot load', async () => { @@ -114,6 +119,12 @@ describe('MuteTimingsTable', () => { expect(deleteRequest).toBeDefined(); }); + + it('shows empty state when no mute timings are configured', async () => { + setTimeIntervalsListEmpty(); + renderWithProvider(); + expect(await screen.findByText(/you haven't created any mute timings yet/i)).toBeInTheDocument(); + }); }); describe('without necessary permissions', () => { diff --git a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx index e9fad0243e8..de41092bc25 100644 --- a/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx @@ -108,24 +108,28 @@ export const MuteTimingsTable = () => { )} {items.length > 0 ? : null} - {items.length === 0 && !hideActions ? ( - + {!hideActions ? ( + + ) : ( + )} - buttonLabel="Add mute timing" - buttonIcon="plus" - buttonSize="lg" - href={makeAMLink('alerting/routes/mute-timing/new', alertManagerSourceName)} - showButton={allowedToCreateMuteTiming} - /> - ) : ( - + )}
); diff --git a/public/app/features/alerting/unified/mocks/server/configure.ts b/public/app/features/alerting/unified/mocks/server/configure.ts index b7efbcd53b5..ceed4471e0c 100644 --- a/public/app/features/alerting/unified/mocks/server/configure.ts +++ b/public/app/features/alerting/unified/mocks/server/configure.ts @@ -14,7 +14,11 @@ import { getDisabledPluginHandler, getPluginMissingHandler, } from 'app/features/alerting/unified/mocks/server/handlers/plugins'; -import { ALERTING_API_SERVER_BASE_URL, paginatedHandlerFor } from 'app/features/alerting/unified/mocks/server/utils'; +import { + ALERTING_API_SERVER_BASE_URL, + getK8sResponse, + paginatedHandlerFor, +} from 'app/features/alerting/unified/mocks/server/utils'; import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges'; import { clearPluginSettingsCache } from 'app/features/plugins/pluginSettings'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; @@ -158,6 +162,19 @@ export const setMuteTimingsListError = () => { return handler; }; +/** + * Makes the mock server respond with no time intervals + */ +export const setTimeIntervalsListEmpty = () => { + const listMuteTimingsPath = listNamespacedTimeIntervalHandler().info.path; + const handler = http.get(listMuteTimingsPath, () => { + return HttpResponse.json(getK8sResponse('TimeIntervalList', [])); + }); + + server.use(handler); + return handler; +}; + export function mimirDataSource() { const dataSource = mockDataSource( { diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx index 5366860504d..a4de7c0a1ac 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRenderer.tsx @@ -206,7 +206,6 @@ function getStyles(theme: GrafanaTheme2) { wrapper: css({ display: 'flex', flexDirection: 'column', - minHeight: '100px', }), wrapperNotCollapsed: css({ '> div:nth-child(2)': { diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap index db3d9c0644a..c630c36d2e6 100644 --- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap +++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModelSchemaV2.test.ts.snap @@ -71,6 +71,7 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model }, ], "title": "Test Panel", + "transparent": true, "vizConfig": { "kind": "timeseries", "spec": { diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts index 275a811998d..19a646f64e8 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.test.ts @@ -268,7 +268,6 @@ describe('transformSceneToSaveModelSchemaV2', () => { title: 'Test Panel 2', description: 'Test Description 2', fieldConfig: { defaults: {}, overrides: [] }, - displayMode: 'transparent', pluginVersion: '7.0.0', $timeRange: new SceneTimeRange({ timeZone: 'UTC', diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts index a4922c6cf94..426c1f5c7bc 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModelSchemaV2.ts @@ -220,6 +220,7 @@ export function vizPanelToSchemaV2( title: vizPanel.state.title, description: vizPanel.state.description ?? '', links: getPanelLinks(vizPanel), + transparent: vizPanel.state.displayMode === 'transparent' ? true : undefined, data: { kind: 'QueryGroup', spec: { diff --git a/public/app/features/dashboard-scene/utils/dashboardSessionState.test.ts b/public/app/features/dashboard-scene/utils/dashboardSessionState.test.ts index 80b1716e7e6..da3d6459dc0 100644 --- a/public/app/features/dashboard-scene/utils/dashboardSessionState.test.ts +++ b/public/app/features/dashboard-scene/utils/dashboardSessionState.test.ts @@ -29,6 +29,17 @@ describe('dashboardSessionState', () => { expect(window.sessionStorage.getItem(PRESERVED_SCENE_STATE_KEY)).toBeNull(); }); + it('should do nothing if dashboard version is 0', () => { + const scene = buildTestScene(); + scene.setState({ version: 0 }); + + const deactivate = scene.activate(); + expect(window.sessionStorage.getItem(PRESERVED_SCENE_STATE_KEY)).toBeNull(); + + deactivate(); + expect(window.sessionStorage.getItem(PRESERVED_SCENE_STATE_KEY)).toBeNull(); + }); + it('should capture dashboard scene state and save it to session storage on deactivation', () => { const scene = buildTestScene(); @@ -84,6 +95,19 @@ describe('dashboardSessionState', () => { expect(locationService.getLocation().search).toBe('?var-customVar=b&from=now-6h&to=now&timezone=browser'); }); + + it('should not restore state if dashboard version is 0', () => { + window.sessionStorage.setItem( + PRESERVED_SCENE_STATE_KEY, + '?var-customVarNotOnDB=b&from=now-5m&to=now&timezone=browser' + ); + const scene = buildTestScene(); + scene.setState({ version: 0 }); + + restoreDashboardStateFromLocalStorage(scene); + + expect(locationService.getLocation().search).toBe('?var-customVar=b&from=now-6h&to=now&timezone=browser'); + }); }); }); diff --git a/public/app/features/dashboard-scene/utils/dashboardSessionState.ts b/public/app/features/dashboard-scene/utils/dashboardSessionState.ts index 6ad764e2222..c31a29843ca 100644 --- a/public/app/features/dashboard-scene/utils/dashboardSessionState.ts +++ b/public/app/features/dashboard-scene/utils/dashboardSessionState.ts @@ -7,6 +7,10 @@ import { DashboardScene } from '../scene/DashboardScene'; export const PRESERVED_SCENE_STATE_KEY = `grafana.dashboard.preservedUrlFiltersState`; export function restoreDashboardStateFromLocalStorage(dashboard: DashboardScene) { + if (!dashboard.state.version) { + return; + } + const preservedUrlState = window.sessionStorage.getItem(PRESERVED_SCENE_STATE_KEY); if (preservedUrlState) { @@ -49,7 +53,7 @@ export function preserveDashboardSceneStateInLocalStorage(scene: DashboardScene) return () => { // Skipping saving state for default home dashboard - if (!scene.state.uid) { + if (!scene.state.uid || !scene.state.version) { return; } diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 0933c48546f..5d16a8cce74 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -140,10 +140,7 @@ export const autoMigrateAngular: Record = { 'grafana-singlestat-panel': 'stat', 'grafana-piechart-panel': 'piechart', 'grafana-worldmap-panel': 'geomap', -}; - -export const autoMigrateRemovedPanelPlugins: Record = { - 'heatmap-new': 'heatmap', // this was a temporary development panel that is now standard + 'natel-discrete-panel': 'state-timeline', }; export class PanelModel implements DataConfigSource, IPanelModel { diff --git a/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts b/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts index c21959c86ef..114466c87ec 100644 --- a/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts +++ b/public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts @@ -1,10 +1,6 @@ -import { autoMigrateRemovedPanelPlugins, autoMigrateAngular } from './PanelModel'; +import { autoMigrateAngular } from './PanelModel'; export function getPanelPluginToMigrateTo(panel: any): string | undefined { - if (autoMigrateRemovedPanelPlugins[panel.type]) { - return autoMigrateRemovedPanelPlugins[panel.type]; - } - // Graph needs special logic as it can be migrated to multiple panels if (panel.type === 'graph') { if (panel.xaxis?.mode === 'series') { diff --git a/public/app/features/expressions/components/Threshold.tsx b/public/app/features/expressions/components/Threshold.tsx index 8f4782241dc..e3fa883d918 100644 --- a/public/app/features/expressions/components/Threshold.tsx +++ b/public/app/features/expressions/components/Threshold.tsx @@ -6,7 +6,6 @@ import { FormEvent, useEffect, useReducer } from 'react'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { InlineField, InlineFieldRow, InlineSwitch, Input, Select, Stack, useStyles2 } from '@grafana/ui'; -import { config } from 'app/core/config'; import { t } from 'app/core/internationalization'; import { EvalFunction } from 'app/features/alerting/state/alertDef'; @@ -86,8 +85,6 @@ export const Threshold = ({ labelWidth, onChange, refIds, query, onError, useHys conditionInState.evaluator.type === EvalFunction.IsOutsideRangeIncluded || conditionInState.evaluator.type === EvalFunction.IsWithinRangeIncluded; - const hysteresisEnabled = Boolean(config.featureToggles?.recoveryThreshold) && useHysteresis; - const id = uniqueId('threshold-'); return ( @@ -125,7 +122,7 @@ export const Threshold = ({ labelWidth, onChange, refIds, query, onError, useHys /> )} - {hysteresisEnabled && } + {useHysteresis && } ); interface HysteresisSectionProps { diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 69bcf471dc2..1519f574356 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -2,14 +2,14 @@ "name": "@grafana-plugins/grafana-azure-monitor-datasource", "description": "Grafana data source for Azure Monitor", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "@kusto/monaco-kusto": "^10.0.0", "fast-deep-equal": "^3.1.3", "i18next": "^24.0.0", @@ -25,8 +25,8 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index 1011936d833..8f0c6d7b214 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -2,15 +2,15 @@ "name": "@grafana-plugins/stackdriver", "description": "Grafana data source for Google Cloud Monitoring", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/google-sdk": "0.1.2", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "debounce-promise": "3.1.2", "fast-deep-equal": "^3.1.3", "i18next": "^24.0.0", @@ -26,8 +26,8 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index c3d6868f5a9..b2772fbc7d1 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/grafana-postgresql-datasource", "description": "PostgreSQL data source plugin", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/sql": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/sql": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.1", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 2a54f816399..dc3ac814652 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/grafana-pyroscope-datasource", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "fast-deep-equal": "^3.1.3", "lodash": "4.17.21", "monaco-editor": "0.34.1", @@ -20,7 +20,7 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index c6e7abc0000..9a7bd250e1f 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/grafana-testdata-datasource", "description": "Generates test data in different forms", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "d3-random": "^3.0.1", "lodash": "4.17.21", "micro-memoize": "^4.1.2", @@ -21,8 +21,8 @@ "uuid": "11.0.5" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/graphite/datasource.test.ts b/public/app/plugins/datasource/graphite/datasource.test.ts index 548797d7342..0ebe09c1c0b 100644 --- a/public/app/plugins/datasource/graphite/datasource.test.ts +++ b/public/app/plugins/datasource/graphite/datasource.test.ts @@ -5,9 +5,9 @@ import { createFetchResponse } from 'test/helpers/createFetchResponse'; import { AbstractLabelMatcher, AbstractLabelOperator, - getFrameDisplayName, - dateTime, DataQueryRequest, + dateTime, + getFrameDisplayName, MetricFindValue, } from '@grafana/data'; import { BackendSrvRequest } from '@grafana/runtime'; @@ -373,62 +373,116 @@ describe('graphiteDatasource', () => { describe('building graphite params', () => { it('should return empty array if no targets', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{}], - }); + const originalTargetMap = { A: '' }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{}], + }, + originalTargetMap + ); expect(results.length).toBe(0); }); it('should uri escape targets', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'prod1.{test,test2}' }, { target: 'prod2.count' }], - }); + const originalTargetMap = { + A: 'prod1.{test,test2}', + B: 'prod2.count', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'prod1.{test,test2}' }, { target: 'prod2.count' }], + }, + originalTargetMap + ); expect(results).toContain('target=prod1.%7Btest%2Ctest2%7D'); }); it('should replace target placeholder', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'series1' }, { target: 'series2' }, { target: 'asPercent(#A,#B)' }], - }); + const originalTargetMap = { + A: 'series1', + B: 'series2', + C: 'asPercent(#A,#B)', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'series1' }, { target: 'series2' }, { target: 'asPercent(#A,#B)' }], + }, + originalTargetMap + ); expect(results[2]).toBe('target=asPercent(series1%2Cseries2)'); }); it('should replace target placeholder for hidden series', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [ - { target: 'series1', hide: true }, - { target: 'sumSeries(#A)', hide: true }, - { target: 'asPercent(#A,#B)' }, - ], - }); + const originalTargetMap = { + A: 'series1', + B: 'sumSeries(#A)', + C: 'asPercent(#A,#B)', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [ + { target: 'series1', hide: true }, + { target: 'sumSeries(#A)', hide: true }, + { target: 'asPercent(#A,#B)' }, + ], + }, + originalTargetMap + ); expect(results[0]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))')); }); it('should replace target placeholder when nesting query references', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'series1' }, { target: 'sumSeries(#A)' }, { target: 'asPercent(#A,#B)' }], - }); + const originalTargetMap = { + A: 'series1', + B: 'sumSeries(#A)', + C: 'asPercent(#A,#B)', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'series1' }, { target: 'sumSeries(#A)' }, { target: 'asPercent(#A,#B)' }], + }, + originalTargetMap + ); expect(results[2]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))')); }); it('should fix wrong minute interval parameters', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: "summarize(prod.25m.count, '25m', 'sum')" }], - }); + const originalTargetMap = { + A: "summarize(prod.25m.count, '25m', 'sum')", + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: "summarize(prod.25m.count, '25m', 'sum')" }], + }, + originalTargetMap + ); expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.25m.count, '25min', 'sum')")); }); it('should fix wrong month interval parameters', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: "summarize(prod.5M.count, '5M', 'sum')" }], - }); + const originalTargetMap = { + A: "summarize(prod.5M.count, '5M', 'sum')", + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: "summarize(prod.5M.count, '5M', 'sum')" }], + }, + originalTargetMap + ); expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.5M.count, '5mon', 'sum')")); }); it('should ignore empty targets', () => { - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'series1' }, { target: '' }], - }); + const originalTargetMap = { + A: 'series1', + B: '', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'series1' }, { target: '' }], + }, + originalTargetMap + ); expect(results.length).toBe(2); }); @@ -442,9 +496,15 @@ describe('graphiteDatasource', () => { }, ]); - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'my.$metric.*' }], - }); + const originalTargetMap = { + A: 'my.$metric.*', + }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'my.$metric.*' }], + }, + originalTargetMap + ); expect(results).toStrictEqual(['target=my.b.*', 'format=json']); }); @@ -456,10 +516,13 @@ describe('graphiteDatasource', () => { current: { value: ['a', 'b'] }, }, ]); - - const results = ctx.ds.buildGraphiteParams({ - targets: [{ target: 'my.[[metric]].*' }], - }); + const originalTargetMap = { A: 'my.[[metric]].*' }; + const results = ctx.ds.buildGraphiteParams( + { + targets: [{ target: 'my.[[metric]].*' }], + }, + originalTargetMap + ); expect(results).toStrictEqual(['target=my.%7Ba%2Cb%7D.*', 'format=json']); }); diff --git a/public/app/plugins/datasource/graphite/datasource.ts b/public/app/plugins/datasource/graphite/datasource.ts index e8a6487a275..52e5b0ebc62 100644 --- a/public/app/plugins/datasource/graphite/datasource.ts +++ b/public/app/plugins/datasource/graphite/datasource.ts @@ -1,4 +1,4 @@ -import { each, indexOf, isArray, isString, map as _map } from 'lodash'; +import { map as _map, each, indexOf, isArray, isString } from 'lodash'; import { lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; @@ -13,13 +13,13 @@ import { DataSourceWithQueryExportSupport, dateMath, dateTime, + getSearchFilterScopedVar, MetricFindValue, QueryResultMetaStat, ScopedVars, TimeRange, TimeZone, toDataFrame, - getSearchFilterScopedVar, } from '@grafana/data'; import { BackendSrvRequest, FetchResponse, getBackendSrv } from '@grafana/runtime'; import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version'; @@ -210,12 +210,19 @@ export class GraphiteDatasource return merge(...streams); } - // Use this object to map the original refID of the query to our sanitised one - const refIds: { [key: string]: string } = {}; + // Use this object to map the sanitised refID to the original + const formattedRefIdsMap: { [key: string]: string } = {}; + // Use this object to map the original refID to the original target + const originalTargetMap: { [key: string]: string } = {}; for (const target of options.targets) { // Sanitise the refID otherwise the Graphite query will fail const formattedRefId = target.refId.replaceAll(' ', '_'); - refIds[formattedRefId] = target.refId; + formattedRefIdsMap[formattedRefId] = target.refId; + // Track the original target to ensure if we need to interpolate a series, we interpolate using the original target + // rather than the target wrapped in aliasSub e.g.: + // Suppose a query has three targets: A: metric1 B: sumSeries(#A) and C: asPercent(#A, #B) + // We want the targets to be interpolated to: A: aliasSub(metric1, "(^.*$)", "\\1 A"), B: aliasSub(sumSeries(metric1), "(^.*$)", "\\1 B") and C: asPercent(metric1, sumSeries(metric1)) + originalTargetMap[target.refId] = target.target || ''; // Use aliasSub to include the refID in the response series name. This allows us to set the refID on the frame. const updatedTarget = `aliasSub(${target.target}, "(^.*$)", "\\1 ${formattedRefId}")`; target.target = updatedTarget; @@ -231,7 +238,7 @@ export class GraphiteDatasource maxDataPoints: options.maxDataPoints, }; - const params = this.buildGraphiteParams(graphOptions, options.scopedVars); + const params = this.buildGraphiteParams(graphOptions, originalTargetMap, options.scopedVars); if (params.length === 0) { return of({ data: [] }); } @@ -255,7 +262,9 @@ export class GraphiteDatasource httpOptions.requestId = this.name + '.panelId.' + options.panelId; } - return this.doGraphiteRequest(httpOptions).pipe(map((result) => this.convertResponseToDataFrames(result, refIds))); + return this.doGraphiteRequest(httpOptions).pipe( + map((result) => this.convertResponseToDataFrames(result, formattedRefIdsMap)) + ); } addTracingHeaders( @@ -975,7 +984,7 @@ export class GraphiteDatasource ); } - buildGraphiteParams(options: any, scopedVars?: ScopedVars): string[] { + buildGraphiteParams(options: any, originalTargetMap: { [key: string]: string }, scopedVars?: ScopedVars): string[] { const graphiteOptions = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout']; const cleanOptions = [], targets: Record = {}; @@ -1006,7 +1015,8 @@ export class GraphiteDatasource } function nestedSeriesRegexReplacer(match: string, g1: string | number) { - return targets[g1] || match; + // Recursively replace all nested series references + return originalTargetMap[g1].replace(regex, nestedSeriesRegexReplacer) || match; } for (i = 0; i < options.targets.length; i++) { diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index d57ff4e08dc..264c033bd83 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/jaeger", "description": "Jaeger plugin for Grafana", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index 223c7845a45..2b975ac9ad9 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/mssql", "description": "MSSQL data source plugin", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/sql": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/sql": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.1", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index 3e5ef53a73c..bd4ef505390 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/mysql", "description": "MySQL data source plugin", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", "@grafana/plugin-ui": "0.10.5", - "@grafana/runtime": "12.0.0-pre", - "@grafana/sql": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/sql": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "lodash": "4.17.21", "react": "18.3.1", "rxjs": "7.8.1", "tslib": "2.8.1" }, "devDependencies": { - "@grafana/e2e-selectors": "12.0.0-pre", - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/e2e-selectors": "12.1.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index f2c1816206d..cfe3ab5c6e7 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/parca", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", - "@grafana/data": "12.0.0-pre", - "@grafana/runtime": "12.0.0-pre", - "@grafana/schema": "12.0.0-pre", - "@grafana/ui": "12.0.0-pre", + "@grafana/data": "12.1.0-pre", + "@grafana/runtime": "12.1.0-pre", + "@grafana/schema": "12.1.0-pre", + "@grafana/ui": "12.1.0-pre", "lodash": "4.17.21", "monaco-editor": "0.34.1", "react": "18.3.1", @@ -18,7 +18,7 @@ "tslib": "2.8.1" }, "devDependencies": { - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/react": "16.2.0", "@testing-library/user-event": "14.6.1", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index ca4093dd1e7..df81acbef1f 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/tempo", "description": "Grafana plugin for the Tempo data source.", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", @@ -39,7 +39,7 @@ "uuid": "11.0.5" }, "devDependencies": { - "@grafana/plugin-configs": "12.0.0-pre", + "@grafana/plugin-configs": "12.1.0-pre", "@testing-library/dom": "10.4.0", "@testing-library/jest-dom": "6.6.3", "@testing-library/react": "16.2.0", diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index 1e68d6d44e9..fa81f8a1d3f 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/zipkin", "description": "Zipkin plugin for Grafana", "private": true, - "version": "12.0.0-pre", + "version": "12.1.0-pre", "dependencies": { "@emotion/css": "11.13.5", "@grafana/data": "workspace:*", diff --git a/public/app/plugins/panel/xychart/scatter.ts b/public/app/plugins/panel/xychart/scatter.ts index d7bb53a3838..e9f36d8ed73 100644 --- a/public/app/plugins/panel/xychart/scatter.ts +++ b/public/app/plugins/panel/xychart/scatter.ts @@ -641,7 +641,8 @@ function fieldValueColors(f: Field, theme: GrafanaTheme2): FieldColorValues { let lasti = steps.length - 1; for (let i = lasti; i > 0; i--) { - conds += `v >= ${steps[i].value} ? ${i} : `; + let rhs = Number(steps[i].value); + conds += `v >= ${rhs} ? ${i} : `; } conds += '0'; diff --git a/public/app/store/configureStore.ts b/public/app/store/configureStore.ts index a5061d7a9ed..b161a695acf 100644 --- a/public/app/store/configureStore.ts +++ b/public/app/store/configureStore.ts @@ -13,6 +13,8 @@ import { folderAPI } from '../api/clients/folder'; import { iamAPI } from '../api/clients/iam'; import { playlistAPI } from '../api/clients/playlist'; import { provisioningAPI } from '../api/clients/provisioning'; +// Used by the API client generator +// PLOP_INJECT_IMPORT import { buildInitialState } from '../core/reducers/navModel'; import { addReducer, createRootReducer } from '../core/reducers/root'; import { alertingApi } from '../features/alerting/unified/api/alertingApi'; @@ -49,6 +51,8 @@ export function configureStore(initialState?: Partial) { provisioningAPI.middleware, folderAPI.middleware, advisorAPI.middleware, + // PLOP_INJECT_MIDDLEWARE + // Used by the API client generator ...extraMiddleware ), devTools: process.env.NODE_ENV !== 'production', diff --git a/public/img/icons/custom/gf-ml-alt.svg b/public/img/icons/custom/gf-ml-alt.svg new file mode 100644 index 00000000000..218d766af2e --- /dev/null +++ b/public/img/icons/custom/gf-ml-alt.svg @@ -0,0 +1 @@ + diff --git a/public/locales/cs-CZ/grafana.json b/public/locales/cs-CZ/grafana.json index 24c09600bbd..f2b73f0cced 100644 --- a/public/locales/cs-CZ/grafana.json +++ b/public/locales/cs-CZ/grafana.json @@ -2716,9 +2716,6 @@ "clear": "Vymazat vyhledávání a filtry", "text": "Nebyly nalezeny žádné výsledky pro váš dotaz" }, - "soft-delete": { - "success": "Nástěnka {{name}} byla přesunuta do Nedávno odstraněné" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index ee7c94e1abe..1ad178e3d4f 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Suche und Filter löschen", "text": "Keine Ergebnisse für Ihre Abfrage gefunden" }, - "soft-delete": { - "success": "Dashboard {{name}} in „Kürzlich gelöscht“ verschoben" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 9f6e982821d..72aae2d0ed0 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Borrar la búsqueda y los filtros", "text": "No se han encontrado resultados para tu consulta" }, - "soft-delete": { - "success": "El panel de control {{name}} se ha movido a Eliminados recientemente" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 379c2d5b74a..c1affc0b43b 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Effacer la recherche et les filtres", "text": "Aucun résultat n'a été trouvé pour votre requête" }, - "soft-delete": { - "success": "Le tableau de bord {{name}} a été déplacé vers Récemment supprimé" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/hu-HU/grafana.json b/public/locales/hu-HU/grafana.json index b45d05edbaf..389f3e8a2ba 100644 --- a/public/locales/hu-HU/grafana.json +++ b/public/locales/hu-HU/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Keresés és szűrők törlése", "text": "Nincs találat a lekérdezésre" }, - "soft-delete": { - "success": "A(z) {{name}} irányítópult áthelyezve a Nemrég töröltek közé" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/id-ID/grafana.json b/public/locales/id-ID/grafana.json index 36758020048..70da04320eb 100644 --- a/public/locales/id-ID/grafana.json +++ b/public/locales/id-ID/grafana.json @@ -2662,9 +2662,6 @@ "clear": "Hapus pencarian dan filter", "text": "Hasil untuk kueri Anda tidak ditemukan" }, - "soft-delete": { - "success": "Dasbor {{name}} dipindahkan ke Baru dihapus" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/it-IT/grafana.json b/public/locales/it-IT/grafana.json index 2c22ca5c261..e55529eedb2 100644 --- a/public/locales/it-IT/grafana.json +++ b/public/locales/it-IT/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Cancella ricerca e filtri", "text": "Nessun risultato trovato per la ricerca" }, - "soft-delete": { - "success": "Dashboard {{name}} spostato in Eliminati di recente" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/ja-JP/grafana.json b/public/locales/ja-JP/grafana.json index 0df6cfcf16c..de4771b9926 100644 --- a/public/locales/ja-JP/grafana.json +++ b/public/locales/ja-JP/grafana.json @@ -2662,9 +2662,6 @@ "clear": "検索とフィルタをクリア", "text": "クエリに一致する結果が見つかりませんでした。" }, - "soft-delete": { - "success": "ダッシュボード、{{name}}が最近削除されたに移動しました" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/ko-KR/grafana.json b/public/locales/ko-KR/grafana.json index e1b16792990..3af7b1237ed 100644 --- a/public/locales/ko-KR/grafana.json +++ b/public/locales/ko-KR/grafana.json @@ -2662,9 +2662,6 @@ "clear": "검색 및 필터 초기화", "text": "쿼리에 대해 찾은 결과 없음" }, - "soft-delete": { - "success": "{{name}} 대시보드를 '최근 삭제됨'으로 이동함" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/nl-NL/grafana.json b/public/locales/nl-NL/grafana.json index 25c1c09308c..609ea743ded 100644 --- a/public/locales/nl-NL/grafana.json +++ b/public/locales/nl-NL/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Zoekopdracht en filters wissen", "text": "Geen resultaten gevonden voor je zoekopdracht" }, - "soft-delete": { - "success": "Dashboard {{name}} is verplaatst naar Onlangs verwijderd" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/pl-PL/grafana.json b/public/locales/pl-PL/grafana.json index 6fdc8481bbe..43c7a1c70ca 100644 --- a/public/locales/pl-PL/grafana.json +++ b/public/locales/pl-PL/grafana.json @@ -2716,9 +2716,6 @@ "clear": "Wyczyść wyszukiwanie i filtry", "text": "Nie znaleziono wyników dla tego zapytania" }, - "soft-delete": { - "success": "Panel {{name}} przeniesiono do sekcji Ostatnio usunięte" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index cab0c1b91cb..dbf76560d65 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Limpar busca e filtros", "text": "Nenhum resultado encontrado para sua consulta" }, - "soft-delete": { - "success": "Painel de controle {{name}} movido para a seção \"Excluídos recentemente\"" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/pt-PT/grafana.json b/public/locales/pt-PT/grafana.json index af3cce19f54..565a52159e3 100644 --- a/public/locales/pt-PT/grafana.json +++ b/public/locales/pt-PT/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Limpar a pesquisa e os filtros", "text": "Não foram encontrados resultados para a sua consulta" }, - "soft-delete": { - "success": "Painel de controlo {{name}} movido para Eliminados recentemente" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/ru-RU/grafana.json b/public/locales/ru-RU/grafana.json index 8c1dd0a7450..14b7b36c5a7 100644 --- a/public/locales/ru-RU/grafana.json +++ b/public/locales/ru-RU/grafana.json @@ -2716,9 +2716,6 @@ "clear": "Очистить поиск и фильтры", "text": "По вашему запросу ничего не найдено" }, - "soft-delete": { - "success": "Дашборд {{name}} перемещен в последние удаленные" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/sv-SE/grafana.json b/public/locales/sv-SE/grafana.json index 190031535bd..a9596c05382 100644 --- a/public/locales/sv-SE/grafana.json +++ b/public/locales/sv-SE/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Rensa sökning och filter", "text": "Inga resultat hittades för din fråga" }, - "soft-delete": { - "success": "Instrumentpanelen {{name}} har flyttats till Nyligen raderat" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/tr-TR/grafana.json b/public/locales/tr-TR/grafana.json index 769660b1aa5..76e90cd6df0 100644 --- a/public/locales/tr-TR/grafana.json +++ b/public/locales/tr-TR/grafana.json @@ -2680,9 +2680,6 @@ "clear": "Aramayı ve filtreleri temizle", "text": "Sorgunuz için sonuç bulunamadı" }, - "soft-delete": { - "success": "{{name}} panosu Son silinenler'e taşındı" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 23dd1d2468c..97c652f3dc2 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -2662,9 +2662,6 @@ "clear": "清除搜索和筛选条件", "text": "未找到与您的查询相关的结果" }, - "soft-delete": { - "success": "数据面板 {{name}} 已移至“最近删除”" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/public/locales/zh-Hant/grafana.json b/public/locales/zh-Hant/grafana.json index b73944a5257..29ae087b7aa 100644 --- a/public/locales/zh-Hant/grafana.json +++ b/public/locales/zh-Hant/grafana.json @@ -2662,9 +2662,6 @@ "clear": "清除搜尋和篩選條件", "text": "未找到您的查詢結果" }, - "soft-delete": { - "success": "儀表板「{{name}}」已移至「最近刪除」" - }, "text-this-repository-is-read-only": "" }, "canvas": { diff --git a/scripts/drone/utils/images.star b/scripts/drone/utils/images.star index d51f9905df0..27f5cb30d47 100644 --- a/scripts/drone/utils/images.star +++ b/scripts/drone/utils/images.star @@ -31,7 +31,7 @@ images = { "drone_downstream": "grafana/drone-downstream", "docker_puppeteer": "grafana/docker-puppeteer:1.1.0", "docs": "grafana/docs-base:latest", - "cypress": "cypress/included:13.10.0", + "cypress": "cypress/included:14.3.2", "dockerize": "jwilder/dockerize:0.6.1", "shellcheck": "koalaman/shellcheck:stable", "rocky": "rockylinux:9", diff --git a/scripts/generate-rtk-apis.ts b/scripts/generate-rtk-apis.ts index 70c21e6417f..8e21370ff11 100644 --- a/scripts/generate-rtk-apis.ts +++ b/scripts/generate-rtk-apis.ts @@ -80,6 +80,7 @@ const config: ConfigFile = { filterEndpoints: ['listPlaylist', 'getPlaylist', 'createPlaylist', 'deletePlaylist', 'replacePlaylist'], tag: true, }, + // PLOP_INJECT_API_CLIENT - Used by the API client generator }, }; diff --git a/yarn.lock b/yarn.lock index cdfd94dfddf..01e155919f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1416,7 +1416,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.27.0, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.26.10, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": +"@babel/runtime@npm:7.27.0, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.24.7, @babel/runtime@npm:^7.25.0, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.25.7, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7": version: 7.27.0 resolution: "@babel/runtime@npm:7.27.0" dependencies: @@ -1692,9 +1692,9 @@ __metadata: languageName: node linkType: hard -"@cypress/request@npm:^3.0.0": - version: 3.0.1 - resolution: "@cypress/request@npm:3.0.1" +"@cypress/request@npm:^3.0.8": + version: 3.0.8 + resolution: "@cypress/request@npm:3.0.8" dependencies: aws-sign2: "npm:~0.7.0" aws4: "npm:^1.8.0" @@ -1702,19 +1702,19 @@ __metadata: combined-stream: "npm:~1.0.6" extend: "npm:~3.0.2" forever-agent: "npm:~0.6.1" - form-data: "npm:~2.3.2" - http-signature: "npm:~1.3.6" + form-data: "npm:~4.0.0" + http-signature: "npm:~1.4.0" is-typedarray: "npm:~1.0.0" isstream: "npm:~0.1.2" json-stringify-safe: "npm:~5.0.1" mime-types: "npm:~2.1.19" performance-now: "npm:^2.1.0" - qs: "npm:6.10.4" + qs: "npm:6.14.0" safe-buffer: "npm:^5.1.2" - tough-cookie: "npm:^4.1.3" + tough-cookie: "npm:^5.0.0" tunnel-agent: "npm:^0.6.0" uuid: "npm:^8.3.2" - checksum: 10/bf48bed6d6e493c05493902fb08b1d0646e7ec4300cf834816c2616f781db1a7fc447bd6f81de7c3076d738e8a6d75354e21d332f8f7ef8d9101d9b2f8e15b3a + checksum: 10/f4ee26acfed457ea017192028ff08d533052c8bae7639d8701831e691e6cd0d7d44284902feb49aa62a90c8014cf66dc2c3efc1712ad7b76e47e06f335c69981 languageName: node linkType: hard @@ -2331,9 +2331,9 @@ __metadata: languageName: node linkType: hard -"@floating-ui/react@npm:0.27.7": - version: 0.27.7 - resolution: "@floating-ui/react@npm:0.27.7" +"@floating-ui/react@npm:0.27.8": + version: 0.27.8 + resolution: "@floating-ui/react@npm:0.27.8" dependencies: "@floating-ui/react-dom": "npm:^2.1.2" "@floating-ui/utils": "npm:^0.2.9" @@ -2341,7 +2341,7 @@ __metadata: peerDependencies: react: ">=17.0.0" react-dom: ">=17.0.0" - checksum: 10/8172bffb125d957e2002961a8db06c72239d371d8ab5fdf7150a7e436cb7d532437c36df5bcfe6acb3f8e705456d6b92c299f547827b89efac49768b5b320231 + checksum: 10/9423e3b7d6298918cb14ad3cfa36ba7b88ef86f18c4c47f7870b367716e918757dc0cf9842ef4995ee784b68aebe725c28c2ae143ecfe16380d3ccfd05cacb43 languageName: node linkType: hard @@ -2485,13 +2485,13 @@ __metadata: resolution: "@grafana-plugins/grafana-azure-monitor-datasource@workspace:public/app/plugins/datasource/azuremonitor" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@kusto/monaco-kusto": "npm:^10.0.0" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" @@ -2529,13 +2529,13 @@ __metadata: resolution: "@grafana-plugins/grafana-postgresql-datasource@workspace:public/app/plugins/datasource/grafana-postgresql-datasource" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/sql": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/sql": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2560,11 +2560,11 @@ __metadata: resolution: "@grafana-plugins/grafana-pyroscope-datasource@workspace:public/app/plugins/datasource/grafana-pyroscope-datasource" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" "@testing-library/react": "npm:16.2.0" @@ -2600,12 +2600,12 @@ __metadata: resolution: "@grafana-plugins/grafana-testdata-datasource@workspace:public/app/plugins/datasource/grafana-testdata-datasource" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2681,13 +2681,13 @@ __metadata: resolution: "@grafana-plugins/mssql@workspace:public/app/plugins/datasource/mssql" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/sql": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/sql": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2712,13 +2712,13 @@ __metadata: resolution: "@grafana-plugins/mysql@workspace:public/app/plugins/datasource/mysql" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/sql": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/sql": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2743,11 +2743,11 @@ __metadata: resolution: "@grafana-plugins/parca@workspace:public/app/plugins/datasource/parca" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/plugin-configs": "npm:12.0.0-pre" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/react": "npm:16.2.0" "@testing-library/user-event": "npm:14.6.1" @@ -2775,14 +2775,14 @@ __metadata: resolution: "@grafana-plugins/stackdriver@workspace:public/app/plugins/datasource/cloud-monitoring" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/google-sdk": "npm:0.1.2" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:6.6.3" "@testing-library/react": "npm:16.2.0" @@ -2827,7 +2827,7 @@ __metadata: "@grafana/lezer-traceql": "npm:0.0.21" "@grafana/monaco-logql": "npm:^0.0.8" "@grafana/o11y-ds-frontend": "workspace:*" - "@grafana/plugin-configs": "npm:12.0.0-pre" + "@grafana/plugin-configs": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" "@grafana/runtime": "workspace:*" "@grafana/schema": "workspace:*" @@ -2911,6 +2911,29 @@ __metadata: languageName: unknown linkType: soft +"@grafana/alerting@workspace:*, @grafana/alerting@workspace:packages/grafana-alerting": + version: 0.0.0-use.local + resolution: "@grafana/alerting@workspace:packages/grafana-alerting" + dependencies: + "@grafana/tsconfig": "npm:^2.0.0" + "@reduxjs/toolkit": "npm:^2.7.0" + "@rtk-query/codegen-openapi": "npm:^2.0.0" + "@types/lodash": "npm:^4" + "@types/react": "npm:18.3.18" + "@types/react-dom": "npm:18.3.5" + lodash: "npm:^4.17.21" + react: "npm:18.3.1" + react-dom: "npm:18.3.1" + type-fest: "npm:^4.40.0" + typescript: "npm:5.7.3" + peerDependencies: + "@grafana/runtime": ^12.0.0-pre + "@grafana/ui": ^12.0.0-pre + react: ^18.0.0 + react-dom: ^18.0.0 + languageName: unknown + linkType: soft + "@grafana/async-query-data@npm:0.3.0": version: 0.3.0 resolution: "@grafana/async-query-data@npm:0.3.0" @@ -2940,12 +2963,12 @@ __metadata: languageName: node linkType: hard -"@grafana/data@npm:12.0.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": +"@grafana/data@npm:12.1.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": version: 0.0.0-use.local resolution: "@grafana/data@workspace:packages/grafana-data" dependencies: "@braintree/sanitize-url": "npm:7.0.1" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@rollup/plugin-node-resolve": "npm:16.0.0" "@types/d3-interpolate": "npm:^3.0.0" @@ -2966,7 +2989,7 @@ __metadata: fast_array_intersect: "npm:1.1.0" history: "npm:4.10.1" lodash: "npm:4.17.21" - marked: "npm:15.0.6" + marked: "npm:15.0.11" marked-mangle: "npm:1.1.10" moment: "npm:2.30.1" moment-timezone: "npm:0.5.47" @@ -2993,7 +3016,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/e2e-selectors@npm:12.0.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": +"@grafana/e2e-selectors@npm:12.1.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": version: 0.0.0-use.local resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors" dependencies: @@ -3095,9 +3118,9 @@ __metadata: "@babel/preset-env": "npm:7.26.9" "@babel/preset-react": "npm:7.26.3" "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@leeoniya/ufuzzy": "npm:1.0.18" "@rollup/plugin-node-resolve": "npm:16.0.0" "@testing-library/dom": "npm:10.4.0" @@ -3204,13 +3227,13 @@ __metadata: resolution: "@grafana/o11y-ds-frontend@workspace:packages/grafana-o11y-ds-frontend" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.1.2" "@testing-library/react": "npm:16.2.0" @@ -3234,7 +3257,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/plugin-configs@npm:12.0.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs": +"@grafana/plugin-configs@npm:12.1.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs": version: 0.0.0-use.local resolution: "@grafana/plugin-configs@workspace:packages/grafana-plugin-configs" dependencies: @@ -3333,14 +3356,14 @@ __metadata: resolution: "@grafana/prometheus@workspace:packages/grafana-prometheus" dependencies: "@emotion/css": "npm:11.13.5" - "@floating-ui/react": "npm:0.27.7" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@floating-ui/react": "npm:0.27.8" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.18" "@lezer/common": "npm:1.2.3" @@ -3396,16 +3419,16 @@ __metadata: languageName: unknown linkType: soft -"@grafana/runtime@npm:12.0.0-pre, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime": +"@grafana/runtime@npm:12.1.0-pre, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime": version: 0.0.0-use.local resolution: "@grafana/runtime@workspace:packages/grafana-runtime" dependencies: - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/faro-web-sdk": "npm:^1.13.2" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@rollup/plugin-node-resolve": "npm:16.0.0" "@rollup/plugin-terser": "npm:0.4.4" "@testing-library/dom": "npm:10.4.0" @@ -3439,11 +3462,11 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:6.10.0": - version: 6.10.0 - resolution: "@grafana/scenes-react@npm:6.10.0" +"@grafana/scenes-react@npm:6.10.2": + version: 6.10.2 + resolution: "@grafana/scenes-react@npm:6.10.2" dependencies: - "@grafana/scenes": "npm:6.10.0" + "@grafana/scenes": "npm:6.10.2" lru-cache: "npm:^10.2.2" react-use: "npm:^17.4.0" peerDependencies: @@ -3455,13 +3478,13 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/559de67feb4005173bc4a08c0c968a32e2e88fc362192825bdae0e5f3e83cf5e78bb4c2a373c66ac2f38a90333622e6623e372989910baf48121135d032291f1 + checksum: 10/f87d51654becdbb9c703b04d60532413ccb3d963e6addb6577d4fe4b96a99d52e792cd12c6fed102b2a01c07266cac29061d1f38cb43e5929ea892c6093ea671 languageName: node linkType: hard -"@grafana/scenes@npm:6.10.0": - version: 6.10.0 - resolution: "@grafana/scenes@npm:6.10.0" +"@grafana/scenes@npm:6.10.2": + version: 6.10.2 + resolution: "@grafana/scenes@npm:6.10.2" dependencies: "@floating-ui/react": "npm:^0.26.16" "@leeoniya/ufuzzy": "npm:^1.0.16" @@ -3479,11 +3502,11 @@ __metadata: react: ^18.0.0 react-dom: ^18.0.0 react-router-dom: ^6.28.0 - checksum: 10/4f89f59374f6bd20fcd0afec7d236e4e1754f43a379348e704c0021952b241ece2d04134de8d668bc4d4fa286ebcf1c7874403c70004e85b1330bd5f76bb5423 + checksum: 10/4a6acc3e3f2ceb3316fcdb8cba79d54b3fca0130f05d480349bfc726ca6624cba6acf2333d96a292cc1a573f578274b1e85b7ba7c0096deda3fb02feb0179e3d languageName: node linkType: hard -"@grafana/schema@npm:12.0.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": +"@grafana/schema@npm:12.1.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": version: 0.0.0-use.local resolution: "@grafana/schema@workspace:packages/grafana-schema" dependencies: @@ -3501,17 +3524,17 @@ __metadata: languageName: unknown linkType: soft -"@grafana/sql@npm:12.0.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql": +"@grafana/sql@npm:12.1.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql": version: 0.0.0-use.local resolution: "@grafana/sql@workspace:packages/grafana-sql" dependencies: "@emotion/css": "npm:11.13.5" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/plugin-ui": "npm:0.10.5" - "@grafana/runtime": "npm:12.0.0-pre" + "@grafana/runtime": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:12.0.0-pre" + "@grafana/ui": "npm:12.1.0-pre" "@react-awesome-query-builder/ui": "npm:6.6.14" "@testing-library/dom": "npm:10.4.0" "@testing-library/jest-dom": "npm:^6.1.2" @@ -3552,7 +3575,7 @@ __metadata: languageName: node linkType: hard -"@grafana/ui@npm:12.0.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": +"@grafana/ui@npm:12.1.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": version: 0.0.0-use.local resolution: "@grafana/ui@workspace:packages/grafana-ui" dependencies: @@ -3561,11 +3584,11 @@ __metadata: "@emotion/react": "npm:11.14.0" "@emotion/serialize": "npm:1.3.3" "@faker-js/faker": "npm:^9.0.0" - "@floating-ui/react": "npm:0.27.7" - "@grafana/data": "npm:12.0.0-pre" - "@grafana/e2e-selectors": "npm:12.0.0-pre" + "@floating-ui/react": "npm:0.27.8" + "@grafana/data": "npm:12.1.0-pre" + "@grafana/e2e-selectors": "npm:12.1.0-pre" "@grafana/faro-web-sdk": "npm:^1.13.2" - "@grafana/schema": "npm:12.0.0-pre" + "@grafana/schema": "npm:12.1.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.18" @@ -3800,10 +3823,10 @@ __metadata: languageName: node linkType: hard -"@inquirer/figures@npm:^1.0.8": - version: 1.0.8 - resolution: "@inquirer/figures@npm:1.0.8" - checksum: 10/0e5e4fbb15e799e818c598fcc3558ef076daf78662149711b046723fd6316381e95f7d5573d6ef0062095ad22c6ac98833033f0948df5c722932107a567fd9c3 +"@inquirer/figures@npm:^1.0.3, @inquirer/figures@npm:^1.0.8": + version: 1.0.11 + resolution: "@inquirer/figures@npm:1.0.11" + checksum: 10/357ddd2e83718bc3c9189d518b93fd69099af9c860354df9a5ac0ec024cb5df1228ae4608d2de7625624d2adcd047db813f29426a610eaae7b9e449f8c753c6b languageName: node linkType: hard @@ -6504,6 +6527,28 @@ __metadata: languageName: node linkType: hard +"@reduxjs/toolkit@npm:^2.7.0": + version: 2.7.0 + resolution: "@reduxjs/toolkit@npm:2.7.0" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@standard-schema/utils": "npm:^0.3.0" + immer: "npm:^10.0.3" + redux: "npm:^5.0.1" + redux-thunk: "npm:^3.1.0" + reselect: "npm:^5.1.0" + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + checksum: 10/cc264efc95f9ebeafa469bf1040d106a33768a802e6f46aa678bf9f26822d049c18b5f10864aa8badb2e62febe58e242860256174528e62b09e8f897d32cd182 + languageName: node + linkType: hard + "@remix-run/router@npm:1.19.1": version: 1.19.1 resolution: "@remix-run/router@npm:1.19.1" @@ -7023,6 +7068,20 @@ __metadata: languageName: node linkType: hard +"@standard-schema/spec@npm:^1.0.0": + version: 1.0.0 + resolution: "@standard-schema/spec@npm:1.0.0" + checksum: 10/aee780cc1431888ca4b9aba9b24ffc8f3073fc083acc105e3951481478a2f4dc957796931b2da9e2d8329584cf211e4542275f188296c1cdff3ed44fd93a8bc8 + languageName: node + linkType: hard + +"@standard-schema/utils@npm:^0.3.0": + version: 0.3.0 + resolution: "@standard-schema/utils@npm:0.3.0" + checksum: 10/7084f875d322792f2e0a5904009434c8374b9345b09ba89828b68fd56fa3c2b366d35bf340d9e8c72736ef01793c2f70d350c372ed79845dc3566c58d34b4b51 + languageName: node + linkType: hard + "@storybook/addon-a11y@npm:^8.6.2": version: 8.6.2 resolution: "@storybook/addon-a11y@npm:8.6.2" @@ -9140,6 +9199,13 @@ __metadata: languageName: node linkType: hard +"@types/fined@npm:*": + version: 1.1.5 + resolution: "@types/fined@npm:1.1.5" + checksum: 10/7a9e58904ac95205a989046dbfb3f5c91f5f07664d8d3e4b2d4e25777e5478252d8cea9637c3dc215526a0d2fb3ab3681047183d3447af00ce934466f6569f56 + languageName: node + linkType: hard + "@types/fs-extra@npm:^11.0.4": version: 11.0.4 resolution: "@types/fs-extra@npm:11.0.4" @@ -9265,6 +9331,16 @@ __metadata: languageName: node linkType: hard +"@types/inquirer@npm:^9.0.3": + version: 9.0.7 + resolution: "@types/inquirer@npm:9.0.7" + dependencies: + "@types/through": "npm:*" + rxjs: "npm:^7.2.0" + checksum: 10/84cefdd10d7ca747ae2338ea35518020abbc28f7670ade446e367c4cd333153618b374d30830253f31a9567dd26e7f3093fb3cd20210af5ba27a1a91b89bb97e + languageName: node + linkType: hard + "@types/is-hotkey@npm:0.1.10": version: 0.1.10 resolution: "@types/is-hotkey@npm:0.1.10" @@ -9371,6 +9447,16 @@ __metadata: languageName: node linkType: hard +"@types/liftoff@npm:^4.0.3": + version: 4.0.3 + resolution: "@types/liftoff@npm:4.0.3" + dependencies: + "@types/fined": "npm:*" + "@types/node": "npm:*" + checksum: 10/ee38489d296f14caef85fdeb9a7de45232e0c221bdcdd97ccae3f720c2d12315377198790c228d569e64eed26b19ae132fa119b7e675b552a69af3b8538807f3 + languageName: node + linkType: hard + "@types/lodash.memoize@npm:^4.1.7": version: 4.1.7 resolution: "@types/lodash.memoize@npm:4.1.7" @@ -9380,7 +9466,14 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:*, @types/lodash@npm:4.17.15, @types/lodash@npm:^4.14.172": +"@types/lodash@npm:*, @types/lodash@npm:^4, @types/lodash@npm:^4.14.172": + version: 4.17.16 + resolution: "@types/lodash@npm:4.17.16" + checksum: 10/9a8bb7471a7521bd65d528e1bd14f79819a3eeb6f8a35a8a44649a7d773775c0813e93fd93bd32ccf350bb076c0bf02c6d47877c4625f526f6dd4d283c746aec + languageName: node + linkType: hard + +"@types/lodash@npm:4.17.15": version: 4.17.15 resolution: "@types/lodash@npm:4.17.15" checksum: 10/27b348b5971b9c670215331b52448a13d7d65bf1fbd320a7049c9c153c1186ff5d116ba75f05f07d32d7ece8a992b26a30c7bdc9be22a3d1e4e3e6068aa04603 @@ -9898,9 +9991,9 @@ __metadata: linkType: hard "@types/symlink-or-copy@npm:^1.2.0": - version: 1.2.2 - resolution: "@types/symlink-or-copy@npm:1.2.2" - checksum: 10/fb8fc2a356d1d7ab222bbea772ca6bf1b4a90b1f14ea46c1522643d3afd018f3b938ead7ba04af88175432a07497c02904bf428008505acf8a4745f1bc6e3892 + version: 1.2.0 + resolution: "@types/symlink-or-copy@npm:1.2.0" + checksum: 10/554d9255387a9da98b685fe74b70d8b123063cf78b8f3be0de2c768cb0f37515d1edb0334be908585d77c6409e79202faf1a66ee8e78bb06ca168a2028d0c3f3 languageName: node linkType: hard @@ -9920,6 +10013,15 @@ __metadata: languageName: node linkType: hard +"@types/through@npm:*": + version: 0.0.33 + resolution: "@types/through@npm:0.0.33" + dependencies: + "@types/node": "npm:*" + checksum: 10/fd0b73f873a64ed5366d1d757c42e5dbbb2201002667c8958eda7ca02fff09d73de91360572db465ee00240c32d50c6039ea736d8eca374300f9664f93e8da39 + languageName: node + linkType: hard + "@types/tinycolor2@npm:1.4.6": version: 1.4.6 resolution: "@types/tinycolor2@npm:1.4.6" @@ -10825,6 +10927,16 @@ __metadata: languageName: node linkType: hard +"aggregate-error@npm:^4.0.0": + version: 4.0.1 + resolution: "aggregate-error@npm:4.0.1" + dependencies: + clean-stack: "npm:^4.0.0" + indent-string: "npm:^5.0.0" + checksum: 10/bb3ffdfd13447800fff237c2cba752c59868ee669104bb995dfbbe0b8320e967d679e683dabb640feb32e4882d60258165cde0baafc4cd467cc7d275a13ad6b5 + languageName: node + linkType: hard + "ajv-draft-04@npm:^1.0.0": version: 1.0.0 resolution: "ajv-draft-04@npm:1.0.0" @@ -11123,6 +11235,13 @@ __metadata: languageName: node linkType: hard +"array-each@npm:^1.0.1": + version: 1.0.1 + resolution: "array-each@npm:1.0.1" + checksum: 10/eb2393c1200003993d97dab2b280aa01e6ca339b383198e5d250cc8cd31f8012a0c22b66f275401a80e89e21bfab420e0f4c77c295637dea525fe0e152ba2300 + languageName: node + linkType: hard + "array-flatten@npm:1.1.1": version: 1.1.1 resolution: "array-flatten@npm:1.1.1" @@ -11151,6 +11270,13 @@ __metadata: languageName: node linkType: hard +"array-slice@npm:^1.0.0": + version: 1.1.0 + resolution: "array-slice@npm:1.1.0" + checksum: 10/3c8ecc7eefe104c97e2207e1d5644be160924c89e08b1807f3cad77f4a8fb10150fc275ebfab90dc02064d178b010cad31b69c9386769d172da270be5e233c51 + languageName: node + linkType: hard + "array-tree-filter@npm:^2.1.0": version: 2.1.0 resolution: "array-tree-filter@npm:2.1.0" @@ -11445,13 +11571,6 @@ __metadata: languageName: node linkType: hard -"b4a@npm:^1.6.4": - version: 1.6.7 - resolution: "b4a@npm:1.6.7" - checksum: 10/1ac056e3bce378d4d3e570e57319360a9d3125ab6916a1921b95bea33d9ee646698ebc75467561fd6fcc80ff697612124c89bb9b95e80db94c6dc23fcb977705 - languageName: node - linkType: hard - "babel-jest@npm:29.7.0, babel-jest@npm:^29.7.0": version: 29.7.0 resolution: "babel-jest@npm:29.7.0" @@ -11614,13 +11733,6 @@ __metadata: languageName: node linkType: hard -"bare-events@npm:^2.2.0": - version: 2.5.4 - resolution: "bare-events@npm:2.5.4" - checksum: 10/135ef380b13f554ca2c6905bdbcfac8edae08fce85b7f953fa01f09a9f5b0da6a25e414111659bc9a6118216f0dd1f732016acd11ce91517f2afb26ebeb4b721 - languageName: node - linkType: hard - "baron@npm:3.0.3": version: 3.0.3 resolution: "baron@npm:3.0.3" @@ -12205,6 +12317,17 @@ __metadata: languageName: node linkType: hard +"capital-case@npm:^1.0.4": + version: 1.0.4 + resolution: "capital-case@npm:1.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10/41fa8fa87f6d24d0835a2b4a9341a3eaecb64ac29cd7c5391f35d6175a0fa98ab044e7f2602e1ec3afc886231462ed71b5b80c590b8b41af903ec2c15e5c5931 + languageName: node + linkType: hard + "case-sensitive-paths-webpack-plugin@npm:^2.4.0": version: 2.4.0 resolution: "case-sensitive-paths-webpack-plugin@npm:2.4.0" @@ -12283,10 +12406,10 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^5.2.0": - version: 5.3.0 - resolution: "chalk@npm:5.3.0" - checksum: 10/6373caaab21bd64c405bfc4bd9672b145647fc9482657b5ea1d549b3b2765054e9d3d928870cdf764fb4aad67555f5061538ff247b8310f110c5c888d92397ea +"chalk@npm:^5.2.0, chalk@npm:^5.3.0": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: 10/29df3ffcdf25656fed6e95962e2ef86d14dfe03cd50e7074b06bad9ffbbf6089adbb40f75c00744d843685c8d008adaf3aed31476780312553caf07fa86e5bc7 languageName: node linkType: hard @@ -12297,6 +12420,26 @@ __metadata: languageName: node linkType: hard +"change-case@npm:^4.1.2": + version: 4.1.2 + resolution: "change-case@npm:4.1.2" + dependencies: + camel-case: "npm:^4.1.2" + capital-case: "npm:^1.0.4" + constant-case: "npm:^3.0.4" + dot-case: "npm:^3.0.4" + header-case: "npm:^2.0.4" + no-case: "npm:^3.0.4" + param-case: "npm:^3.0.4" + pascal-case: "npm:^3.1.2" + path-case: "npm:^3.0.4" + sentence-case: "npm:^3.0.4" + snake-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/e4bc4a093a1f7cce8b33896665cf9e456e3bc3cc0def2ad7691b1994cfca99b3188d0a513b16855b01a6bd20692fcde12a7d4d87a5615c4c515bbbf0e651f116 + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -12454,10 +12597,10 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^4.0.0": - version: 4.0.0 - resolution: "ci-info@npm:4.0.0" - checksum: 10/c983bb7ff1b06648f4a47432201abbd58291147d8ab5043dbb5c03e1a0e3fb2347f40d29b66a3044f28ffeb5dade01ac35aa6bd4e7464a44d9a49a3d7532415a +"ci-info@npm:^4.0.0, ci-info@npm:^4.1.0": + version: 4.2.0 + resolution: "ci-info@npm:4.2.0" + checksum: 10/928d8457f3476ffc4a66dec93b9cdf1944d5e60dba69fbd6a0fc95b652386f6ef64857f6e32372533210ef6d8954634af2c7693d7c07778ee015f3629a5e0dd9 languageName: node linkType: hard @@ -12500,6 +12643,15 @@ __metadata: languageName: node linkType: hard +"clean-stack@npm:^4.0.0": + version: 4.2.0 + resolution: "clean-stack@npm:4.2.0" + dependencies: + escape-string-regexp: "npm:5.0.0" + checksum: 10/373f656a31face5c615c0839213b9b542a0a48057abfb1df66900eab4dc2a5c6097628e4a0b5aa559cdfc4e66f8a14ea47be9681773165a44470ef5fb8ccc172 + languageName: node + linkType: hard + "cli-cursor@npm:3.1.0, cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -12509,6 +12661,15 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: "npm:^5.0.0" + checksum: 10/1eb9a3f878b31addfe8d82c6d915ec2330cec8447ab1f117f4aa34f0137fbb3137ec3466e1c9a65bcb7557f6e486d343f2da57f253a2f668d691372dfa15c090 + languageName: node + linkType: hard + "cli-spinners@npm:2.6.1": version: 2.6.1 resolution: "cli-spinners@npm:2.6.1" @@ -12516,23 +12677,23 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0": +"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10/a0a863f442df35ed7294424f5491fa1756bd8d2e4ff0c8736531d886cec0ece4d85e8663b77a5afaf1d296e3cbbebff92e2e99f52bbea89b667cbe789b994794 languageName: node linkType: hard -"cli-table3@npm:~0.6.1": - version: 0.6.3 - resolution: "cli-table3@npm:0.6.3" +"cli-table3@npm:~0.6.5": + version: 0.6.5 + resolution: "cli-table3@npm:0.6.5" dependencies: "@colors/colors": "npm:1.5.0" string-width: "npm:^4.2.0" dependenciesMeta: "@colors/colors": optional: true - checksum: 10/8d82b75be7edc7febb1283dc49582a521536527cba80af62a2e4522a0ee39c252886a1a2f02d05ae9d753204dbcffeb3a40d1358ee10dccd7fe8d935cfad3f85 + checksum: 10/8dca71256f6f1367bab84c33add3f957367c7c43750a9828a4212ebd31b8df76bd7419d386e3391ac7419698a8540c25f1a474584028f35b170841cde2e055c5 languageName: node linkType: hard @@ -12761,7 +12922,7 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": +"combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" dependencies: @@ -12968,6 +13129,17 @@ __metadata: languageName: node linkType: hard +"constant-case@npm:^3.0.4": + version: 3.0.4 + resolution: "constant-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case: "npm:^2.0.2" + checksum: 10/6c3346d51afc28d9fae922e966c68eb77a19d94858dba230dd92d7b918b37d36db50f0311e9ecf6847e43e934b1c01406a0936973376ab17ec2c471fbcfb2cf3 + languageName: node + linkType: hard + "constants-browserify@npm:^1.0.0": version: 1.0.0 resolution: "constants-browserify@npm:1.0.0" @@ -13727,11 +13899,11 @@ __metadata: languageName: node linkType: hard -"cypress@npm:13.10.0": - version: 13.10.0 - resolution: "cypress@npm:13.10.0" +"cypress@npm:14.3.2": + version: 14.3.2 + resolution: "cypress@npm:14.3.2" dependencies: - "@cypress/request": "npm:^3.0.0" + "@cypress/request": "npm:^3.0.8" "@cypress/xvfb": "npm:^1.2.4" "@types/sinonjs__fake-timers": "npm:8.1.1" "@types/sizzle": "npm:^2.3.2" @@ -13742,8 +13914,9 @@ __metadata: cachedir: "npm:^2.3.0" chalk: "npm:^4.1.0" check-more-types: "npm:^2.24.0" + ci-info: "npm:^4.1.0" cli-cursor: "npm:^3.1.0" - cli-table3: "npm:~0.6.1" + cli-table3: "npm:~0.6.5" commander: "npm:^6.2.1" common-tags: "npm:^1.8.0" dayjs: "npm:^1.10.4" @@ -13756,7 +13929,6 @@ __metadata: figures: "npm:^3.2.0" fs-extra: "npm:^9.1.0" getos: "npm:^3.2.1" - is-ci: "npm:^3.0.1" is-installed-globally: "npm:~0.4.0" lazy-ass: "npm:^1.6.0" listr2: "npm:^3.8.3" @@ -13768,14 +13940,15 @@ __metadata: process: "npm:^0.11.10" proxy-from-env: "npm:1.0.0" request-progress: "npm:^3.0.0" - semver: "npm:^7.5.3" + semver: "npm:^7.7.1" supports-color: "npm:^8.1.1" - tmp: "npm:~0.2.1" + tmp: "npm:~0.2.3" + tree-kill: "npm:1.2.2" untildify: "npm:^4.0.0" yauzl: "npm:^2.10.0" bin: cypress: bin/cypress - checksum: 10/d3dbce9a00a108d76d0f91ba419106be24a6ad2dfa3c4a3cbb70db7c053b84e9cff2465b31c77ed3a40574bdba6f908b9aa247bdc7e8115f23b622f9ef05d51d + checksum: 10/895c12d4069c47c80e3e99f7031cb54d4d708f4989bdeac50fb5a9bef43b84e3dbb274aab60b214e2fe83049eee098224a1e2830809954f8250a64f521a98aa5 languageName: node linkType: hard @@ -14431,6 +14604,22 @@ __metadata: languageName: node linkType: hard +"del@npm:^7.1.0": + version: 7.1.0 + resolution: "del@npm:7.1.0" + dependencies: + globby: "npm:^13.1.2" + graceful-fs: "npm:^4.2.10" + is-glob: "npm:^4.0.3" + is-path-cwd: "npm:^3.0.0" + is-path-inside: "npm:^4.0.0" + p-map: "npm:^5.5.0" + rimraf: "npm:^3.0.2" + slash: "npm:^4.0.0" + checksum: 10/93527e78e95125809ff20a112814b00648ed64af204be1a565862698060c9ec8f5c5fe1a4866725acfde9b0da6423f4b7a7642c1d38cd4b05cbeb643a7b089e3 + languageName: node + linkType: hard + "delaunator@npm:5": version: 5.0.0 resolution: "delaunator@npm:5.0.0" @@ -14489,6 +14678,13 @@ __metadata: languageName: node linkType: hard +"detect-file@npm:^1.0.0": + version: 1.0.0 + resolution: "detect-file@npm:1.0.0" + checksum: 10/1861e4146128622e847abe0e1ed80fef01e78532665858a792267adf89032b7a9c698436137707fcc6f02956c2a6a0052d6a0cef5be3d4b76b1ff0da88e2158a + languageName: node + linkType: hard + "detect-indent@npm:^5.0.0": version: 5.0.0 resolution: "detect-indent@npm:5.0.0" @@ -14737,13 +14933,13 @@ __metadata: linkType: hard "domutils@npm:^3.0.1, domutils@npm:^3.1.0": - version: 3.2.2 - resolution: "domutils@npm:3.2.2" + version: 3.1.0 + resolution: "domutils@npm:3.1.0" dependencies: dom-serializer: "npm:^2.0.0" domelementtype: "npm:^2.3.0" domhandler: "npm:^5.0.3" - checksum: 10/2e08842151aa406f50fe5e6d494f4ec73c2373199fa00d1f77b56ec604e566b7f226312ae35ab8160bb7f27a27c7285d574c8044779053e499282ca9198be210 + checksum: 10/9a169a6e57ac4c738269a73ab4caf785114ed70e46254139c1bbc8144ac3102aacb28a6149508395ae34aa5d6a40081f4fa5313855dc8319c6d8359866b6dfea languageName: node linkType: hard @@ -14892,6 +15088,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.3.0": + version: 10.4.0 + resolution: "emoji-regex@npm:10.4.0" + checksum: 10/76bb92c5bcf0b6980d37e535156231e4a9d0aa6ab3b9f5eabf7690231d5aa5d5b8e516f36e6804cbdd0f1c23dfef2a60c40ab7bb8aedd890584281a565b97c50 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -15430,6 +15633,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 10/20daabe197f3cb198ec28546deebcf24b3dbb1a5a269184381b3116d12f0532e06007f4bc8da25669d6a7f8efb68db0758df4cd981f57bc5b57f521a3e12c59e + languageName: node + linkType: hard + "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -16007,6 +16217,15 @@ __metadata: languageName: node linkType: hard +"expand-tilde@npm:^2.0.0, expand-tilde@npm:^2.0.2": + version: 2.0.2 + resolution: "expand-tilde@npm:2.0.2" + dependencies: + homedir-polyfill: "npm:^1.0.1" + checksum: 10/2efe6ed407d229981b1b6ceb552438fbc9e5c7d6a6751ad6ced3e0aa5cf12f0b299da695e90d6c2ac79191b5c53c613e508f7149e4573abfbb540698ddb7301a + languageName: node + linkType: hard + "expect@npm:^29.0.0, expect@npm:^29.7.0": version: 29.7.0 resolution: "expect@npm:29.7.0" @@ -16075,14 +16294,14 @@ __metadata: languageName: node linkType: hard -"extend@npm:~3.0.2": +"extend@npm:^3.0.2, extend@npm:~3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" checksum: 10/59e89e2dc798ec0f54b36d82f32a27d5f6472c53974f61ca098db5d4648430b725387b53449a34df38fd0392045434426b012f302b3cc049a6500ccf82877e4e languageName: node linkType: hard -"external-editor@npm:^3.0.3": +"external-editor@npm:^3.0.3, external-editor@npm:^3.1.0": version: 3.1.0 resolution: "external-editor@npm:3.1.0" dependencies: @@ -16145,14 +16364,14 @@ __metadata: languageName: node linkType: hard -"fast-fifo@npm:^1.3.2": +"fast-fifo@npm:^1.1.0": version: 1.3.2 resolution: "fast-fifo@npm:1.3.2" checksum: 10/6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 languageName: node linkType: hard -"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3": +"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2, fast-glob@npm:^3.3.3": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" dependencies: @@ -16236,11 +16455,11 @@ __metadata: linkType: hard "fastq@npm:^1.13.0, fastq@npm:^1.6.0": - version: 1.19.1 - resolution: "fastq@npm:1.19.1" + version: 1.17.1 + resolution: "fastq@npm:1.17.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10/75679dc226316341c4f2a6b618571f51eac96779906faecd8921b984e844d6ae42fabb2df69b1071327d398d5716693ea9c9c8941f64ac9e89ec2032ce59d730 + checksum: 10/a443180068b527dd7b3a63dc7f2a47ceca2f3e97b9c00a1efe5538757e6cc4056a3526df94308075d7727561baf09ebaa5b67da8dcbddb913a021c5ae69d1f69 languageName: node linkType: hard @@ -16465,6 +16684,31 @@ __metadata: languageName: node linkType: hard +"findup-sync@npm:^5.0.0": + version: 5.0.0 + resolution: "findup-sync@npm:5.0.0" + dependencies: + detect-file: "npm:^1.0.0" + is-glob: "npm:^4.0.3" + micromatch: "npm:^4.0.4" + resolve-dir: "npm:^1.0.1" + checksum: 10/576716c77a0e8330b17ae9cba27d1fda8907c8cda7bf33a47f1999e16e089bfc6df4dd62933e0760f430736183c054348c34aa45dd882d49c8c098f55b89ee1d + languageName: node + linkType: hard + +"fined@npm:^2.0.0": + version: 2.0.0 + resolution: "fined@npm:2.0.0" + dependencies: + expand-tilde: "npm:^2.0.2" + is-plain-object: "npm:^5.0.0" + object.defaults: "npm:^1.1.0" + object.pick: "npm:^1.3.0" + parse-filepath: "npm:^1.0.2" + checksum: 10/3c5125a5b4eabb9a9569a9bc55a629d4f463ea8926cca9ee0b54d0e0351715aaed7f245a5372defbb59a0aaccdfefae9dc1a9ac0c7b1167ba8537284db956852 + languageName: node + linkType: hard + "fishery@npm:^2.2.2": version: 2.2.3 resolution: "fishery@npm:2.2.3" @@ -16474,6 +16718,13 @@ __metadata: languageName: node linkType: hard +"flagged-respawn@npm:^2.0.0": + version: 2.0.0 + resolution: "flagged-respawn@npm:2.0.0" + checksum: 10/1b48b1aca4614833bc1c1aa5a7af09232bc284168334b85492e580b51f9bc5ee1a59e4d830934ca91f6dc483300043ac8fe17f88f97f289153f6fcbd8dc9171b + languageName: node + linkType: hard + "flat-cache@npm:^3.0.4": version: 3.1.1 resolution: "flat-cache@npm:3.1.1" @@ -16541,6 +16792,22 @@ __metadata: languageName: node linkType: hard +"for-in@npm:^1.0.1": + version: 1.0.2 + resolution: "for-in@npm:1.0.2" + checksum: 10/09f4ae93ce785d253ac963d94c7f3432d89398bf25ac7a24ed034ca393bf74380bdeccc40e0f2d721a895e54211b07c8fad7132e8157827f6f7f059b70b4043d + languageName: node + linkType: hard + +"for-own@npm:^1.0.0": + version: 1.0.0 + resolution: "for-own@npm:1.0.0" + dependencies: + for-in: "npm:^1.0.1" + checksum: 10/233238f6e9060f61295a7f7c7e3e9de11aaef57e82a108e7f350dc92ae84fe2189848077ac4b8db47fd8edd45337ed8d9f66bd0b1efa4a6a1b3f38aa21b7ab2e + languageName: node + linkType: hard + "foreground-child@npm:^3.1.0": version: 3.1.1 resolution: "foreground-child@npm:3.1.1" @@ -16604,25 +16871,15 @@ __metadata: languageName: node linkType: hard -"form-data@npm:^4.0.0": - version: 4.0.0 - resolution: "form-data@npm:4.0.0" +"form-data@npm:^4.0.0, form-data@npm:~4.0.0": + version: 4.0.2 + resolution: "form-data@npm:4.0.2" dependencies: asynckit: "npm:^0.4.0" combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" mime-types: "npm:^2.1.12" - checksum: 10/7264aa760a8cf09482816d8300f1b6e2423de1b02bba612a136857413fdc96d7178298ced106817655facc6b89036c6e12ae31c9eb5bdc16aabf502ae8a5d805 - languageName: node - linkType: hard - -"form-data@npm:~2.3.2": - version: 2.3.3 - resolution: "form-data@npm:2.3.3" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.6" - mime-types: "npm:^2.1.12" - checksum: 10/1b6f3ccbf4540e535887b42218a2431a3f6cfdea320119c2affa2a7a374ad8fdd1e60166fc865181f45d49b1684c3e90e7b2190d3fe016692957afb9cf0d0d02 + checksum: 10/82c65b426af4a40090e517a1bc9057f76970b4c6043e37aa49859c447d88553e77d4cc5626395079a53d2b0889ba5f2a49f3900db3ad3f3f1bf76613532572fb languageName: node linkType: hard @@ -16906,6 +17163,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.0.0": + version: 1.3.0 + resolution: "get-east-asian-width@npm:1.3.0" + checksum: 10/8e8e779eb28701db7fdb1c8cab879e39e6ae23f52dadd89c8aed05869671cee611a65d4f8557b83e981428623247d8bc5d0c7a4ef3ea7a41d826e73600112ad8 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7": version: 1.2.7 resolution: "get-intrinsic@npm:1.2.7" @@ -17222,6 +17486,17 @@ __metadata: languageName: node linkType: hard +"global-modules@npm:^1.0.0": + version: 1.0.0 + resolution: "global-modules@npm:1.0.0" + dependencies: + global-prefix: "npm:^1.0.1" + is-windows: "npm:^1.0.1" + resolve-dir: "npm:^1.0.0" + checksum: 10/e4031a01c0c7401349bb69e1499c7268d636552b16374c0002d677c7a6185da6782a2927a7a3a7c046eb7be97cd26b3c7b1b736f9818ecc7ac09e9d61449065e + languageName: node + linkType: hard + "global-modules@npm:^2.0.0": version: 2.0.0 resolution: "global-modules@npm:2.0.0" @@ -17231,6 +17506,19 @@ __metadata: languageName: node linkType: hard +"global-prefix@npm:^1.0.1": + version: 1.0.2 + resolution: "global-prefix@npm:1.0.2" + dependencies: + expand-tilde: "npm:^2.0.2" + homedir-polyfill: "npm:^1.0.1" + ini: "npm:^1.3.4" + is-windows: "npm:^1.0.1" + which: "npm:^1.2.14" + checksum: 10/68cf78f81cd85310095ca1f0ec22dd5f43a1059646b2c7b3fc4a7c9ce744356e66ca833adda4e5753e38021847aaec393a159a029ba2d257c08ccb3f00ca2899 + languageName: node + linkType: hard + "global-prefix@npm:^3.0.0": version: 3.0.0 resolution: "global-prefix@npm:3.0.0" @@ -17303,6 +17591,19 @@ __metadata: languageName: node linkType: hard +"globby@npm:^13.1.2, globby@npm:^13.2.2": + version: 13.2.2 + resolution: "globby@npm:13.2.2" + dependencies: + dir-glob: "npm:^3.0.1" + fast-glob: "npm:^3.3.0" + ignore: "npm:^5.2.4" + merge2: "npm:^1.4.1" + slash: "npm:^4.0.0" + checksum: 10/4494a9d2162a7e4d327988b26be66d8eab87d7f59a83219e74b065e2c3ced23698f68fb10482bf9337133819281803fb886d6ae06afbb2affa743623eb0b1949 + languageName: node + linkType: hard + "globby@npm:^14.0.0": version: 14.0.1 resolution: "globby@npm:14.0.1" @@ -17354,9 +17655,10 @@ __metadata: "@emotion/eslint-plugin": "npm:11.12.0" "@emotion/react": "npm:11.14.0" "@fingerprintjs/fingerprintjs": "npm:^3.4.2" - "@floating-ui/react": "npm:0.27.7" + "@floating-ui/react": "npm:0.27.8" "@formatjs/intl-durationformat": "npm:^0.7.0" "@glideapps/glide-data-grid": "npm:^6.0.0" + "@grafana/alerting": "workspace:*" "@grafana/aws-sdk": "npm:0.6.0" "@grafana/azure-sdk": "npm:0.0.7" "@grafana/data": "workspace:*" @@ -17376,8 +17678,8 @@ __metadata: "@grafana/plugin-ui": "npm:0.10.5" "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" - "@grafana/scenes": "npm:6.10.0" - "@grafana/scenes-react": "npm:6.10.0" + "@grafana/scenes": "npm:6.10.2" + "@grafana/scenes-react": "npm:6.10.2" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" @@ -17503,7 +17805,7 @@ __metadata: croner: "npm:^9.0.0" css-loader: "npm:7.1.2" css-minimizer-webpack-plugin: "npm:7.0.0" - cypress: "npm:13.10.0" + cypress: "npm:14.3.2" cypress-file-upload: "npm:5.0.8" cypress-recurse: "npm:^1.35.3" d3: "npm:7.9.0" @@ -17573,7 +17875,7 @@ __metadata: lru-cache: "npm:11.0.2" lru-memoize: "npm:^1.1.0" lucene: "npm:^2.1.1" - marked: "npm:15.0.6" + marked: "npm:15.0.11" memoize-one: "npm:6.0.0" micro-memoize: "npm:^4.1.2" mini-css-extract-plugin: "npm:2.9.2" @@ -17593,6 +17895,7 @@ __metadata: ol-ext: "npm:4.0.26" openapi-types: "npm:^12.1.3" pdf-parse: "npm:^1.1.1" + plop: "npm:^4.0.1" pluralize: "npm:^8.0.0" postcss: "npm:8.5.1" postcss-loader: "npm:8.1.1" @@ -17740,12 +18043,12 @@ __metadata: languageName: node linkType: hard -"handlebars@npm:^4.7.7": - version: 4.7.7 - resolution: "handlebars@npm:4.7.7" +"handlebars@npm:^4.7.7, handlebars@npm:^4.7.8": + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" dependencies: minimist: "npm:^1.2.5" - neo-async: "npm:^2.6.0" + neo-async: "npm:^2.6.2" source-map: "npm:^0.6.1" uglify-js: "npm:^3.1.4" wordwrap: "npm:^1.0.0" @@ -17754,7 +18057,7 @@ __metadata: optional: true bin: handlebars: bin/handlebars - checksum: 10/617b1e689b7577734abc74564bdb8cdaddf8fd48ce72afdb489f426e9c60a7d6ee2a2707c023720c4059070128243c948bded8f2716e4543378033e3971b85ea + checksum: 10/bd528f4dd150adf67f3f857118ef0fa43ff79a153b1d943fa0a770f2599e38b25a7a0dbac1a3611a4ec86970fd2325a81310fb788b5c892308c9f8743bd02e11 languageName: node linkType: hard @@ -17865,6 +18168,16 @@ __metadata: languageName: node linkType: hard +"header-case@npm:^2.0.4": + version: 2.0.4 + resolution: "header-case@npm:2.0.4" + dependencies: + capital-case: "npm:^1.0.4" + tslib: "npm:^2.0.3" + checksum: 10/571c83eeb25e8130d172218712f807c0b96d62b020981400bccc1503a7cf14b09b8b10498a962d2739eccf231d950e3848ba7d420b58a6acd2f9283439546cd9 + languageName: node + linkType: hard + "headers-polyfill@npm:^4.0.2": version: 4.0.2 resolution: "headers-polyfill@npm:4.0.2" @@ -17958,6 +18271,15 @@ __metadata: languageName: node linkType: hard +"homedir-polyfill@npm:^1.0.1": + version: 1.0.3 + resolution: "homedir-polyfill@npm:1.0.3" + dependencies: + parse-passwd: "npm:^1.0.0" + checksum: 10/18dd4db87052c6a2179d1813adea0c4bfcfa4f9996f0e226fefb29eb3d548e564350fa28ec46b0bf1fbc0a1d2d6922ceceb80093115ea45ff8842a4990139250 + languageName: node + linkType: hard + "hookified@npm:^1.6.0": version: 1.7.0 resolution: "hookified@npm:1.7.0" @@ -18294,14 +18616,14 @@ __metadata: languageName: node linkType: hard -"http-signature@npm:~1.3.6": - version: 1.3.6 - resolution: "http-signature@npm:1.3.6" +"http-signature@npm:~1.4.0": + version: 1.4.0 + resolution: "http-signature@npm:1.4.0" dependencies: assert-plus: "npm:^1.0.0" jsprim: "npm:^2.0.2" - sshpk: "npm:^1.14.1" - checksum: 10/5f08e0c82174999da97114facb0d0d47e268d60b6fc10f92cb87b99d5ccccd36f79b9508c29dda0b4f4e3a1b2f7bcaf847e68ecd5da2f1fc465fcd1d054b7884 + sshpk: "npm:^1.18.0" + checksum: 10/f9f5eed4ac5db5e1ec6d00652680c7d8b76d553560017e34505c0c22c37abb2e6d22b9268ed4a8542aa9746852a2d64850531091e443393c9c8e0f4fd4174455 languageName: node linkType: hard @@ -18433,16 +18755,16 @@ __metadata: linkType: hard "i18next@npm:^23.5.1 || ^24.2.0, i18next@npm:^24.0.0": - version: 24.2.3 - resolution: "i18next@npm:24.2.3" + version: 24.2.2 + resolution: "i18next@npm:24.2.2" dependencies: - "@babel/runtime": "npm:^7.26.10" + "@babel/runtime": "npm:^7.23.2" peerDependencies: typescript: ^5 peerDependenciesMeta: typescript: optional: true - checksum: 10/6c73d964f2a98b1aa2c2717fe6da66fc265bcbbc5fcd52b2bfff51ff013d30d4f7d7449c4eb7f464d27af43e2e73f2e7f1d46a144d731bd3bdb1385d4c199e4c + checksum: 10/f66ed9e56d9412e59502f5df39163631daf9f1264774732fb21edbd66a528ca7a6b67dc2e2aec95683c6c7956e42c651587a54bd8ee082bd12008880ce6cd326 languageName: node linkType: hard @@ -18595,6 +18917,13 @@ __metadata: languageName: node linkType: hard +"indent-string@npm:^5.0.0": + version: 5.0.0 + resolution: "indent-string@npm:5.0.0" + checksum: 10/e466c27b6373440e6d84fbc19e750219ce25865cb82d578e41a6053d727e5520dc5725217d6eb1cc76005a1bb1696a0f106d84ce7ebda3033b963a38583fb3b3 + languageName: node + linkType: hard + "infer-owner@npm:^1.0.4": version: 1.0.4 resolution: "infer-owner@npm:1.0.4" @@ -18633,7 +18962,7 @@ __metadata: languageName: node linkType: hard -"ini@npm:^1.3.2, ini@npm:^1.3.5, ini@npm:^1.3.8": +"ini@npm:^1.3.2, ini@npm:^1.3.4, ini@npm:^1.3.5, ini@npm:^1.3.8": version: 1.3.8 resolution: "ini@npm:1.3.8" checksum: 10/314ae176e8d4deb3def56106da8002b462221c174ddb7ce0c49ee72c8cd1f9044f7b10cc555a7d8850982c3b9ca96fc212122749f5234bc2b6fb05fb942ed566 @@ -18694,6 +19023,26 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^9.2.10": + version: 9.3.7 + resolution: "inquirer@npm:9.3.7" + dependencies: + "@inquirer/figures": "npm:^1.0.3" + ansi-escapes: "npm:^4.3.2" + cli-width: "npm:^4.1.0" + external-editor: "npm:^3.1.0" + mute-stream: "npm:1.0.0" + ora: "npm:^5.4.1" + run-async: "npm:^3.0.0" + rxjs: "npm:^7.8.1" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.2" + checksum: 10/92d0a0f55701e05a8dd3624eab0c03c6e96de18285cefdbef4edc5c1a77c8283361700e088db275ec5d5b36c45ff9428127125d78428e07ea4c5574f55b17176 + languageName: node + linkType: hard + "internal-slot@npm:^1.1.0": version: 1.1.0 resolution: "internal-slot@npm:1.1.0" @@ -18771,6 +19120,16 @@ __metadata: languageName: node linkType: hard +"is-absolute@npm:^1.0.0": + version: 1.0.0 + resolution: "is-absolute@npm:1.0.0" + dependencies: + is-relative: "npm:^1.0.0" + is-windows: "npm:^1.0.1" + checksum: 10/9d16b2605eda3f3ce755410f1d423e327ad3a898bcb86c9354cf63970ed3f91ba85e9828aa56f5d6a952b9fae43d0477770f78d37409ae8ecc31e59ebc279b27 + languageName: node + linkType: hard + "is-alphabetical@npm:^1.0.0": version: 1.0.4 resolution: "is-alphabetical@npm:1.0.4" @@ -18876,7 +19235,7 @@ __metadata: languageName: node linkType: hard -"is-ci@npm:3.0.1, is-ci@npm:^3.0.1": +"is-ci@npm:3.0.1": version: 3.0.1 resolution: "is-ci@npm:3.0.1" dependencies: @@ -19071,6 +19430,13 @@ __metadata: languageName: node linkType: hard +"is-interactive@npm:^2.0.0": + version: 2.0.0 + resolution: "is-interactive@npm:2.0.0" + checksum: 10/e8d52ad490bed7ae665032c7675ec07732bbfe25808b0efbc4d5a76b1a1f01c165f332775c63e25e9a03d319ebb6b24f571a9e902669fc1e40b0a60b5be6e26c + languageName: node + linkType: hard + "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -19147,6 +19513,13 @@ __metadata: languageName: node linkType: hard +"is-path-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "is-path-cwd@npm:3.0.0" + checksum: 10/bc34d13b6a03dfca4a3ab6a8a5ba78ae4b24f4f1db4b2b031d2760c60d0913bd16a4b980dcb4e590adfc906649d5f5132684079a3972bd219da49deebb9adea8 + languageName: node + linkType: hard + "is-path-inside@npm:^3.0.2": version: 3.0.3 resolution: "is-path-inside@npm:3.0.3" @@ -19154,6 +19527,13 @@ __metadata: languageName: node linkType: hard +"is-path-inside@npm:^4.0.0": + version: 4.0.0 + resolution: "is-path-inside@npm:4.0.0" + checksum: 10/8810fa11c58e6360b82c3e0d6cd7d9c7d0392d3ac9eb10f980b81f9839f40ac6d1d6d6f05d069db0d227759801228f0b072e1b6c343e4469b065ab5fe0b68fe5 + languageName: node + linkType: hard + "is-plain-obj@npm:^1.0.0, is-plain-obj@npm:^1.1.0": version: 1.1.0 resolution: "is-plain-obj@npm:1.1.0" @@ -19217,6 +19597,15 @@ __metadata: languageName: node linkType: hard +"is-relative@npm:^1.0.0": + version: 1.0.0 + resolution: "is-relative@npm:1.0.0" + dependencies: + is-unc-path: "npm:^1.0.0" + checksum: 10/3271a0df109302ef5e14a29dcd5d23d9788e15ade91a40b942b035827ffbb59f7ce9ff82d036ea798541a52913cbf9d2d0b66456340887b51f3542d57b5a4c05 + languageName: node + linkType: hard + "is-set@npm:^2.0.3": version: 2.0.3 resolution: "is-set@npm:2.0.3" @@ -19302,6 +19691,15 @@ __metadata: languageName: node linkType: hard +"is-unc-path@npm:^1.0.0": + version: 1.0.0 + resolution: "is-unc-path@npm:1.0.0" + dependencies: + unc-path-regex: "npm:^0.1.2" + checksum: 10/e8abfde203f7409f5b03a5f1f8636e3a41e78b983702ef49d9343eb608cdfe691429398e8815157519b987b739bcfbc73ae7cf4c8582b0ab66add5171088eab6 + languageName: node + linkType: hard + "is-unicode-supported@npm:^0.1.0": version: 0.1.0 resolution: "is-unicode-supported@npm:0.1.0" @@ -19309,6 +19707,20 @@ __metadata: languageName: node linkType: hard +"is-unicode-supported@npm:^1.3.0": + version: 1.3.0 + resolution: "is-unicode-supported@npm:1.3.0" + checksum: 10/20a1fc161afafaf49243551a5ac33b6c4cf0bbcce369fcd8f2951fbdd000c30698ce320de3ee6830497310a8f41880f8066d440aa3eb0a853e2aa4836dd89abc + languageName: node + linkType: hard + +"is-unicode-supported@npm:^2.0.0": + version: 2.1.0 + resolution: "is-unicode-supported@npm:2.1.0" + checksum: 10/f254e3da6b0ab1a57a94f7273a7798dd35d1d45b227759f600d0fa9d5649f9c07fa8d3c8a6360b0e376adf916d151ec24fc9a50c5295c58bae7ca54a76a063f9 + languageName: node + linkType: hard + "is-valid-glob@npm:^1.0.0": version: 1.0.0 resolution: "is-valid-glob@npm:1.0.0" @@ -19349,6 +19761,13 @@ __metadata: languageName: node linkType: hard +"is-windows@npm:^1.0.1": + version: 1.0.2 + resolution: "is-windows@npm:1.0.2" + checksum: 10/438b7e52656fe3b9b293b180defb4e448088e7023a523ec21a91a80b9ff8cdb3377ddb5b6e60f7c7de4fa8b63ab56e121b6705fe081b3cf1b828b0a380009ad7 + languageName: node + linkType: hard + "is-wsl@npm:^2.2.0": version: 2.2.0 resolution: "is-wsl@npm:2.2.0" @@ -19388,6 +19807,13 @@ __metadata: languageName: node linkType: hard +"isbinaryfile@npm:^5.0.0": + version: 5.0.4 + resolution: "isbinaryfile@npm:5.0.4" + checksum: 10/6162e900b17e6c73da6138667d6b195ed234f9fd9d073e7c8c07ee36657e63b6a69d73da55f522d45a1928f5da4642b5d25d27e24ebd3bb68b83647d594bee79 + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -19402,7 +19828,7 @@ __metadata: languageName: node linkType: hard -"isobject@npm:^3.0.1": +"isobject@npm:^3.0.0, isobject@npm:^3.0.1": version: 3.0.1 resolution: "isobject@npm:3.0.1" checksum: 10/db85c4c970ce30693676487cca0e61da2ca34e8d4967c2e1309143ff910c207133a969f9e4ddb2dc6aba670aabce4e0e307146c310350b298e74a31f7d464703 @@ -20841,6 +21267,22 @@ __metadata: languageName: node linkType: hard +"liftoff@npm:^4.0.0": + version: 4.0.0 + resolution: "liftoff@npm:4.0.0" + dependencies: + extend: "npm:^3.0.2" + findup-sync: "npm:^5.0.0" + fined: "npm:^2.0.0" + flagged-respawn: "npm:^2.0.0" + is-plain-object: "npm:^5.0.0" + object.map: "npm:^1.0.1" + rechoir: "npm:^0.8.0" + resolve: "npm:^1.20.0" + checksum: 10/bb9f467ed4d792ce6b519558a2d7b9ef4ec28c7aa09270981395631d456c031128881c2bd31375c7c9d416abb4fc1263dbf17d9ba46bac9df0e88e04c378c699 + languageName: node + linkType: hard + "lilconfig@npm:^3.1.2, lilconfig@npm:^3.1.3": version: 3.1.3 resolution: "lilconfig@npm:3.1.3" @@ -21137,6 +21579,16 @@ __metadata: languageName: node linkType: hard +"log-symbols@npm:^6.0.0": + version: 6.0.0 + resolution: "log-symbols@npm:6.0.0" + dependencies: + chalk: "npm:^5.3.0" + is-unicode-supported: "npm:^1.3.0" + checksum: 10/510cdda36700cbcd87a2a691ea08d310a6c6b449084018f7f2ec4f732ca5e51b301ff1327aadd96f53c08318e616276c65f7fe22f2a16704fb0715d788bc3c33 + languageName: node + linkType: hard + "log-update@npm:^4.0.0": version: 4.0.0 resolution: "log-update@npm:4.0.0" @@ -21377,6 +21829,15 @@ __metadata: languageName: node linkType: hard +"make-iterator@npm:^1.0.0": + version: 1.0.1 + resolution: "make-iterator@npm:1.0.1" + dependencies: + kind-of: "npm:^6.0.2" + checksum: 10/d38afc388f4374b15c0622d4fa4d3e8c3154e3a6ba35b01e9a5179c127d7dd09a91fa571056aa9e041981b39f80bdbab035c05475e56ef675a18bdf550f0cb6a + languageName: node + linkType: hard + "makeerror@npm:1.0.12": version: 1.0.12 resolution: "makeerror@npm:1.0.12" @@ -21386,6 +21847,13 @@ __metadata: languageName: node linkType: hard +"map-cache@npm:^0.2.0": + version: 0.2.2 + resolution: "map-cache@npm:0.2.2" + checksum: 10/3067cea54285c43848bb4539f978a15dedc63c03022abeec6ef05c8cb6829f920f13b94bcaf04142fc6a088318e564c4785704072910d120d55dbc2e0c421969 + languageName: node + linkType: hard + "map-obj@npm:^1.0.0": version: 1.0.1 resolution: "map-obj@npm:1.0.1" @@ -21423,12 +21891,12 @@ __metadata: languageName: node linkType: hard -"marked@npm:15.0.6": - version: 15.0.6 - resolution: "marked@npm:15.0.6" +"marked@npm:15.0.11": + version: 15.0.11 + resolution: "marked@npm:15.0.11" bin: marked: bin/marked.js - checksum: 10/81c6949655fa6bf0478ab73896cc4be8b7f8b5b755aa1fbfc7cbe3f936cda201dd94dddd09e842e57fffc099c72bef5fe49ec0fa5d7560ee81cc5240860094ce + checksum: 10/939e75f3e989ef4d72d6da9c7e80e43d49ffdc7af8ae2a38cc8c73f20a629d9659a0acda1d0cb5a5f90876cbfb1520d029f98790a934b46592dfa178fbd2838c languageName: node linkType: hard @@ -21651,6 +22119,13 @@ __metadata: languageName: node linkType: hard +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: 10/eb5893c99e902ccebbc267c6c6b83092966af84682957f79313311edb95e8bb5f39fb048d77132b700474d1c86d90ccc211e99bae0935447a4834eb4c882982c + languageName: node + linkType: hard + "min-indent@npm:^1.0.0, min-indent@npm:^1.0.1": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -21920,6 +22395,15 @@ __metadata: languageName: node linkType: hard +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 10/16fd79c28645759505914561e249b9a1f5fe3362279ad95487a4501e4467abeb714fd35b95307326b8fd03f3c7719065ef11a6f97b7285d7888306d1bd2232ba + languageName: node + linkType: hard + "mktemp@npm:~0.4.0": version: 0.4.0 resolution: "mktemp@npm:0.4.0" @@ -22165,7 +22649,7 @@ __metadata: languageName: node linkType: hard -"mute-stream@npm:^1.0.0": +"mute-stream@npm:1.0.0, mute-stream@npm:^1.0.0": version: 1.0.0 resolution: "mute-stream@npm:1.0.0" checksum: 10/36fc968b0e9c9c63029d4f9dc63911950a3bdf55c9a87f58d3a266289b67180201cade911e7699f8b2fa596b34c9db43dad37649e3f7fdd13c3bb9edb0017ee7 @@ -22230,7 +22714,7 @@ __metadata: languageName: node linkType: hard -"neo-async@npm:^2.6.0, neo-async@npm:^2.6.2": +"neo-async@npm:^2.6.2": version: 2.6.2 resolution: "neo-async@npm:2.6.2" checksum: 10/1a7948fea86f2b33ec766bc899c88796a51ba76a4afc9026764aedc6e7cde692a09067031e4a1bf6db4f978ccd99e7f5b6c03fe47ad9865c3d4f99050d67e002 @@ -22426,6 +22910,27 @@ __metadata: languageName: node linkType: hard +"node-plop@npm:^0.32.0": + version: 0.32.0 + resolution: "node-plop@npm:0.32.0" + dependencies: + "@types/inquirer": "npm:^9.0.3" + change-case: "npm:^4.1.2" + del: "npm:^7.1.0" + globby: "npm:^13.2.2" + handlebars: "npm:^4.7.8" + inquirer: "npm:^9.2.10" + isbinaryfile: "npm:^5.0.0" + lodash.get: "npm:^4.4.2" + lower-case: "npm:^2.0.2" + mkdirp: "npm:^3.0.1" + resolve: "npm:^1.22.4" + title-case: "npm:^3.0.3" + upper-case: "npm:^2.0.2" + checksum: 10/e584ecd9090e4ce310af93357e0dcbbb073e528374e020cf796c24e85ee5d2141308c5bb4dca533141f24e94bf5a4e4c6d86621d1322917915ea6f66cc6b3326 + languageName: node + linkType: hard + "node-readfiles@npm:^0.2.0": version: 0.2.0 resolution: "node-readfiles@npm:0.2.0" @@ -22859,6 +23364,18 @@ __metadata: languageName: node linkType: hard +"object.defaults@npm:^1.1.0": + version: 1.1.0 + resolution: "object.defaults@npm:1.1.0" + dependencies: + array-each: "npm:^1.0.1" + array-slice: "npm:^1.0.0" + for-own: "npm:^1.0.0" + isobject: "npm:^3.0.0" + checksum: 10/9b194806eb9b5cf8c956d20e9869b3c7431c85748d761a570b45beb71041119408ca2c3d380fe43d4340019e6d03fab91d60842cb3d7259ceffd9e582cd79fb8 + languageName: node + linkType: hard + "object.entries@npm:^1.1.8": version: 1.1.8 resolution: "object.entries@npm:1.1.8" @@ -22893,6 +23410,16 @@ __metadata: languageName: node linkType: hard +"object.map@npm:^1.0.1": + version: 1.0.1 + resolution: "object.map@npm:1.0.1" + dependencies: + for-own: "npm:^1.0.0" + make-iterator: "npm:^1.0.0" + checksum: 10/c2b945a309f789441fae30e4c0772066b45ad03eb1c0f91b8ae117700c975676652b356f61635fe0b21ae021d98f10a04d2f1c6cf30aef14111154e756b162d7 + languageName: node + linkType: hard + "object.omit@npm:^3.0.0": version: 3.0.0 resolution: "object.omit@npm:3.0.0" @@ -22902,6 +23429,15 @@ __metadata: languageName: node linkType: hard +"object.pick@npm:^1.3.0": + version: 1.3.0 + resolution: "object.pick@npm:1.3.0" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10/92d7226a6b581d0d62694a5632b6a1594c81b3b5a4eb702a7662e0b012db532557067d6f773596c577f75322eba09cdca37ca01ea79b6b29e3e17365f15c615e + languageName: node + linkType: hard + "object.values@npm:^1.1.6, object.values@npm:^1.2.0, object.values@npm:^1.2.1": version: 1.2.1 resolution: "object.values@npm:1.2.1" @@ -23011,6 +23547,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: "npm:^5.0.0" + checksum: 10/eb08d2da9339819e2f9d52cab9caf2557d80e9af8c7d1ae86e1a0fef027d00a88e9f5bd67494d350df360f7c559fbb44e800b32f310fb989c860214eacbb561c + languageName: node + linkType: hard + "open@npm:^10.0.3": version: 10.0.3 resolution: "open@npm:10.0.3" @@ -23115,6 +23660,23 @@ __metadata: languageName: node linkType: hard +"ora@npm:^8.0.0": + version: 8.2.0 + resolution: "ora@npm:8.2.0" + dependencies: + chalk: "npm:^5.3.0" + cli-cursor: "npm:^5.0.0" + cli-spinners: "npm:^2.9.2" + is-interactive: "npm:^2.0.0" + is-unicode-supported: "npm:^2.0.0" + log-symbols: "npm:^6.0.0" + stdin-discarder: "npm:^0.2.2" + string-width: "npm:^7.2.0" + strip-ansi: "npm:^7.1.0" + checksum: 10/cea932fdcb29549cd7b5af81f427760986429cadc752b1dd4bf31bc6821f5ba137e1ef9a18cde7bdfbe5b4e3d3201e76b048765c51a27b15d18c57ac0e0a909a + languageName: node + linkType: hard + "os-tmpdir@npm:~1.0.2": version: 1.0.2 resolution: "os-tmpdir@npm:1.0.2" @@ -23251,6 +23813,15 @@ __metadata: languageName: node linkType: hard +"p-map@npm:^5.5.0": + version: 5.5.0 + resolution: "p-map@npm:5.5.0" + dependencies: + aggregate-error: "npm:^4.0.0" + checksum: 10/089a709d2525208a965b7907cc8e58af950542629b538198fc142c40e7f36b3b492dd6a46a1279515ccab58bb6f047e04593c0ab5ef4539d312adf7f761edf55 + languageName: node + linkType: hard + "p-pipe@npm:3.1.0": version: 3.1.0 resolution: "p-pipe@npm:3.1.0" @@ -23450,6 +24021,17 @@ __metadata: languageName: node linkType: hard +"parse-filepath@npm:^1.0.2": + version: 1.0.2 + resolution: "parse-filepath@npm:1.0.2" + dependencies: + is-absolute: "npm:^1.0.0" + map-cache: "npm:^0.2.0" + path-root: "npm:^0.1.1" + checksum: 10/6794c3f38d3921f0f7cc63fb1fb0c4d04cd463356ad389c8ce6726d3c50793b9005971f4138975a6d7025526058d5e65e9bfe634d0765e84c4e2571152665a69 + languageName: node + linkType: hard + "parse-headers@npm:^2.0.2": version: 2.0.4 resolution: "parse-headers@npm:2.0.4" @@ -23489,6 +24071,13 @@ __metadata: languageName: node linkType: hard +"parse-passwd@npm:^1.0.0": + version: 1.0.0 + resolution: "parse-passwd@npm:1.0.0" + checksum: 10/4e55e0231d58f828a41d0f1da2bf2ff7bcef8f4cb6146e69d16ce499190de58b06199e6bd9b17fbf0d4d8aef9052099cdf8c4f13a6294b1a522e8e958073066e + languageName: node + linkType: hard + "parse-path@npm:^7.0.0": version: 7.0.0 resolution: "parse-path@npm:7.0.0" @@ -23508,12 +24097,12 @@ __metadata: linkType: hard "parse5-htmlparser2-tree-adapter@npm:^7.0.0": - version: 7.1.0 - resolution: "parse5-htmlparser2-tree-adapter@npm:7.1.0" + version: 7.0.0 + resolution: "parse5-htmlparser2-tree-adapter@npm:7.0.0" dependencies: - domhandler: "npm:^5.0.3" + domhandler: "npm:^5.0.2" parse5: "npm:^7.0.0" - checksum: 10/75910af9137451e9c53e1e0d712f7393f484e89e592b1809ee62ad6cedd61b98daeaa5206ff5d9f06778002c91fac311afedde4880e1916fdb44fa71199dae73 + checksum: 10/23dbe45fdd338fe726cf5c55b236e1f403aeb0c1b926e18ab8ef0aa580980a25f8492d160fe2ed0ec906c3c8e38b51e68ef5620a3b9460d9458ea78946a3f7c0 languageName: node linkType: hard @@ -23569,6 +24158,16 @@ __metadata: languageName: node linkType: hard +"path-case@npm:^3.0.4": + version: 3.0.4 + resolution: "path-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/61de0526222629f65038a66f63330dd22d5b54014ded6636283e1d15364da38b3cf29e4433aa3f9d8b0dba407ae2b059c23b0104a34ee789944b1bc1c5c7e06d + languageName: node + linkType: hard + "path-exists@npm:^3.0.0": version: 3.0.0 resolution: "path-exists@npm:3.0.0" @@ -23618,6 +24217,22 @@ __metadata: languageName: node linkType: hard +"path-root-regex@npm:^0.1.0": + version: 0.1.2 + resolution: "path-root-regex@npm:0.1.2" + checksum: 10/dcd75d1f8e93faabe35a58e875b0f636839b3658ff2ad8c289463c40bc1a844debe0dab73c3398ef9dc8f6ec6c319720aff390cf4633763ddcf3cf4b1bbf7e8b + languageName: node + linkType: hard + +"path-root@npm:^0.1.1": + version: 0.1.1 + resolution: "path-root@npm:0.1.1" + dependencies: + path-root-regex: "npm:^0.1.0" + checksum: 10/ff88aebfc1c59ace510cc06703d67692a11530989920427625e52b66a303ca9b3d4059b0b7d0b2a73248d1ad29bcb342b8b786ec00592f3101d38a45fd3b2e08 + languageName: node + linkType: hard + "path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" @@ -23857,6 +24472,24 @@ __metadata: languageName: node linkType: hard +"plop@npm:^4.0.1": + version: 4.0.1 + resolution: "plop@npm:4.0.1" + dependencies: + "@types/liftoff": "npm:^4.0.3" + chalk: "npm:^5.3.0" + interpret: "npm:^3.1.1" + liftoff: "npm:^4.0.0" + minimist: "npm:^1.2.8" + node-plop: "npm:^0.32.0" + ora: "npm:^8.0.0" + v8flags: "npm:^4.0.1" + bin: + plop: bin/plop.js + checksum: 10/cb7f4c2b8fe3b1f3735a0a9ab92a7b1b0aa940293ce85840b2ec55821b62a697e1e29e9d68ff298d23eb5501e965fc707f586a962a4bf213dcf606f2589837a4 + languageName: node + linkType: hard + "pluralize@npm:8.0.0, pluralize@npm:^8.0.0": version: 8.0.0 resolution: "pluralize@npm:8.0.0" @@ -24667,15 +25300,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.10.4": - version: 6.10.4 - resolution: "qs@npm:6.10.4" - dependencies: - side-channel: "npm:^1.0.4" - checksum: 10/8887a53f63180e0e0291deafef581e550bc3656f2453adc8d3ca34b49c04354d31079962f7faf90ab8f5fd6e3d70ee6645042b27814a757a3a5d5708ae3f58e0 - languageName: node - linkType: hard - "qs@npm:6.13.0": version: 6.13.0 resolution: "qs@npm:6.13.0" @@ -24685,12 +25309,12 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.11.2, qs@npm:^6.4.0": - version: 6.13.1 - resolution: "qs@npm:6.13.1" +"qs@npm:6.14.0, qs@npm:^6.11.2, qs@npm:^6.4.0": + version: 6.14.0 + resolution: "qs@npm:6.14.0" dependencies: - side-channel: "npm:^1.0.6" - checksum: 10/53cf5fdc5f342a9ffd3968f20c8c61624924cf928d86fff525240620faba8ca5cfd6c3f12718cc755561bfc3dc9721bc8924e38f53d8925b03940f0b8a902212 + side-channel: "npm:^1.1.0" + checksum: 10/a60e49bbd51c935a8a4759e7505677b122e23bf392d6535b8fc31c1e447acba2c901235ecb192764013cd2781723dc1f61978b5fdd93cc31d7043d31cdc01974 languageName: node linkType: hard @@ -24708,6 +25332,13 @@ __metadata: languageName: node linkType: hard +"queue-tick@npm:^1.0.1": + version: 1.0.1 + resolution: "queue-tick@npm:1.0.1" + checksum: 10/f447926c513b64a857906f017a3b350f7d11277e3c8d2a21a42b7998fa1a613d7a829091e12d142bb668905c8f68d8103416c7197856efb0c72fa835b8e254b5 + languageName: node + linkType: hard + "queue-typescript@npm:^1.0.1": version: 1.0.1 resolution: "queue-typescript@npm:1.0.1" @@ -26560,6 +27191,16 @@ __metadata: languageName: node linkType: hard +"resolve-dir@npm:^1.0.0, resolve-dir@npm:^1.0.1": + version: 1.0.1 + resolution: "resolve-dir@npm:1.0.1" + dependencies: + expand-tilde: "npm:^2.0.0" + global-modules: "npm:^1.0.0" + checksum: 10/ef736b8ed60d6645c3b573da17d329bfb50ec4e1d6c5ffd6df49e3497acef9226f9810ea6823b8ece1560e01dcb13f77a9f6180d4f242d00cc9a8f4de909c65c + languageName: node + linkType: hard + "resolve-from@npm:5.0.0, resolve-from@npm:^5.0.0": version: 5.0.0 resolution: "resolve-from@npm:5.0.0" @@ -26675,6 +27316,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" + dependencies: + onetime: "npm:^7.0.0" + signal-exit: "npm:^4.1.0" + checksum: 10/838dd54e458d89cfbc1a923b343c1b0f170a04100b4ce1733e97531842d7b440463967e521216e8ab6c6f8e89df877acc7b7f4c18ec76e99fb9bf5a60d358d2c + languageName: node + linkType: hard + "ret@npm:^0.2.0": version: 0.2.2 resolution: "ret@npm:0.2.2" @@ -26733,7 +27384,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": +"rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: @@ -26962,6 +27613,13 @@ __metadata: languageName: node linkType: hard +"run-async@npm:^3.0.0": + version: 3.0.0 + resolution: "run-async@npm:3.0.0" + checksum: 10/97fb8747f7765b77ebcd311d3a33548099336f04c6434e0763039b98c1de0f1b4421000695aff8751f309c0b995d8dfd620c1f1e4c35572da38c101488165305 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -26978,7 +27636,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:7.8.1, rxjs@npm:^7.5.1, rxjs@npm:^7.5.5": +"rxjs@npm:7.8.1": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: @@ -26987,6 +27645,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:^7.2.0, rxjs@npm:^7.5.1, rxjs@npm:^7.5.5, rxjs@npm:^7.8.1": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d + languageName: node + linkType: hard + "safe-array-concat@npm:^1.1.3": version: 1.1.3 resolution: "safe-array-concat@npm:1.1.3" @@ -27239,7 +27906,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.0": +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2, semver@npm:^7.6.3, semver@npm:^7.7.0, semver@npm:^7.7.1": version: 7.7.1 resolution: "semver@npm:7.7.1" bin: @@ -27269,6 +27936,17 @@ __metadata: languageName: node linkType: hard +"sentence-case@npm:^3.0.4": + version: 3.0.4 + resolution: "sentence-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10/3cfe6c0143e649132365695706702d7f729f484fa7b25f43435876efe7af2478243eefb052bacbcce10babf9319fd6b5b6bc59b94c80a1c819bcbb40651465d5 + languageName: node + linkType: hard + "serialize-error@npm:^8.1.0": version: 8.1.0 resolution: "serialize-error@npm:8.1.0" @@ -27545,7 +28223,7 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": +"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0": version: 1.1.0 resolution: "side-channel@npm:1.1.0" dependencies: @@ -27629,6 +28307,13 @@ __metadata: languageName: node linkType: hard +"slash@npm:^4.0.0": + version: 4.0.0 + resolution: "slash@npm:4.0.0" + checksum: 10/da8e4af73712253acd21b7853b7e0dbba776b786e82b010a5bfc8b5051a1db38ed8aba8e1e8f400dd2c9f373be91eb1c42b66e91abb407ff42b10feece5e1d2d + languageName: node + linkType: hard + "slash@npm:^5.0.0, slash@npm:^5.1.0": version: 5.1.0 resolution: "slash@npm:5.1.0" @@ -27820,6 +28505,16 @@ __metadata: languageName: node linkType: hard +"snake-case@npm:^3.0.4": + version: 3.0.4 + resolution: "snake-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/0a7a79900bbb36f8aaa922cf111702a3647ac6165736d5dc96d3ef367efc50465cac70c53cd172c382b022dac72ec91710608e5393de71f76d7142e6fd80e8a3 + languageName: node + linkType: hard + "socket.io-adapter@npm:~2.5.2": version: 2.5.5 resolution: "socket.io-adapter@npm:2.5.5" @@ -27922,11 +28617,11 @@ __metadata: linkType: hard "sort-keys@npm:^5.0.0": - version: 5.1.0 - resolution: "sort-keys@npm:5.1.0" + version: 5.0.0 + resolution: "sort-keys@npm:5.0.0" dependencies: is-plain-obj: "npm:^4.0.0" - checksum: 10/d14936082b2fd1efbddb42c1f7ece39acf8c2e54e4bc65b92ee634ffc7a4a955bdfb334f28ce1273c947611c11f8a73711d147dc43922172a782eb4d71b8c3a2 + checksum: 10/9c0b7a468312075be03770b260b2cc0e5d55149025e564edaed41c9ff619199698aad6712a6fe4bbc75c541efb081276ac6bbd4cf2723d742f272f7a8fe354f5 languageName: node linkType: hard @@ -28155,9 +28850,9 @@ __metadata: languageName: node linkType: hard -"sshpk@npm:^1.14.1": - version: 1.17.0 - resolution: "sshpk@npm:1.17.0" +"sshpk@npm:^1.18.0": + version: 1.18.0 + resolution: "sshpk@npm:1.18.0" dependencies: asn1: "npm:~0.2.3" assert-plus: "npm:^1.0.0" @@ -28172,7 +28867,7 @@ __metadata: sshpk-conv: bin/sshpk-conv sshpk-sign: bin/sshpk-sign sshpk-verify: bin/sshpk-verify - checksum: 10/668c2a279a6ce66fd739ce5684e37927dd75427cc020c828a208f85890a4c400705d4ba09f32fa44efca894339dc6931941664f6f6ba36dfa543de6d006cbe9c + checksum: 10/858339d43e3c6b6a848772a66f69442ce74f1a37655d9f35ba9d1f85329499ff0000af9f8ab83dbb39ad24c0c370edabe0be1e39863f70c6cded9924b8458c34 languageName: node linkType: hard @@ -28277,6 +28972,13 @@ __metadata: languageName: node linkType: hard +"stdin-discarder@npm:^0.2.2": + version: 0.2.2 + resolution: "stdin-discarder@npm:0.2.2" + checksum: 10/642ffd05bd5b100819d6b24a613d83c6e3857c6de74eb02fc51506fa61dc1b0034665163831873868157c4538d71e31762bcf319be86cea04c3aba5336470478 + languageName: node + linkType: hard + "storybook@npm:^8.6.2": version: 8.6.2 resolution: "storybook@npm:8.6.2" @@ -28315,16 +29017,12 @@ __metadata: linkType: hard "streamx@npm:^2.12.0, streamx@npm:^2.12.5, streamx@npm:^2.13.2, streamx@npm:^2.14.0": - version: 2.22.0 - resolution: "streamx@npm:2.22.0" + version: 2.15.7 + resolution: "streamx@npm:2.15.7" dependencies: - bare-events: "npm:^2.2.0" - fast-fifo: "npm:^1.3.2" - text-decoder: "npm:^1.1.0" - dependenciesMeta: - bare-events: - optional: true - checksum: 10/9c329bb316e2085e207e471ecd0da18b4ed5b1cfe5cf10e9e7fad3f8f50c6ca1a6a844bdfd9bc7521560b97f229890de82ca162a0e66115300b91a489b1cbefd + fast-fifo: "npm:^1.1.0" + queue-tick: "npm:^1.0.1" + checksum: 10/5f59f49383b41546e87b15ba54970cca262d1eb0826c5b88c4dc5329d18821fed06f92f849da68c764ad91ea05f394518510957371cb0abd3617bdabed465cdb languageName: node linkType: hard @@ -28402,6 +29100,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10/42f9e82f61314904a81393f6ef75b832c39f39761797250de68c041d8ba4df2ef80db49ab6cd3a292923a6f0f409b8c9980d120f7d32c820b4a8a84a2598a295 + languageName: node + linkType: hard + "string.prototype.includes@npm:^2.0.1": version: 2.0.1 resolution: "string.prototype.includes@npm:2.0.1" @@ -28525,7 +29234,7 @@ __metadata: languageName: node linkType: hard -"strip-ansi@npm:^7.0.1": +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": version: 7.1.0 resolution: "strip-ansi@npm:7.1.0" dependencies: @@ -29068,15 +29777,6 @@ __metadata: languageName: node linkType: hard -"text-decoder@npm:^1.1.0": - version: 1.2.3 - resolution: "text-decoder@npm:1.2.3" - dependencies: - b4a: "npm:^1.6.4" - checksum: 10/bcdec33c0f070aeac38e46e4cafdcd567a58473ed308bdf75260bfbd8f7dc76acbc0b13226afaec4a169d0cb44cec2ab89c57b6395ccf02e941eaebbe19e124a - languageName: node - linkType: hard - "text-extensions@npm:^1.0.0": version: 1.9.0 resolution: "text-extensions@npm:1.9.0" @@ -29192,6 +29892,15 @@ __metadata: languageName: node linkType: hard +"title-case@npm:^3.0.3": + version: 3.0.3 + resolution: "title-case@npm:3.0.3" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/369fe90f650a66205c34ebef63a69c6d1fd411ae3aad23db0aae165ddb881af50e67c6ea6800d605bc2b9e0ab5f22dada58fe97a1a7e7f3131ee0ef176cc65ec + languageName: node + linkType: hard + "tlds@npm:1.252.0": version: 1.252.0 resolution: "tlds@npm:1.252.0" @@ -29201,6 +29910,24 @@ __metadata: languageName: node linkType: hard +"tldts-core@npm:^6.1.86": + version: 6.1.86 + resolution: "tldts-core@npm:6.1.86" + checksum: 10/cb5dff9cc15661ac773a2099e98c99a5cb3cebc35909c23cc4261ff7992032c7501995ae995de3574dbbf3431e59c47496534d52f5e96abcb231f0e72144c020 + languageName: node + linkType: hard + +"tldts@npm:^6.1.32": + version: 6.1.86 + resolution: "tldts@npm:6.1.86" + dependencies: + tldts-core: "npm:^6.1.86" + bin: + tldts: bin/cli.js + checksum: 10/f7e66824e44479ccdda55ea556af14ce61c4d27708be403e3f90631defde49f82a580e1ca07187cc7e3b349e257a30c2808a22903f3a0548e136ebb609ccc109 + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -29210,12 +29937,10 @@ __metadata: languageName: node linkType: hard -"tmp@npm:~0.2.1": - version: 0.2.1 - resolution: "tmp@npm:0.2.1" - dependencies: - rimraf: "npm:^3.0.0" - checksum: 10/445148d72df3ce99356bc89a7857a0c5c3b32958697a14e50952c6f7cf0a8016e746ababe9a74c1aa52f04c526661992f14659eba34d3c6701d49ba2f3cf781b +"tmp@npm:~0.2.1, tmp@npm:~0.2.3": + version: 0.2.3 + resolution: "tmp@npm:0.2.3" + checksum: 10/7b13696787f159c9754793a83aa79a24f1522d47b87462ddb57c18ee93ff26c74cbb2b8d9138f571d2e0e765c728fb2739863a672b280528512c6d83d511c6fa languageName: node linkType: hard @@ -29300,7 +30025,7 @@ __metadata: languageName: node linkType: hard -"tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.3, tough-cookie@npm:^4.1.4": +"tough-cookie@npm:^4.1.2, tough-cookie@npm:^4.1.4": version: 4.1.4 resolution: "tough-cookie@npm:4.1.4" dependencies: @@ -29312,6 +30037,15 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:^5.0.0": + version: 5.1.2 + resolution: "tough-cookie@npm:5.1.2" + dependencies: + tldts: "npm:^6.1.32" + checksum: 10/de430e6e6d34b794137e05b8ac2aa6b74ebbe6cdceb4126f168cf1e76101162a4b2e0e7587c3b70e728bd8654fc39958b2035be7619ee6f08e7257610ba4cd04 + languageName: node + linkType: hard + "tr46@npm:^3.0.0": version: 3.0.0 resolution: "tr46@npm:3.0.0" @@ -29335,6 +30069,15 @@ __metadata: languageName: node linkType: hard +"tree-kill@npm:1.2.2": + version: 1.2.2 + resolution: "tree-kill@npm:1.2.2" + bin: + tree-kill: cli.js + checksum: 10/49117f5f410d19c84b0464d29afb9642c863bc5ba40fcb9a245d474c6d5cc64d1b177a6e6713129eb346b40aebb9d4631d967517f9fbe8251c35b21b13cd96c7 + languageName: node + linkType: hard + "tree-sitter-json@npm:=0.24.8": version: 0.24.8 resolution: "tree-sitter-json@npm:0.24.8" @@ -29674,10 +30417,10 @@ __metadata: languageName: node linkType: hard -"type-fest@npm:^4.18.2, type-fest@npm:^4.26.1": - version: 4.26.1 - resolution: "type-fest@npm:4.26.1" - checksum: 10/b82676194f80af228cb852e320d2ea8381c89d667d2e4d9f2bdfc8f254bccc039c7741a90c53617a4de0c9fdca8265ed18eb0888cd628f391c5c381c33a9f94b +"type-fest@npm:^4.18.2, type-fest@npm:^4.26.1, type-fest@npm:^4.40.0": + version: 4.40.0 + resolution: "type-fest@npm:4.40.0" + checksum: 10/dbca20979d18c6b8c87ca28cd999d9ae6b34e0c54c3a87ac65530a32f7a178d38d3788044a589f47c9fde3f3c81422e7b021ec1455f7242b724a2d9c642ce8b8 languageName: node linkType: hard @@ -29869,6 +30612,13 @@ __metadata: languageName: node linkType: hard +"unc-path-regex@npm:^0.1.2": + version: 0.1.2 + resolution: "unc-path-regex@npm:0.1.2" + checksum: 10/a05fa2006bf4606051c10fc7968f08ce7b28fa646befafa282813aeb1ac1a56f65cb1b577ca7851af2726198d59475bb49b11776036257b843eaacee2860a4ec + languageName: node + linkType: hard + "underscore.string@npm:~3.3.4": version: 3.3.6 resolution: "underscore.string@npm:3.3.6" @@ -29887,9 +30637,9 @@ __metadata: linkType: hard "undici@npm:^6.19.5": - version: 6.21.2 - resolution: "undici@npm:6.21.2" - checksum: 10/9cd9ead22599c23aa2a7dfa5b80fa1491bebb294bf1dc64c9c0f90ea4ec8e272a4db2810e2565d65b952249f105523d2929d7cc951f88cf0a1f082143bae8d75 + version: 6.19.8 + resolution: "undici@npm:6.19.8" + checksum: 10/19ae4ba38b029a664d99fd330935ef59136cf99edb04ed821042f27b5a9e84777265fb744c8a7abc83f2059afb019446c69a4ebef07bbc0ed6b2de8d67ef4090 languageName: node linkType: hard @@ -30075,6 +30825,24 @@ __metadata: languageName: node linkType: hard +"upper-case-first@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case-first@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/4487db4701effe3b54ced4b3e4aa4d9ab06c548f97244d04aafb642eedf96a76d5a03cf5f38f10f415531d5792d1ac6e1b50f2a76984dc6964ad530f12876409 + languageName: node + linkType: hard + +"upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/508723a2b03ab90cf1d6b7e0397513980fab821cbe79c87341d0e96cedefadf0d85f9d71eac24ab23f526a041d585a575cfca120a9f920e44eb4f8a7cf89121c + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -30254,6 +31022,13 @@ __metadata: languageName: node linkType: hard +"v8flags@npm:^4.0.1": + version: 4.0.1 + resolution: "v8flags@npm:4.0.1" + checksum: 10/69863ede75ff79579654951c78724c084bc337d0ebe1d9bffc6924f3f2bd0b40a9eb4c568fc795201d5eb72311b77e5d75a7e1544faa12355412360dc37d76e2 + languageName: node + linkType: hard + "validate-npm-package-license@npm:3.0.4, validate-npm-package-license@npm:^3.0.1, validate-npm-package-license@npm:^3.0.4": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4" @@ -30998,7 +31773,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.3.1": +"which@npm:^1.2.14, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: