diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index db3f1f04806..9dc87a6548a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -799,7 +799,7 @@ playwright.storybook.config.ts @grafana/grafana-frontend-platform /public/app/core/components/ColorScale/ @grafana/dataviz-squad /public/app/core/components/DynamicImports/ @grafana/grafana-search-navigate-organise /public/app/core/components/EmptyListCTA/ @grafana/grafana-frontend-platform -/public/app/core/components/FolderFilter/ @grafana/sharing-squad +/public/app/core/components/FolderFilter/ @grafana/grafana-search-navigate-organise /public/app/core/components/Footer/ @grafana/grafana-search-navigate-organise /public/app/core/components/ForgottenPassword/ @grafana/grafana-search-navigate-organise /public/app/core/components/Form/ @grafana/grafana-frontend-platform diff --git a/.github/workflows/backport-workflow.yml b/.github/workflows/backport-workflow.yml index f3d6761440c..ef7254ffe6a 100644 --- a/.github/workflows/backport-workflow.yml +++ b/.github/workflows/backport-workflow.yml @@ -36,7 +36,7 @@ jobs: private_key: ${{ fromJSON(steps.secrets.outputs.secrets).APP_PEM }} - name: Download PR info artifact - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 id: download-pr-info with: github-token: ${{ github.token }} diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index 2e9b370d4e0..cd2c123e472 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -141,12 +141,12 @@ jobs: node-version-file: '.nvmrc' - name: Get built packages from pr - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: buildPr - name: Get built packages from base - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: buildBase @@ -225,7 +225,7 @@ jobs: persist-credentials: false - name: 'Download artifact' - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: levitate diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index 08b5ad7aba9..30bfc4705ad 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -159,7 +159,7 @@ jobs: with: registry: 'us-docker.pkg.dev' environment: 'dev' - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: grafana-docker-tar-gz path: . @@ -221,10 +221,10 @@ jobs: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: grafana-tar-gz - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: ${{ needs.build-e2e-runner.outputs.artifact }} - name: chmod +x @@ -298,7 +298,7 @@ jobs: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: grafana-tar-gz - name: Run E2E tests @@ -360,7 +360,7 @@ jobs: run: | docker cp cpp-e2e-deploy:/outputs.json /tmp/outputs.json - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: grafana-tar-gz @@ -400,7 +400,7 @@ jobs: node-version-file: '.nvmrc' - name: Download blob reports from GitHub Actions Artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: path: blobs pattern: playwright-blob-* @@ -479,7 +479,7 @@ jobs: - uses: actions/checkout@v5 with: persist-credentials: false - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: grafana-tar-gz - name: Run PR a11y test @@ -531,7 +531,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Get pa11y results - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: pa11y-ci-results - name: Extract and publish metrics diff --git a/.github/workflows/publish-artifact.yml b/.github/workflows/publish-artifact.yml index 8ea0b9b90a0..ad0cc86de66 100644 --- a/.github/workflows/publish-artifact.yml +++ b/.github/workflows/publish-artifact.yml @@ -44,7 +44,7 @@ jobs: permissions: id-token: write steps: - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: ${{ inputs.name }} pattern: ${{ inputs.pattern }} diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 2fc23359a7a..435fc8b6849 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -224,27 +224,27 @@ jobs: - build steps: - uses: grafana/shared-workflows/actions/dockerhub-login@dockerhub-login/v1.0.2 - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-list-linux-amd64 path: . - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-list-linux-arm64 path: . - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-list-linux-armv7 path: . - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-linux-amd64 path: dist - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-linux-arm64 path: dist - - uses: actions/download-artifact@4a24838f3d5601fd639834081e118c2995d51e1c + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 with: name: artifacts-linux-armv7 path: dist diff --git a/.github/workflows/trufflehog.yml b/.github/workflows/trufflehog.yml index 7278c8ad3ed..c5bc7fd095e 100644 --- a/.github/workflows/trufflehog.yml +++ b/.github/workflows/trufflehog.yml @@ -31,6 +31,6 @@ jobs: persist-credentials: false fetch-depth: ${{ steps.fetch_depth.outputs.fetch_depth }} - name: Trufflehog - uses: trufflesecurity/trufflehog@ad6fc8fb446b8fafbf7ea8193d2d6bfd42f45690 # v3.90.11 + uses: trufflesecurity/trufflehog@b84c3d14d189e16da175e2c27fa8136603783ffc # v3.90.12 with: extra_args: --results=verified diff --git a/.gitignore b/.gitignore index 036bb5dd2b1..d0e233d6a42 100644 --- a/.gitignore +++ b/.gitignore @@ -250,9 +250,12 @@ public/mockServiceWorker.js /e2e-playwright/test-plugins/*/dist /apps/provisioning/cmd/job-controller/bin/ - # Ignore unified storage kv store files /grafana-kv-data + +# Ignore debug output from test library +/pkg/storage/secret/metadata/testdata/rapid/TestStateMachine/ + /codeowners-manifest/ # Ignore grafana/hippocampus local cache folder diff --git a/.nvmrc b/.nvmrc index aebd91c521b..c519bf5baaa 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v22.16.0 +v24.11.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1f5d9ddace..c62e18c754e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,9 @@ Thank you for your interest in contributing to Grafana! We welcome all people who want to contribute in a healthy and constructive manner within our community. To help us create a safe and positive community experience for all, we require all participants to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). -This document is a guide to help you through the process of contributing to Grafana. Be sure to check out the [Grafana Champions program](https://grafana.com/community/champions/?src=github&camp=community-cross-platform-engagement) as you start to contribute. It’s designed to recognize and empower individuals who are actively contributing to the growth and success of the Grafana ecosystem. +This document is a guide to help you through the process of contributing to Grafana. Be sure to check out the [Grafana Champions program](https://grafana.com/community/champions/?src=github&camp=community-cross-platform-engagement) as you start to contribute. It's designed to recognize and empower individuals who are actively contributing to the growth and success of the Grafana ecosystem. + +> **Help us improve!** We'd love to hear about your contributor experience. Take a moment to share your feedback in our [Open Source Contributor Experience Survey](https://gra.fan/ome). Your input helps us make contributing to Grafana better for everyone. Whether you're a new contributor or a seasoned veteran, we hope these resources help you connect with the community. diff --git a/Dockerfile b/Dockerfile index 7e83cb1684c..b6c3f267491 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ ARG JS_SRC=js-builder FROM alpine:3.22.2 AS alpine-base FROM ubuntu:22.04 AS ubuntu-base FROM golang:1.25.3-alpine AS go-builder-base -FROM --platform=${JS_PLATFORM} node:22-alpine AS js-builder-base +FROM --platform=${JS_PLATFORM} node:24-alpine AS js-builder-base # Javascript build stage FROM --platform=${JS_PLATFORM} ${JS_IMAGE} AS js-builder ARG JS_NODE_ENV=production diff --git a/Makefile b/Makefile index 5bf0150e047..56c2a2c63ff 100644 --- a/Makefile +++ b/Makefile @@ -178,7 +178,7 @@ gen-apps: do-gen-apps gofmt ## Generate code for Grafana App SDK apps and run go @if [ -n "$$CODEGEN_VERIFY" ]; then \ echo "Verifying generated code is up to date..."; \ if ! git diff --quiet; then \ - echo "Error: Generated apps code is not up to date. Please run 'make gen-apps' to regenerate."; \ + echo "Error: Generated code is not up to date. Please run 'make gen-apps', 'make gen-cue', and 'make gen-jsonnet' to regenerate."; \ git diff --name-only; \ exit 1; \ fi; \ diff --git a/README.md b/README.md index 033ebdc1ea6..d92844ed9aa 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ The open-source platform for monitoring and observability [![License](https://img.shields.io/github/license/grafana/grafana)](LICENSE) -[![Drone](https://drone.grafana.net/api/badges/grafana/grafana/status.svg)](https://drone.grafana.net/grafana/grafana) [![Go Report Card](https://goreportcard.com/badge/github.com/grafana/grafana)](https://goreportcard.com/report/github.com/grafana/grafana) Grafana allows you to query, visualize, alert on and understand your metrics no matter where they are stored. Create, explore, and share dashboards with your team and foster a data-driven culture: @@ -36,6 +35,8 @@ If you're interested in contributing to the Grafana project: - Explore our [beginner-friendly issues](https://github.com/grafana/grafana/issues?q=is%3Aopen+is%3Aissue+label%3A%22beginner+friendly%22). - Look through our [style guide and Storybook](https://developers.grafana.com/ui/latest/index.html). +> Share your contributor experience in our [feedback survey](https://gra.fan/ome) to help us improve. + ## Get involved - Follow [@grafana on X (formerly Twitter)](https://x.com/grafana/). diff --git a/apps/advisor/go.mod b/apps/advisor/go.mod index da70ceca234..1bfc3c9671a 100644 --- a/apps/advisor/go.mod +++ b/apps/advisor/go.mod @@ -96,7 +96,7 @@ require ( github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad // indirect github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // indirect github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect - github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // indirect + github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/fatih/color v1.18.0 // indirect diff --git a/apps/advisor/go.sum b/apps/advisor/go.sum index 89d9baaa4b4..8594bd52e94 100644 --- a/apps/advisor/go.sum +++ b/apps/advisor/go.sum @@ -383,8 +383,8 @@ github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e h1:7pAt github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e/go.mod h1:KZyoO3jngyZCLyCf100FEQTrwAHj33AIMj4Zv4u3MNE= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= -github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 h1:LGTt2LtYX8vaai32d+c9L0sMcP+Dg9w1kO6+lbsxxYg= -github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 h1:ScHTwNbcVC6JH1OSyXzj8S4w67BIpRXwTSjrac3/PSw= +github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33/go.mod h1:8pvvk5OLaLN9LLxghyczUapn/97l+mBgIb10qC1LG84= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= diff --git a/apps/alerting/notifications/Makefile b/apps/alerting/notifications/Makefile index 039a81a6f6e..9564d011d14 100644 --- a/apps/alerting/notifications/Makefile +++ b/apps/alerting/notifications/Makefile @@ -1,8 +1,11 @@ include ../../sdk.mk -.PHONY: generate -generate: do-generate ## Run Grafana App SDK code generation - -.PHONY: do-generate -do-generate: install-app-sdk update-app-sdk - @$(APP_SDK_BIN) generate --grouping=group --gogenpath=./pkg/apis --defencoding=yaml --postprocess +.PHONY: generate # Run Grafana App SDK code generation +generate: install-app-sdk update-app-sdk + @$(APP_SDK_BIN) generate \ + --source=./kinds/ \ + --gogenpath=./pkg/apis \ + --grouping=group \ + --genoperatorstate=false \ + --defencoding=none + \ No newline at end of file diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_client_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_client_gen.go index 057453e2257..1e41e0e09df 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_client_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_client_gen.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ReceiverClient struct { @@ -76,24 +75,6 @@ func (c *ReceiverClient) Patch(ctx context.Context, identifier resource.Identifi return c.client.Patch(ctx, identifier, req, opts) } -func (c *ReceiverClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus ReceiverStatus, opts resource.UpdateOptions) (*Receiver, error) { - return c.client.Update(ctx, &Receiver{ - TypeMeta: metav1.TypeMeta{ - Kind: ReceiverKind().Kind(), - APIVersion: GroupVersion.Identifier(), - }, - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: opts.ResourceVersion, - Namespace: identifier.Namespace, - Name: identifier.Name, - }, - Status: newStatus, - }, resource.UpdateOptions{ - Subresource: "status", - ResourceVersion: opts.ResourceVersion, - }) -} - func (c *ReceiverClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { return c.client.Delete(ctx, identifier, opts) } diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_object_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_object_gen.go index 3ff0363cc88..00bb1a34745 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_object_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_object_gen.go @@ -21,8 +21,6 @@ type Receiver struct { // Spec is the spec of the Receiver Spec ReceiverSpec `json:"spec" yaml:"spec"` - - Status ReceiverStatus `json:"status" yaml:"status"` } func (o *Receiver) GetSpec() any { @@ -39,15 +37,11 @@ func (o *Receiver) SetSpec(spec any) error { } func (o *Receiver) GetSubresources() map[string]any { - return map[string]any{ - "status": o.Status, - } + return map[string]any{} } func (o *Receiver) GetSubresource(name string) (any, bool) { switch name { - case "status": - return o.Status, true default: return nil, false } @@ -55,13 +49,6 @@ func (o *Receiver) GetSubresource(name string) (any, bool) { func (o *Receiver) SetSubresource(name string, value any) error { switch name { - case "status": - cast, ok := value.(ReceiverStatus) - if !ok { - return fmt.Errorf("cannot set status type %#v, not of type ReceiverStatus", value) - } - o.Status = cast - return nil default: return fmt.Errorf("subresource '%s' does not exist", name) } @@ -233,7 +220,6 @@ func (o *Receiver) DeepCopyInto(dst *Receiver) { dst.TypeMeta.Kind = o.TypeMeta.Kind o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) o.Spec.DeepCopyInto(&dst.Spec) - o.Status.DeepCopyInto(&dst.Status) } // Interface compliance compile-time check @@ -305,15 +291,3 @@ func (s *ReceiverSpec) DeepCopy() *ReceiverSpec { func (s *ReceiverSpec) DeepCopyInto(dst *ReceiverSpec) { resource.CopyObjectInto(dst, s) } - -// DeepCopy creates a full deep copy of ReceiverStatus -func (s *ReceiverStatus) DeepCopy() *ReceiverStatus { - cpy := &ReceiverStatus{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies ReceiverStatus into another ReceiverStatus object -func (s *ReceiverStatus) DeepCopyInto(dst *ReceiverStatus) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_client_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_client_gen.go index da611aa591a..27d605e1898 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_client_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_client_gen.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type RoutingTreeClient struct { @@ -76,24 +75,6 @@ func (c *RoutingTreeClient) Patch(ctx context.Context, identifier resource.Ident return c.client.Patch(ctx, identifier, req, opts) } -func (c *RoutingTreeClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus RoutingTreeStatus, opts resource.UpdateOptions) (*RoutingTree, error) { - return c.client.Update(ctx, &RoutingTree{ - TypeMeta: metav1.TypeMeta{ - Kind: RoutingTreeKind().Kind(), - APIVersion: GroupVersion.Identifier(), - }, - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: opts.ResourceVersion, - Namespace: identifier.Namespace, - Name: identifier.Name, - }, - Status: newStatus, - }, resource.UpdateOptions{ - Subresource: "status", - ResourceVersion: opts.ResourceVersion, - }) -} - func (c *RoutingTreeClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { return c.client.Delete(ctx, identifier, opts) } diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_object_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_object_gen.go index f51fc9732a9..e59f0dada5c 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_object_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_object_gen.go @@ -21,8 +21,6 @@ type RoutingTree struct { // Spec is the spec of the RoutingTree Spec RoutingTreeSpec `json:"spec" yaml:"spec"` - - Status RoutingTreeStatus `json:"status" yaml:"status"` } func (o *RoutingTree) GetSpec() any { @@ -39,15 +37,11 @@ func (o *RoutingTree) SetSpec(spec any) error { } func (o *RoutingTree) GetSubresources() map[string]any { - return map[string]any{ - "status": o.Status, - } + return map[string]any{} } func (o *RoutingTree) GetSubresource(name string) (any, bool) { switch name { - case "status": - return o.Status, true default: return nil, false } @@ -55,13 +49,6 @@ func (o *RoutingTree) GetSubresource(name string) (any, bool) { func (o *RoutingTree) SetSubresource(name string, value any) error { switch name { - case "status": - cast, ok := value.(RoutingTreeStatus) - if !ok { - return fmt.Errorf("cannot set status type %#v, not of type RoutingTreeStatus", value) - } - o.Status = cast - return nil default: return fmt.Errorf("subresource '%s' does not exist", name) } @@ -233,7 +220,6 @@ func (o *RoutingTree) DeepCopyInto(dst *RoutingTree) { dst.TypeMeta.Kind = o.TypeMeta.Kind o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) o.Spec.DeepCopyInto(&dst.Spec) - o.Status.DeepCopyInto(&dst.Status) } // Interface compliance compile-time check @@ -305,15 +291,3 @@ func (s *RoutingTreeSpec) DeepCopy() *RoutingTreeSpec { func (s *RoutingTreeSpec) DeepCopyInto(dst *RoutingTreeSpec) { resource.CopyObjectInto(dst, s) } - -// DeepCopy creates a full deep copy of RoutingTreeStatus -func (s *RoutingTreeStatus) DeepCopy() *RoutingTreeStatus { - cpy := &RoutingTreeStatus{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies RoutingTreeStatus into another RoutingTreeStatus object -func (s *RoutingTreeStatus) DeepCopyInto(dst *RoutingTreeStatus) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_status_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_status_gen.go deleted file mode 100644 index 4ee320a974c..00000000000 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/routingtree_status_gen.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// +k8s:openapi-gen=true -type RoutingTreestatusOperatorState struct { - // lastEvaluation is the ResourceVersion last evaluated - LastEvaluation string `json:"lastEvaluation"` - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - State RoutingTreeStatusOperatorStateState `json:"state"` - // descriptiveState is an optional more descriptive state field which has no requirements on format - DescriptiveState *string `json:"descriptiveState,omitempty"` - // details contains any extra information that is operator-specific - Details map[string]interface{} `json:"details,omitempty"` -} - -// NewRoutingTreestatusOperatorState creates a new RoutingTreestatusOperatorState object. -func NewRoutingTreestatusOperatorState() *RoutingTreestatusOperatorState { - return &RoutingTreestatusOperatorState{} -} - -// +k8s:openapi-gen=true -type RoutingTreeStatus struct { - // 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 map[string]RoutingTreestatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` -} - -// NewRoutingTreeStatus creates a new RoutingTreeStatus object. -func NewRoutingTreeStatus() *RoutingTreeStatus { - return &RoutingTreeStatus{} -} - -// +k8s:openapi-gen=true -type RoutingTreeStatusOperatorStateState string - -const ( - RoutingTreeStatusOperatorStateStateSuccess RoutingTreeStatusOperatorStateState = "success" - RoutingTreeStatusOperatorStateStateInProgress RoutingTreeStatusOperatorStateState = "in_progress" - RoutingTreeStatusOperatorStateStateFailed RoutingTreeStatusOperatorStateState = "failed" -) diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_client_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_client_gen.go index 54bbfb9ceb2..265021f4b71 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_client_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_client_gen.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type TemplateGroupClient struct { @@ -76,24 +75,6 @@ func (c *TemplateGroupClient) Patch(ctx context.Context, identifier resource.Ide return c.client.Patch(ctx, identifier, req, opts) } -func (c *TemplateGroupClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus TemplateGroupStatus, opts resource.UpdateOptions) (*TemplateGroup, error) { - return c.client.Update(ctx, &TemplateGroup{ - TypeMeta: metav1.TypeMeta{ - Kind: TemplateGroupKind().Kind(), - APIVersion: GroupVersion.Identifier(), - }, - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: opts.ResourceVersion, - Namespace: identifier.Namespace, - Name: identifier.Name, - }, - Status: newStatus, - }, resource.UpdateOptions{ - Subresource: "status", - ResourceVersion: opts.ResourceVersion, - }) -} - func (c *TemplateGroupClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { return c.client.Delete(ctx, identifier, opts) } diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_object_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_object_gen.go index 459b07343c5..0866bcb258c 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_object_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_object_gen.go @@ -21,8 +21,6 @@ type TemplateGroup struct { // Spec is the spec of the TemplateGroup Spec TemplateGroupSpec `json:"spec" yaml:"spec"` - - Status TemplateGroupStatus `json:"status" yaml:"status"` } func (o *TemplateGroup) GetSpec() any { @@ -39,15 +37,11 @@ func (o *TemplateGroup) SetSpec(spec any) error { } func (o *TemplateGroup) GetSubresources() map[string]any { - return map[string]any{ - "status": o.Status, - } + return map[string]any{} } func (o *TemplateGroup) GetSubresource(name string) (any, bool) { switch name { - case "status": - return o.Status, true default: return nil, false } @@ -55,13 +49,6 @@ func (o *TemplateGroup) GetSubresource(name string) (any, bool) { func (o *TemplateGroup) SetSubresource(name string, value any) error { switch name { - case "status": - cast, ok := value.(TemplateGroupStatus) - if !ok { - return fmt.Errorf("cannot set status type %#v, not of type TemplateGroupStatus", value) - } - o.Status = cast - return nil default: return fmt.Errorf("subresource '%s' does not exist", name) } @@ -233,7 +220,6 @@ func (o *TemplateGroup) DeepCopyInto(dst *TemplateGroup) { dst.TypeMeta.Kind = o.TypeMeta.Kind o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) o.Spec.DeepCopyInto(&dst.Spec) - o.Status.DeepCopyInto(&dst.Status) } // Interface compliance compile-time check @@ -305,15 +291,3 @@ func (s *TemplateGroupSpec) DeepCopy() *TemplateGroupSpec { func (s *TemplateGroupSpec) DeepCopyInto(dst *TemplateGroupSpec) { resource.CopyObjectInto(dst, s) } - -// DeepCopy creates a full deep copy of TemplateGroupStatus -func (s *TemplateGroupStatus) DeepCopy() *TemplateGroupStatus { - cpy := &TemplateGroupStatus{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies TemplateGroupStatus into another TemplateGroupStatus object -func (s *TemplateGroupStatus) DeepCopyInto(dst *TemplateGroupStatus) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_status_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_status_gen.go deleted file mode 100644 index 8312358cda5..00000000000 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/templategroup_status_gen.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// +k8s:openapi-gen=true -type TemplateGroupstatusOperatorState struct { - // lastEvaluation is the ResourceVersion last evaluated - LastEvaluation string `json:"lastEvaluation"` - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - State TemplateGroupStatusOperatorStateState `json:"state"` - // descriptiveState is an optional more descriptive state field which has no requirements on format - DescriptiveState *string `json:"descriptiveState,omitempty"` - // details contains any extra information that is operator-specific - Details map[string]interface{} `json:"details,omitempty"` -} - -// NewTemplateGroupstatusOperatorState creates a new TemplateGroupstatusOperatorState object. -func NewTemplateGroupstatusOperatorState() *TemplateGroupstatusOperatorState { - return &TemplateGroupstatusOperatorState{} -} - -// +k8s:openapi-gen=true -type TemplateGroupStatus struct { - // 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 map[string]TemplateGroupstatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` -} - -// NewTemplateGroupStatus creates a new TemplateGroupStatus object. -func NewTemplateGroupStatus() *TemplateGroupStatus { - return &TemplateGroupStatus{} -} - -// +k8s:openapi-gen=true -type TemplateGroupStatusOperatorStateState string - -const ( - TemplateGroupStatusOperatorStateStateSuccess TemplateGroupStatusOperatorStateState = "success" - TemplateGroupStatusOperatorStateStateInProgress TemplateGroupStatusOperatorStateState = "in_progress" - TemplateGroupStatusOperatorStateStateFailed TemplateGroupStatusOperatorStateState = "failed" -) diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_client_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_client_gen.go index afa269ac96f..09ddb684613 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_client_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_client_gen.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type TimeIntervalClient struct { @@ -76,24 +75,6 @@ func (c *TimeIntervalClient) Patch(ctx context.Context, identifier resource.Iden return c.client.Patch(ctx, identifier, req, opts) } -func (c *TimeIntervalClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus TimeIntervalStatus, opts resource.UpdateOptions) (*TimeInterval, error) { - return c.client.Update(ctx, &TimeInterval{ - TypeMeta: metav1.TypeMeta{ - Kind: TimeIntervalKind().Kind(), - APIVersion: GroupVersion.Identifier(), - }, - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: opts.ResourceVersion, - Namespace: identifier.Namespace, - Name: identifier.Name, - }, - Status: newStatus, - }, resource.UpdateOptions{ - Subresource: "status", - ResourceVersion: opts.ResourceVersion, - }) -} - func (c *TimeIntervalClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { return c.client.Delete(ctx, identifier, opts) } diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_object_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_object_gen.go index b4646bb8422..0ef813ee40c 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_object_gen.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_object_gen.go @@ -21,8 +21,6 @@ type TimeInterval struct { // Spec is the spec of the TimeInterval Spec TimeIntervalSpec `json:"spec" yaml:"spec"` - - Status TimeIntervalStatus `json:"status" yaml:"status"` } func (o *TimeInterval) GetSpec() any { @@ -39,15 +37,11 @@ func (o *TimeInterval) SetSpec(spec any) error { } func (o *TimeInterval) GetSubresources() map[string]any { - return map[string]any{ - "status": o.Status, - } + return map[string]any{} } func (o *TimeInterval) GetSubresource(name string) (any, bool) { switch name { - case "status": - return o.Status, true default: return nil, false } @@ -55,13 +49,6 @@ func (o *TimeInterval) GetSubresource(name string) (any, bool) { func (o *TimeInterval) SetSubresource(name string, value any) error { switch name { - case "status": - cast, ok := value.(TimeIntervalStatus) - if !ok { - return fmt.Errorf("cannot set status type %#v, not of type TimeIntervalStatus", value) - } - o.Status = cast - return nil default: return fmt.Errorf("subresource '%s' does not exist", name) } @@ -233,7 +220,6 @@ func (o *TimeInterval) DeepCopyInto(dst *TimeInterval) { dst.TypeMeta.Kind = o.TypeMeta.Kind o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) o.Spec.DeepCopyInto(&dst.Spec) - o.Status.DeepCopyInto(&dst.Status) } // Interface compliance compile-time check @@ -305,15 +291,3 @@ func (s *TimeIntervalSpec) DeepCopy() *TimeIntervalSpec { func (s *TimeIntervalSpec) DeepCopyInto(dst *TimeIntervalSpec) { resource.CopyObjectInto(dst, s) } - -// DeepCopy creates a full deep copy of TimeIntervalStatus -func (s *TimeIntervalStatus) DeepCopy() *TimeIntervalStatus { - cpy := &TimeIntervalStatus{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies TimeIntervalStatus into another TimeIntervalStatus object -func (s *TimeIntervalStatus) DeepCopyInto(dst *TimeIntervalStatus) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_status_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_status_gen.go deleted file mode 100644 index a6128624acd..00000000000 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/timeinterval_status_gen.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// +k8s:openapi-gen=true -type TimeIntervalstatusOperatorState struct { - // lastEvaluation is the ResourceVersion last evaluated - LastEvaluation string `json:"lastEvaluation"` - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - State TimeIntervalStatusOperatorStateState `json:"state"` - // descriptiveState is an optional more descriptive state field which has no requirements on format - DescriptiveState *string `json:"descriptiveState,omitempty"` - // details contains any extra information that is operator-specific - Details map[string]interface{} `json:"details,omitempty"` -} - -// NewTimeIntervalstatusOperatorState creates a new TimeIntervalstatusOperatorState object. -func NewTimeIntervalstatusOperatorState() *TimeIntervalstatusOperatorState { - return &TimeIntervalstatusOperatorState{} -} - -// +k8s:openapi-gen=true -type TimeIntervalStatus struct { - // 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 map[string]TimeIntervalstatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` -} - -// NewTimeIntervalStatus creates a new TimeIntervalStatus object. -func NewTimeIntervalStatus() *TimeIntervalStatus { - return &TimeIntervalStatus{} -} - -// +k8s:openapi-gen=true -type TimeIntervalStatusOperatorStateState string - -const ( - TimeIntervalStatusOperatorStateStateSuccess TimeIntervalStatusOperatorStateState = "success" - TimeIntervalStatusOperatorStateStateInProgress TimeIntervalStatusOperatorStateState = "in_progress" - TimeIntervalStatusOperatorStateStateFailed TimeIntervalStatusOperatorStateState = "failed" -) diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/zz_openapi_gen.go b/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/zz_openapi_gen.go deleted file mode 100644 index 782a0d1ca54..00000000000 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/zz_openapi_gen.go +++ /dev/null @@ -1,1321 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -// Code generated by grafana-app-sdk. DO NOT EDIT. - -package v0alpha1 - -import ( - common "k8s.io/kube-openapi/pkg/common" - spec "k8s.io/kube-openapi/pkg/validation/spec" -) - -func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { - return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.Receiver": schema_pkg_apis_alertingnotifications_v0alpha1_Receiver(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverIntegration": schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverIntegration(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverList": schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverList(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverSpec": schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverSpec(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverStatus": schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverStatus(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverstatusOperatorState": schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverstatusOperatorState(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTree": schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTree(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeList": schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeList(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeMatcher": schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeMatcher(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeRoute": schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeRoute(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeRouteDefaults": schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeRouteDefaults(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeSpec": schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeSpec(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeStatus": schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeStatus(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreestatusOperatorState": schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreestatusOperatorState(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroup": schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroup(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupList": schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroupList(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupSpec": schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroupSpec(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupStatus": schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroupStatus(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupstatusOperatorState": schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroupstatusOperatorState(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeInterval": schema_pkg_apis_alertingnotifications_v0alpha1_TimeInterval(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalInterval": schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalInterval(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalList": schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalList(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalSpec": schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalSpec(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalStatus": schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalStatus(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalTimeRange": schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalTimeRange(ref), - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalstatusOperatorState": schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalstatusOperatorState(ref), - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_Receiver(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Description: "Spec is the spec of the Receiver", - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverStatus"), - }, - }, - }, - Required: []string{"metadata", "spec", "status"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverSpec", "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverIntegration(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "uid": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "type": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "version": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "disableResolveMessage": { - SchemaProps: spec.SchemaProps{ - Type: []string{"boolean"}, - Format: "", - }, - }, - "settings": { - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - "secureFields": { - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - }, - }, - }, - }, - Required: []string{"type", "version", "settings"}, - }, - }, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.Receiver"), - }, - }, - }, - }, - }, - }, - Required: []string{"metadata", "items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.Receiver", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "title": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "integrations": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverIntegration"), - }, - }, - }, - }, - }, - }, - Required: []string{"title", "integrations"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverIntegration"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "operatorStates": { - SchemaProps: spec.SchemaProps{ - Description: "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.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverstatusOperatorState"), - }, - }, - }, - }, - }, - "additionalFields": { - SchemaProps: spec.SchemaProps{ - Description: "additionalFields is reserved for future use", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.ReceiverstatusOperatorState"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_ReceiverstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "lastEvaluation": { - SchemaProps: spec.SchemaProps{ - Description: "lastEvaluation is the ResourceVersion last evaluated", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "state": { - SchemaProps: spec.SchemaProps{ - Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "descriptiveState": { - SchemaProps: spec.SchemaProps{ - Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", - Type: []string{"string"}, - Format: "", - }, - }, - "details": { - SchemaProps: spec.SchemaProps{ - Description: "details contains any extra information that is operator-specific", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - Required: []string{"lastEvaluation", "state"}, - }, - }, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTree(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Description: "Spec is the spec of the RoutingTree", - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeStatus"), - }, - }, - }, - Required: []string{"metadata", "spec", "status"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeSpec", "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTree"), - }, - }, - }, - }, - }, - }, - Required: []string{"metadata", "items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTree", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeMatcher(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "type": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "label": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "value": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"type", "label", "value"}, - }, - }, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeRoute(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "receiver": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "matchers": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeMatcher"), - }, - }, - }, - }, - }, - "continue": { - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - "group_by": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "mute_time_intervals": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "active_time_intervals": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "routes": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeRoute"), - }, - }, - }, - }, - }, - "group_wait": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "group_interval": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "repeat_interval": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"continue"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeMatcher", "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeRoute"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeRouteDefaults(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "receiver": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "group_by": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "group_wait": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "group_interval": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - "repeat_interval": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"receiver"}, - }, - }, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "defaults": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeRouteDefaults"), - }, - }, - "routes": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeRoute"), - }, - }, - }, - }, - }, - }, - Required: []string{"defaults", "routes"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeRoute", "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreeRouteDefaults"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "operatorStates": { - SchemaProps: spec.SchemaProps{ - Description: "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.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreestatusOperatorState"), - }, - }, - }, - }, - }, - "additionalFields": { - SchemaProps: spec.SchemaProps{ - Description: "additionalFields is reserved for future use", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.RoutingTreestatusOperatorState"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_RoutingTreestatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "lastEvaluation": { - SchemaProps: spec.SchemaProps{ - Description: "lastEvaluation is the ResourceVersion last evaluated", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "state": { - SchemaProps: spec.SchemaProps{ - Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "descriptiveState": { - SchemaProps: spec.SchemaProps{ - Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", - Type: []string{"string"}, - Format: "", - }, - }, - "details": { - SchemaProps: spec.SchemaProps{ - Description: "details contains any extra information that is operator-specific", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - Required: []string{"lastEvaluation", "state"}, - }, - }, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroup(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Description: "Spec is the spec of the TemplateGroup", - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupStatus"), - }, - }, - }, - Required: []string{"metadata", "spec", "status"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupSpec", "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroupList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroup"), - }, - }, - }, - }, - }, - }, - Required: []string{"metadata", "items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroup", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroupSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "title": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "content": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"title", "content"}, - }, - }, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroupStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "operatorStates": { - SchemaProps: spec.SchemaProps{ - Description: "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.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupstatusOperatorState"), - }, - }, - }, - }, - }, - "additionalFields": { - SchemaProps: spec.SchemaProps{ - Description: "additionalFields is reserved for future use", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TemplateGroupstatusOperatorState"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TemplateGroupstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "lastEvaluation": { - SchemaProps: spec.SchemaProps{ - Description: "lastEvaluation is the ResourceVersion last evaluated", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "state": { - SchemaProps: spec.SchemaProps{ - Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "descriptiveState": { - SchemaProps: spec.SchemaProps{ - Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", - Type: []string{"string"}, - Format: "", - }, - }, - "details": { - SchemaProps: spec.SchemaProps{ - Description: "details contains any extra information that is operator-specific", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - Required: []string{"lastEvaluation", "state"}, - }, - }, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TimeInterval(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Description: "Spec is the spec of the TimeInterval", - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalStatus"), - }, - }, - }, - Required: []string{"metadata", "spec", "status"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalSpec", "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalInterval(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "times": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalTimeRange"), - }, - }, - }, - }, - }, - "weekdays": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "days_of_month": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "months": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "years": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "location": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalTimeRange"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "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", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeInterval"), - }, - }, - }, - }, - }, - }, - Required: []string{"metadata", "items"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeInterval", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "name": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "time_intervals": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalInterval"), - }, - }, - }, - }, - }, - }, - Required: []string{"name", "time_intervals"}, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalInterval"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "operatorStates": { - SchemaProps: spec.SchemaProps{ - Description: "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.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalstatusOperatorState"), - }, - }, - }, - }, - }, - "additionalFields": { - SchemaProps: spec.SchemaProps{ - Description: "additionalFields is reserved for future use", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1.TimeIntervalstatusOperatorState"}, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalTimeRange(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "start_time": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "end_time": { - SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"start_time", "end_time"}, - }, - }, - } -} - -func schema_pkg_apis_alertingnotifications_v0alpha1_TimeIntervalstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "lastEvaluation": { - SchemaProps: spec.SchemaProps{ - Description: "lastEvaluation is the ResourceVersion last evaluated", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "state": { - SchemaProps: spec.SchemaProps{ - Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "descriptiveState": { - SchemaProps: spec.SchemaProps{ - Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", - Type: []string{"string"}, - Format: "", - }, - }, - "details": { - SchemaProps: spec.SchemaProps{ - Description: "details contains any extra information that is operator-specific", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - Required: []string{"lastEvaluation", "state"}, - }, - }, - } -} diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications_manifest.go b/apps/alerting/notifications/pkg/apis/alertingnotifications_manifest.go index 97b11485dd1..cebfbec12f1 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications_manifest.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications_manifest.go @@ -20,16 +20,16 @@ import ( ) var ( - rawSchemaReceiverv0alpha1 = []byte(`{"Integration":{"additionalProperties":false,"properties":{"disableResolveMessage":{"type":"boolean"},"secureFields":{"additionalProperties":{"type":"boolean"},"type":"object"},"settings":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"type":{"type":"string"},"uid":{"type":"string"},"version":{"type":"string"}},"required":["type","version","settings"],"type":"object"},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"Receiver":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"integrations":{"items":{"$ref":"#/components/schemas/Integration"},"type":"array"},"title":{"type":"string"}},"required":["title","integrations"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + rawSchemaReceiverv0alpha1 = []byte(`{"Integration":{"additionalProperties":false,"properties":{"disableResolveMessage":{"type":"boolean"},"secureFields":{"additionalProperties":{"type":"boolean"},"type":"object"},"settings":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"type":{"type":"string"},"uid":{"type":"string"},"version":{"type":"string"}},"required":["type","version","settings"],"type":"object"},"Receiver":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"integrations":{"items":{"$ref":"#/components/schemas/Integration"},"type":"array"},"title":{"type":"string"}},"required":["title","integrations"],"type":"object"}}`) versionSchemaReceiverv0alpha1 app.VersionSchema _ = json.Unmarshal(rawSchemaReceiverv0alpha1, &versionSchemaReceiverv0alpha1) - rawSchemaRoutingTreev0alpha1 = []byte(`{"Matcher":{"additionalProperties":false,"properties":{"label":{"type":"string"},"type":{"enum":["=","!=","=~","!~"],"type":"string"},"value":{"type":"string"}},"required":["type","label","value"],"type":"object"},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"Route":{"additionalProperties":false,"properties":{"active_time_intervals":{"items":{"type":"string"},"type":"array"},"continue":{"type":"boolean"},"group_by":{"items":{"type":"string"},"type":"array"},"group_interval":{"type":"string"},"group_wait":{"type":"string"},"matchers":{"items":{"$ref":"#/components/schemas/Matcher"},"type":"array"},"mute_time_intervals":{"items":{"type":"string"},"type":"array"},"receiver":{"type":"string"},"repeat_interval":{"type":"string"},"routes":{"items":{"$ref":"#/components/schemas/Route"},"type":"array"}},"required":["continue"],"type":"object"},"RouteDefaults":{"additionalProperties":false,"properties":{"group_by":{"items":{"type":"string"},"type":"array"},"group_interval":{"type":"string"},"group_wait":{"type":"string"},"receiver":{"type":"string"},"repeat_interval":{"type":"string"}},"required":["receiver"],"type":"object"},"RoutingTree":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"defaults":{"$ref":"#/components/schemas/RouteDefaults"},"routes":{"items":{"$ref":"#/components/schemas/Route"},"type":"array"}},"required":["defaults","routes"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + rawSchemaRoutingTreev0alpha1 = []byte(`{"Matcher":{"additionalProperties":false,"properties":{"label":{"type":"string"},"type":{"enum":["=","!=","=~","!~"],"type":"string"},"value":{"type":"string"}},"required":["type","label","value"],"type":"object"},"Route":{"additionalProperties":false,"properties":{"active_time_intervals":{"items":{"type":"string"},"type":"array"},"continue":{"type":"boolean"},"group_by":{"items":{"type":"string"},"type":"array"},"group_interval":{"type":"string"},"group_wait":{"type":"string"},"matchers":{"items":{"$ref":"#/components/schemas/Matcher"},"type":"array"},"mute_time_intervals":{"items":{"type":"string"},"type":"array"},"receiver":{"type":"string"},"repeat_interval":{"type":"string"},"routes":{"items":{"$ref":"#/components/schemas/Route"},"type":"array"}},"required":["continue"],"type":"object"},"RouteDefaults":{"additionalProperties":false,"properties":{"group_by":{"items":{"type":"string"},"type":"array"},"group_interval":{"type":"string"},"group_wait":{"type":"string"},"receiver":{"type":"string"},"repeat_interval":{"type":"string"}},"required":["receiver"],"type":"object"},"RoutingTree":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"defaults":{"$ref":"#/components/schemas/RouteDefaults"},"routes":{"items":{"$ref":"#/components/schemas/Route"},"type":"array"}},"required":["defaults","routes"],"type":"object"}}`) versionSchemaRoutingTreev0alpha1 app.VersionSchema _ = json.Unmarshal(rawSchemaRoutingTreev0alpha1, &versionSchemaRoutingTreev0alpha1) - rawSchemaTemplateGroupv0alpha1 = []byte(`{"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"TemplateGroup":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"content":{"type":"string"},"title":{"type":"string"}},"required":["title","content"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + rawSchemaTemplateGroupv0alpha1 = []byte(`{"TemplateGroup":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"content":{"type":"string"},"title":{"type":"string"}},"required":["title","content"],"type":"object"}}`) versionSchemaTemplateGroupv0alpha1 app.VersionSchema _ = json.Unmarshal(rawSchemaTemplateGroupv0alpha1, &versionSchemaTemplateGroupv0alpha1) - rawSchemaTimeIntervalv0alpha1 = []byte(`{"Interval":{"additionalProperties":false,"properties":{"days_of_month":{"items":{"type":"string"},"type":"array"},"location":{"type":"string"},"months":{"items":{"type":"string"},"type":"array"},"times":{"items":{"$ref":"#/components/schemas/TimeRange"},"type":"array"},"weekdays":{"items":{"type":"string"},"type":"array"},"years":{"items":{"type":"string"},"type":"array"}},"type":"object"},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"TimeInterval":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"TimeRange":{"additionalProperties":false,"properties":{"end_time":{"type":"string"},"start_time":{"type":"string"}},"required":["start_time","end_time"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"name":{"type":"string"},"time_intervals":{"items":{"$ref":"#/components/schemas/Interval"},"type":"array"}},"required":["name","time_intervals"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + rawSchemaTimeIntervalv0alpha1 = []byte(`{"Interval":{"additionalProperties":false,"properties":{"days_of_month":{"items":{"type":"string"},"type":"array"},"location":{"type":"string"},"months":{"items":{"type":"string"},"type":"array"},"times":{"items":{"$ref":"#/components/schemas/TimeRange"},"type":"array"},"weekdays":{"items":{"type":"string"},"type":"array"},"years":{"items":{"type":"string"},"type":"array"}},"type":"object"},"TimeInterval":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"TimeRange":{"additionalProperties":false,"properties":{"end_time":{"type":"string"},"start_time":{"type":"string"}},"required":["start_time","end_time"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"name":{"type":"string"},"time_intervals":{"items":{"$ref":"#/components/schemas/Interval"},"type":"array"}},"required":["name","time_intervals"],"type":"object"}}`) versionSchemaTimeIntervalv0alpha1 app.VersionSchema _ = json.Unmarshal(rawSchemaTimeIntervalv0alpha1, &versionSchemaTimeIntervalv0alpha1) ) diff --git a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue index f0278fc431c..ef1490091c2 100644 --- a/apps/dashboard/kinds/v2beta1/dashboard_spec.cue +++ b/apps/dashboard/kinds/v2beta1/dashboard_spec.cue @@ -452,6 +452,7 @@ QueryOptionsSpec: { interval?: string cacheTimeout?: string hideTimeOverride?: bool + timeCompare?: string } DataQueryKind: { diff --git a/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go b/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go new file mode 100644 index 00000000000..38792ade026 --- /dev/null +++ b/apps/dashboard/pkg/apis/dashboard/cuevalidator/validator.go @@ -0,0 +1,29 @@ +package cuevalidator + +import ( + "sync" + + "cuelang.org/go/cue" + cuejson "cuelang.org/go/encoding/json" +) + +// Validator provides thread-safe CUE schema validation. +// +// CUE is not safe for concurrent use: https://github.com/cue-lang/cue/discussions/1205#discussioncomment-1189238 +// This validator uses a mutex to protect concurrent access to the underlying CUE validation. +type Validator struct { + schema cue.Value + mu sync.Mutex +} + +func NewValidator(schema cue.Value) *Validator { + return &Validator{ + schema: schema, + } +} + +func (v *Validator) Validate(data []byte) error { + v.mu.Lock() + defer v.mu.Unlock() + return cuejson.Validate(data, v.schema) +} diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue index 526ddba3bfc..7d8a0e551bf 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_kind.cue @@ -670,6 +670,10 @@ lineage: schemas: [{ // Controls if the timeFrom or timeShift overrides are shown in the panel header hideTimeOverride?: bool + // Compare the current time range with a previous period + // For example "1d" to compare current period but shifted back 1 day + timeCompare?: string + // Dynamically load the panel libraryPanel?: #LibraryPanelRef diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/librarypanel.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/librarypanel.go new file mode 100644 index 00000000000..be4ea4d037c --- /dev/null +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/librarypanel.go @@ -0,0 +1,91 @@ +package v0alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" +) + +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type LibraryPanel struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Panel properties + Spec LibraryPanelSpec `json:"spec"` + + // Status will show errors + Status *LibraryPanelStatus `json:"status,omitempty"` +} + +// +k8s:deepcopy-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type LibraryPanelList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + Items []LibraryPanel `json:"items"` +} + +// +k8s:deepcopy-gen=true +type LibraryPanelSpec struct { + // The panel type + Type string `json:"type"` + + // The panel type + PluginVersion string `json:"pluginVersion,omitempty"` + + // The title of the library panel + Title string `json:"title,omitempty"` + + // The title of the panel when displayed in the dashboard + PanelTitle string `json:"panelTitle,omitempty"` + + // Library panel description + Description string `json:"description,omitempty"` + + // The options schema depends on the panel type + Options common.Unstructured `json:"options"` + + // The fieldConfig schema depends on the panel type + FieldConfig common.Unstructured `json:"fieldConfig"` + + // The default datasource type + Datasource *data.DataSourceRef `json:"datasource,omitempty"` + + // The grid position + GridPos GridPos `json:"gridPos,omitempty"` + + // Whether the panel is transparent + Transparent bool `json:"transparent,omitempty"` + + // The links for the panel + Links []common.Unstructured `json:"links,omitempty"` + + // The datasource queries + // +listType=atomic + Targets []data.DataQuery `json:"targets,omitempty"` +} + +// +k8s:deepcopy-gen=true +type GridPos struct { + W int `json:"w"` + H int `json:"h"` + X int `json:"x"` + Y int `json:"y"` +} + +// +k8s:deepcopy-gen=true +type LibraryPanelStatus struct { + // Translation warnings (mostly things that were in SQL columns but not found in the saved body) + Warnings []string `json:"warnings,omitempty"` + + // The properties previously stored in SQL that are not included in this model + Missing common.Unstructured `json:"missing,omitempty"` +} diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/types.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/types.go index 141141f861f..7f813a12a1f 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/types.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/types.go @@ -1,95 +1,5 @@ package v0alpha1 -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" -) - -// +k8s:deepcopy-gen=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type LibraryPanel struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata - // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata - // +optional - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Panel properties - Spec LibraryPanelSpec `json:"spec"` - - // Status will show errors - Status *LibraryPanelStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type LibraryPanelList struct { - metav1.TypeMeta `json:",inline"` - // +optional - metav1.ListMeta `json:"metadata,omitempty"` - - Items []LibraryPanel `json:"items"` -} - -// +k8s:deepcopy-gen=true -type LibraryPanelSpec struct { - // The panel type - Type string `json:"type"` - - // The panel type - PluginVersion string `json:"pluginVersion,omitempty"` - - // The title of the library panel - Title string `json:"title,omitempty"` - - // The title of the panel when displayed in the dashboard - PanelTitle string `json:"panelTitle,omitempty"` - - // Library panel description - Description string `json:"description,omitempty"` - - // The options schema depends on the panel type - Options common.Unstructured `json:"options"` - - // The fieldConfig schema depends on the panel type - FieldConfig common.Unstructured `json:"fieldConfig"` - - // The default datasource type - Datasource *data.DataSourceRef `json:"datasource,omitempty"` - - // The grid position - GridPos GridPos `json:"gridPos,omitempty"` - - // Whether the panel is transparent - Transparent bool `json:"transparent,omitempty"` - - // The links for the panel - Links []common.Unstructured `json:"links,omitempty"` - - // The datasource queries - // +listType=set - Targets []data.DataQuery `json:"targets,omitempty"` -} - -// +k8s:deepcopy-gen=true -type GridPos struct { - W int `json:"w"` - H int `json:"h"` - X int `json:"x"` - Y int `json:"y"` -} - -// +k8s:deepcopy-gen=true -type LibraryPanelStatus struct { - // Translation warnings (mostly things that were in SQL columns but not found in the saved body) - Warnings []string `json:"warnings,omitempty"` - - // The properties previously stored in SQL that are not included in this model - Missing common.Unstructured `json:"missing,omitempty"` -} - // This is like the legacy DTO where access and metadata are all returned in a single call // +k8s:deepcopy-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go index 6fdf1dd4515..7c5573f600a 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/validation.go @@ -7,13 +7,13 @@ import ( "strings" "sync" + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" "k8s.io/apimachinery/pkg/util/validation/field" "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" - cuejson "cuelang.org/go/encoding/json" ) func ValidateDashboardSpec(obj *Dashboard, forceValidation bool) (field.ErrorList, field.ErrorList) { @@ -33,7 +33,7 @@ func ValidateDashboardSpec(obj *Dashboard, forceValidation bool) (field.ErrorLis }, schemaVersionError } - if err := cuejson.Validate(data, getCueSchema()); err != nil { + if err := getValidator().Validate(data); err != nil { errs := field.ErrorList{} for _, e := range errors.Errors(err) { @@ -71,20 +71,21 @@ func formatErrorPath(path []string) string { } var ( - compiledSchema cue.Value - getSchemaOnce sync.Once + validator *cuevalidator.Validator + getSchemaOnce sync.Once ) //go:embed dashboard_kind.cue var schemaSource string -func getCueSchema() cue.Value { +func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { cueCtx := cuecontext.New() - compiledSchema = cueCtx.CompileString(schemaSource).LookupPath( + compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( cue.ParsePath("lineage.schemas[0].schema.spec"), ) + validator = cuevalidator.NewValidator(compiledSchema) }) - return compiledSchema + return validator } diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go index efb39a56d7d..1e81a87f2fa 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go @@ -882,7 +882,7 @@ func schema_pkg_apis_dashboard_v0alpha1_LibraryPanelSpec(ref common.ReferenceCal "targets": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ - "x-kubernetes-list-type": "set", + "x-kubernetes-list-type": "atomic", }, }, SchemaProps: spec.SchemaProps{ diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue index 526ddba3bfc..7d8a0e551bf 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_kind.cue @@ -670,6 +670,10 @@ lineage: schemas: [{ // Controls if the timeFrom or timeShift overrides are shown in the panel header hideTimeOverride?: bool + // Compare the current time range with a previous period + // For example "1d" to compare current period but shifted back 1 day + timeCompare?: string + // Dynamically load the panel libraryPanel?: #LibraryPanelRef diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go index 7020aa46b90..b48e38ffd7b 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/validation.go @@ -12,8 +12,8 @@ import ( "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" - cuejson "cuelang.org/go/encoding/json" + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" "github.com/grafana/grafana/apps/dashboard/pkg/migration/schemaversion" ) @@ -34,7 +34,7 @@ func ValidateDashboardSpec(obj *Dashboard, forceValidation bool) (field.ErrorLis }, schemaVersionError } - if err := cuejson.Validate(data, getCueSchema()); err != nil { + if err := getValidator().Validate(data); err != nil { errs := field.ErrorList{} for _, e := range errors.Errors(err) { @@ -72,20 +72,21 @@ func formatErrorPath(path []string) string { } var ( - compiledSchema cue.Value - getSchemaOnce sync.Once + validator *cuevalidator.Validator + getSchemaOnce sync.Once ) //go:embed dashboard_kind.cue var schemaSource string -func getCueSchema() cue.Value { +func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { cueCtx := cuecontext.New() - compiledSchema = cueCtx.CompileString(schemaSource).LookupPath( + compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( cue.ParsePath("lineage.schemas[0].schema.spec"), ) + validator = cuevalidator.NewValidator(compiledSchema) }) - return compiledSchema + return validator } diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go index 7c61faa8924..ca9dcd3e514 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/validation.go @@ -12,7 +12,8 @@ import ( "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" - cuejson "cuelang.org/go/encoding/json" + + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" ) func ValidateDashboardSpec(obj *Dashboard) field.ErrorList { @@ -26,7 +27,7 @@ func ValidateDashboardSpec(obj *Dashboard) field.ErrorList { // Custom validation for action query params and headers validateAndTrimActionArrays(obj) - if err := cuejson.Validate(data, getCueSchema()); err != nil { + if err := getValidator().Validate(data); err != nil { errs := field.ErrorList{} for _, e := range errors.Errors(err) { @@ -123,20 +124,21 @@ func formatErrorPath(path []string) string { } var ( - compiledSchema cue.Value - getSchemaOnce sync.Once + validator *cuevalidator.Validator + getSchemaOnce sync.Once ) //go:embed dashboard_spec.cue var schemaSource string -func getCueSchema() cue.Value { +func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { cueCtx := cuecontext.New() - compiledSchema = cueCtx.CompileString(schemaSource).LookupPath( + compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( cue.ParsePath("DashboardSpec"), ) + validator = cuevalidator.NewValidator(compiledSchema) }) - return compiledSchema + return validator } diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue index fb46afb5c76..31603c6240b 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec.cue @@ -456,6 +456,7 @@ QueryOptionsSpec: { interval?: string cacheTimeout?: string hideTimeOverride?: bool + timeCompare?: string } DataQueryKind: { diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go index 35674997511..4229e5765d1 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_spec_gen.go @@ -278,6 +278,7 @@ type DashboardQueryOptionsSpec struct { Interval *string `json:"interval,omitempty"` CacheTimeout *string `json:"cacheTimeout,omitempty"` HideTimeOverride *bool `json:"hideTimeOverride,omitempty"` + TimeCompare *string `json:"timeCompare,omitempty"` } // NewDashboardQueryOptionsSpec creates a new DashboardQueryOptionsSpec object. diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go index 7c859626b98..518c133bcd7 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/validation.go @@ -12,7 +12,8 @@ import ( "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/errors" - cuejson "cuelang.org/go/encoding/json" + + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/cuevalidator" ) func ValidateDashboardSpec(obj *Dashboard) field.ErrorList { @@ -26,7 +27,7 @@ func ValidateDashboardSpec(obj *Dashboard) field.ErrorList { // Custom validation for action query params and headers validateAndTrimActionArrays(obj) - if err := cuejson.Validate(data, getCueSchema()); err != nil { + if err := getValidator().Validate(data); err != nil { errs := field.ErrorList{} for _, e := range errors.Errors(err) { @@ -123,20 +124,21 @@ func formatErrorPath(path []string) string { } var ( - compiledSchema cue.Value - getSchemaOnce sync.Once + validator *cuevalidator.Validator + getSchemaOnce sync.Once ) //go:embed dashboard_spec.cue var schemaSource string -func getCueSchema() cue.Value { +func getValidator() *cuevalidator.Validator { getSchemaOnce.Do(func() { cueCtx := cuecontext.New() - compiledSchema = cueCtx.CompileString(schemaSource).LookupPath( + compiledSchema := cueCtx.CompileString(schemaSource).LookupPath( cue.ParsePath("DashboardSpec"), ) + validator = cuevalidator.NewValidator(compiledSchema) }) - return compiledSchema + return validator } diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go index 5e51134e80c..97fb1c10326 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/zz_generated.openapi.go @@ -3426,6 +3426,12 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardQueryOptionsSpec(ref common.Refe Format: "", }, }, + "timeCompare": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json index 3c8310630d7..dae0d524c23 100644 --- a/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json +++ b/apps/dashboard/pkg/migration/testdata/dev-dashboards-output/panel-table/table_sparkline_cell.v42.json @@ -208,7 +208,27 @@ "value": 80 } ] - } + }, + "actions": [ + { + "fetch": { + "body": "{}", + "headers": [["Content-Type", "application/json"]], + "method": "GET", + "queryParams": [], + "url": "/api/health" + }, + "title": "Get instance health", + "type": "fetch" + } + ], + "links": [ + { + "targetBlank": true, + "title": "Google Grafana", + "url": "https://google.com/search?q=grafana" + } + ] }, "overrides": [] }, @@ -592,4 +612,4 @@ "title": "Panel Tests - Table - Sparklines", "uid": "d6373b49-1957-4f00-9218-ee2120d3ecd9", "weekStart": "" -} \ No newline at end of file +} diff --git a/apps/iam/go.mod b/apps/iam/go.mod index 154a6bb0632..b98eea39b77 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -165,7 +165,7 @@ require ( github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad // indirect github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // indirect github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect - github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // indirect + github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect diff --git a/apps/iam/go.sum b/apps/iam/go.sum index ad77e3a1a30..8a7ec4477df 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -499,8 +499,8 @@ github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTE github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= -github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 h1:LGTt2LtYX8vaai32d+c9L0sMcP+Dg9w1kO6+lbsxxYg= -github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 h1:ScHTwNbcVC6JH1OSyXzj8S4w67BIpRXwTSjrac3/PSw= +github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33/go.mod h1:8pvvk5OLaLN9LLxghyczUapn/97l+mBgIb10qC1LG84= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index ab1de1b4186..f0ef53718bc 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -3,8 +3,8 @@ module github.com/grafana/grafana/apps/plugins go 1.25.3 require ( - github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37 github.com/grafana/grafana-app-sdk v0.48.1 + github.com/grafana/grafana-app-sdk/logging v0.48.1 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde k8s.io/apimachinery v0.34.1 k8s.io/apiserver v0.34.1 @@ -14,7 +14,6 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/blang/semver/v4 v4.0.0 // indirect github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -37,8 +36,8 @@ require ( github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect + github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect - github.com/grafana/grafana-app-sdk/logging v0.48.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -93,7 +92,6 @@ require ( k8s.io/api v0.34.1 // indirect k8s.io/apiextensions-apiserver v0.34.1 // indirect k8s.io/client-go v0.34.1 // indirect - k8s.io/component-base v0.34.1 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index 07264ead5aa..fb208331835 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -1,7 +1,5 @@ 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous= github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -242,8 +240,6 @@ k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= -k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= diff --git a/apps/plugins/kinds/manifest.cue b/apps/plugins/kinds/manifest.cue index f16ba2d1a72..b3f5251432b 100644 --- a/apps/plugins/kinds/manifest.cue +++ b/apps/plugins/kinds/manifest.cue @@ -5,14 +5,13 @@ manifest: { groupOverride: "plugins.grafana.app" versions: { "v0alpha1": { - served: false + served: true codegen: { ts: {enabled: false} go: {enabled: true} } kinds: [ - pluginMetaV0Alpha1, - pluginInstallV0Alpha1, + pluginV0Alpha1, ] } } diff --git a/apps/plugins/kinds/pluginmeta.cue b/apps/plugins/kinds/plugin.cue similarity index 92% rename from apps/plugins/kinds/pluginmeta.cue rename to apps/plugins/kinds/plugin.cue index 93f41095707..4f381082969 100644 --- a/apps/plugins/kinds/pluginmeta.cue +++ b/apps/plugins/kinds/plugin.cue @@ -4,17 +4,33 @@ import ( "time" ) -pluginMetaV0Alpha1: { - kind: "PluginMeta" - plural: "pluginsmeta" +pluginV0Alpha1: { + kind: "Plugin" + plural: "plugins" scope: "Namespaced" schema: { spec: { - pluginJSON: #JSONData, + id: string + version: string + url?: string + class: "core" | "external" | "cdn" + } + } + routes: { + "/meta": { + "GET": { + request: {} + response: #JSONData, + responseMetadata: { + typeMeta: false + objectMeta: false + } + } } } } + // JSON configuration schema for Grafana plugins // Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json #JSONData: { @@ -217,4 +233,4 @@ pluginMetaV0Alpha1: { title?: string description?: string }] -} +} \ No newline at end of file diff --git a/apps/plugins/kinds/plugininstall.cue b/apps/plugins/kinds/plugininstall.cue deleted file mode 100644 index d72cfac7342..00000000000 --- a/apps/plugins/kinds/plugininstall.cue +++ /dev/null @@ -1,15 +0,0 @@ -package plugins - -pluginInstallV0Alpha1: { - kind: "PluginInstall" - plural: "plugininstalls" - scope: "Namespaced" - schema: { - spec: { - id: string - version: string - url?: string - class: "core" | "external" | "cdn" - } - } -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go new file mode 100644 index 00000000000..3e9bcb3a2bb --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_client_gen.go @@ -0,0 +1,123 @@ +package v0alpha1 + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type PluginClient struct { + client *resource.TypedClient[*Plugin, *PluginList] +} + +func NewPluginClient(client resource.Client) *PluginClient { + return &PluginClient{ + client: resource.NewTypedClient[*Plugin, *PluginList](client, PluginKind()), + } +} + +func NewPluginClientFromGenerator(generator resource.ClientGenerator) (*PluginClient, error) { + c, err := generator.ClientFor(PluginKind()) + if err != nil { + return nil, err + } + return NewPluginClient(c), nil +} + +func (c *PluginClient) Get(ctx context.Context, identifier resource.Identifier) (*Plugin, error) { + return c.client.Get(ctx, identifier) +} + +func (c *PluginClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginList, error) { + return c.client.List(ctx, namespace, opts) +} + +func (c *PluginClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginList, error) { + resp, err := c.client.List(ctx, namespace, resource.ListOptions{ + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + for resp.GetContinue() != "" { + page, err := c.client.List(ctx, namespace, resource.ListOptions{ + Continue: resp.GetContinue(), + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + resp.SetContinue(page.GetContinue()) + resp.SetResourceVersion(page.GetResourceVersion()) + resp.SetItems(append(resp.GetItems(), page.GetItems()...)) + } + return resp, nil +} + +func (c *PluginClient) Create(ctx context.Context, obj *Plugin, opts resource.CreateOptions) (*Plugin, error) { + // Make sure apiVersion and kind are set + obj.APIVersion = GroupVersion.Identifier() + obj.Kind = PluginKind().Kind() + return c.client.Create(ctx, obj, opts) +} + +func (c *PluginClient) Update(ctx context.Context, obj *Plugin, opts resource.UpdateOptions) (*Plugin, error) { + return c.client.Update(ctx, obj, opts) +} + +func (c *PluginClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Plugin, error) { + return c.client.Patch(ctx, identifier, req, opts) +} + +func (c *PluginClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus PluginStatus, opts resource.UpdateOptions) (*Plugin, error) { + return c.client.Update(ctx, &Plugin{ + TypeMeta: metav1.TypeMeta{ + Kind: PluginKind().Kind(), + APIVersion: GroupVersion.Identifier(), + }, + ObjectMeta: metav1.ObjectMeta{ + ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, + }, + Status: newStatus, + }, resource.UpdateOptions{ + Subresource: "status", + ResourceVersion: opts.ResourceVersion, + }) +} + +func (c *PluginClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + return c.client.Delete(ctx, identifier, opts) +} + +type GetMetaRequest struct { + Headers http.Header +} + +func (c *PluginClient) GetMeta(ctx context.Context, identifier resource.Identifier, request GetMetaRequest) (*GetMeta, error) { + resp, err := c.client.SubresourceRequest(ctx, identifier, resource.CustomRouteRequestOptions{ + Path: "/meta", + Verb: "GET", + Headers: request.Headers, + }) + if err != nil { + return nil, err + } + cast := GetMeta{} + err = json.Unmarshal(resp, &cast) + if err != nil { + return nil, fmt.Errorf("unable to unmarshal response bytes into GetMeta: %w", err) + } + return &cast, nil +} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_codec_gen.go similarity index 56% rename from apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go rename to apps/plugins/pkg/apis/plugins/v0alpha1/plugin_codec_gen.go index 77fb6f918a2..c8fe78b00f1 100644 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_codec_gen.go +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_codec_gen.go @@ -11,18 +11,18 @@ import ( "github.com/grafana/grafana-app-sdk/resource" ) -// PluginMetaJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding -type PluginMetaJSONCodec struct{} +// PluginJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding +type PluginJSONCodec struct{} // Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` -func (*PluginMetaJSONCodec) Read(reader io.Reader, into resource.Object) error { +func (*PluginJSONCodec) Read(reader io.Reader, into resource.Object) error { return json.NewDecoder(reader).Decode(into) } // Write writes JSON-encoded bytes into `writer` marshaled from `from` -func (*PluginMetaJSONCodec) Write(writer io.Writer, from resource.Object) error { +func (*PluginJSONCodec) Write(writer io.Writer, from resource.Object) error { return json.NewEncoder(writer).Encode(from) } // Interface compliance checks -var _ resource.Codec = &PluginMetaJSONCodec{} +var _ resource.Codec = &PluginJSONCodec{} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go new file mode 100644 index 00000000000..6b0e6899a5b --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_getmeta_response_types_gen.go @@ -0,0 +1,463 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +import ( + time "time" +) + +// +k8s:openapi-gen=true +type Info struct { + // Required fields + // +listType=set + Keywords []string `json:"keywords"` + Logos V0alpha1InfoLogos `json:"logos"` + Updated time.Time `json:"updated"` + Version string `json:"version"` + // Optional fields + Author *V0alpha1InfoAuthor `json:"author,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Links []V0alpha1InfoLinks `json:"links,omitempty"` + // +listType=atomic + Screenshots []V0alpha1InfoScreenshots `json:"screenshots,omitempty"` +} + +// NewInfo creates a new Info object. +func NewInfo() *Info { + return &Info{ + Keywords: []string{}, + Logos: *NewV0alpha1InfoLogos(), + } +} + +// +k8s:openapi-gen=true +type Dependencies struct { + // Required field + GrafanaDependency string `json:"grafanaDependency"` + // Optional fields + GrafanaVersion *string `json:"grafanaVersion,omitempty"` + // +listType=set + // +listMapKey=id + Plugins []V0alpha1DependenciesPlugins `json:"plugins,omitempty"` + Extensions *V0alpha1DependenciesExtensions `json:"extensions,omitempty"` +} + +// NewDependencies creates a new Dependencies object. +func NewDependencies() *Dependencies { + return &Dependencies{} +} + +// +k8s:openapi-gen=true +type EnterpriseFeatures struct { + // Allow additional properties + HealthDiagnosticsErrors *bool `json:"healthDiagnosticsErrors,omitempty"` +} + +// NewEnterpriseFeatures creates a new EnterpriseFeatures object. +func NewEnterpriseFeatures() *EnterpriseFeatures { + return &EnterpriseFeatures{ + HealthDiagnosticsErrors: (func(input bool) *bool { return &input })(false), + } +} + +// +k8s:openapi-gen=true +type Include struct { + Uid *string `json:"uid,omitempty"` + Type *IncludeType `json:"type,omitempty"` + Name *string `json:"name,omitempty"` + Component *string `json:"component,omitempty"` + Role *IncludeRole `json:"role,omitempty"` + Action *string `json:"action,omitempty"` + Path *string `json:"path,omitempty"` + AddToNav *bool `json:"addToNav,omitempty"` + DefaultNav *bool `json:"defaultNav,omitempty"` + Icon *string `json:"icon,omitempty"` +} + +// NewInclude creates a new Include object. +func NewInclude() *Include { + return &Include{} +} + +// +k8s:openapi-gen=true +type QueryOptions struct { + MaxDataPoints *bool `json:"maxDataPoints,omitempty"` + MinInterval *bool `json:"minInterval,omitempty"` + CacheTimeout *bool `json:"cacheTimeout,omitempty"` +} + +// NewQueryOptions creates a new QueryOptions object. +func NewQueryOptions() *QueryOptions { + return &QueryOptions{} +} + +// +k8s:openapi-gen=true +type Route struct { + Path *string `json:"path,omitempty"` + Method *string `json:"method,omitempty"` + Url *string `json:"url,omitempty"` + ReqSignedIn *bool `json:"reqSignedIn,omitempty"` + ReqRole *string `json:"reqRole,omitempty"` + ReqAction *string `json:"reqAction,omitempty"` + // +listType=atomic + Headers []string `json:"headers,omitempty"` + Body map[string]interface{} `json:"body,omitempty"` + TokenAuth *V0alpha1RouteTokenAuth `json:"tokenAuth,omitempty"` + JwtTokenAuth *V0alpha1RouteJwtTokenAuth `json:"jwtTokenAuth,omitempty"` + // +listType=atomic + UrlParams []V0alpha1RouteUrlParams `json:"urlParams,omitempty"` +} + +// NewRoute creates a new Route object. +func NewRoute() *Route { + return &Route{} +} + +// +k8s:openapi-gen=true +type IAM struct { + // +listType=atomic + Permissions []V0alpha1IAMPermissions `json:"permissions,omitempty"` +} + +// NewIAM creates a new IAM object. +func NewIAM() *IAM { + return &IAM{} +} + +// +k8s:openapi-gen=true +type Role struct { + Role *V0alpha1RoleRole `json:"role,omitempty"` + // +listType=set + Grants []string `json:"grants,omitempty"` +} + +// NewRole creates a new Role object. +func NewRole() *Role { + return &Role{} +} + +// +k8s:openapi-gen=true +type Extensions struct { + // +listType=atomic + AddedComponents []V0alpha1ExtensionsAddedComponents `json:"addedComponents,omitempty"` + // +listType=atomic + AddedLinks []V0alpha1ExtensionsAddedLinks `json:"addedLinks,omitempty"` + // +listType=set + // +listMapKey=id + ExposedComponents []V0alpha1ExtensionsExposedComponents `json:"exposedComponents,omitempty"` + // +listType=set + // +listMapKey=id + ExtensionPoints []V0alpha1ExtensionsExtensionPoints `json:"extensionPoints,omitempty"` +} + +// NewExtensions creates a new Extensions object. +func NewExtensions() *Extensions { + return &Extensions{} +} + +// +k8s:openapi-gen=true +type GetMeta struct { + // Unique name of the plugin + Id string `json:"id"` + // Plugin type + Type GetMetaType `json:"type"` + // Human-readable name of the plugin + Name string `json:"name"` + // Metadata for the plugin + Info Info `json:"info"` + // Dependency information + Dependencies Dependencies `json:"dependencies"` + // Optional fields + Alerting *bool `json:"alerting,omitempty"` + Annotations *bool `json:"annotations,omitempty"` + AutoEnabled *bool `json:"autoEnabled,omitempty"` + Backend *bool `json:"backend,omitempty"` + BuildMode *string `json:"buildMode,omitempty"` + BuiltIn *bool `json:"builtIn,omitempty"` + Category *GetMetaCategory `json:"category,omitempty"` + EnterpriseFeatures *EnterpriseFeatures `json:"enterpriseFeatures,omitempty"` + Executable *string `json:"executable,omitempty"` + HideFromList *bool `json:"hideFromList,omitempty"` + // +listType=atomic + Includes []Include `json:"includes,omitempty"` + Logs *bool `json:"logs,omitempty"` + Metrics *bool `json:"metrics,omitempty"` + MultiValueFilterOperators *bool `json:"multiValueFilterOperators,omitempty"` + PascalName *string `json:"pascalName,omitempty"` + Preload *bool `json:"preload,omitempty"` + QueryOptions *QueryOptions `json:"queryOptions,omitempty"` + // +listType=atomic + Routes []Route `json:"routes,omitempty"` + SkipDataQuery *bool `json:"skipDataQuery,omitempty"` + State *GetMetaState `json:"state,omitempty"` + Streaming *bool `json:"streaming,omitempty"` + Tracing *bool `json:"tracing,omitempty"` + Iam *IAM `json:"iam,omitempty"` + // +listType=atomic + Roles []Role `json:"roles,omitempty"` + Extensions *Extensions `json:"extensions,omitempty"` +} + +// NewGetMeta creates a new GetMeta object. +func NewGetMeta() *GetMeta { + return &GetMeta{ + Info: *NewInfo(), + Dependencies: *NewDependencies(), + } +} + +// +k8s:openapi-gen=true +type V0alpha1InfoLogos struct { + Small string `json:"small"` + Large string `json:"large"` +} + +// NewV0alpha1InfoLogos creates a new V0alpha1InfoLogos object. +func NewV0alpha1InfoLogos() *V0alpha1InfoLogos { + return &V0alpha1InfoLogos{} +} + +// +k8s:openapi-gen=true +type V0alpha1InfoAuthor struct { + Name *string `json:"name,omitempty"` + Email *string `json:"email,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewV0alpha1InfoAuthor creates a new V0alpha1InfoAuthor object. +func NewV0alpha1InfoAuthor() *V0alpha1InfoAuthor { + return &V0alpha1InfoAuthor{} +} + +// +k8s:openapi-gen=true +type V0alpha1InfoLinks struct { + Name *string `json:"name,omitempty"` + Url *string `json:"url,omitempty"` +} + +// NewV0alpha1InfoLinks creates a new V0alpha1InfoLinks object. +func NewV0alpha1InfoLinks() *V0alpha1InfoLinks { + return &V0alpha1InfoLinks{} +} + +// +k8s:openapi-gen=true +type V0alpha1InfoScreenshots struct { + Name *string `json:"name,omitempty"` + Path *string `json:"path,omitempty"` +} + +// NewV0alpha1InfoScreenshots creates a new V0alpha1InfoScreenshots object. +func NewV0alpha1InfoScreenshots() *V0alpha1InfoScreenshots { + return &V0alpha1InfoScreenshots{} +} + +// +k8s:openapi-gen=true +type V0alpha1DependenciesPlugins struct { + Id string `json:"id"` + Type V0alpha1DependenciesPluginsType `json:"type"` + Name string `json:"name"` +} + +// NewV0alpha1DependenciesPlugins creates a new V0alpha1DependenciesPlugins object. +func NewV0alpha1DependenciesPlugins() *V0alpha1DependenciesPlugins { + return &V0alpha1DependenciesPlugins{} +} + +// +k8s:openapi-gen=true +type V0alpha1DependenciesExtensions struct { + // +listType=set + ExposedComponents []string `json:"exposedComponents,omitempty"` +} + +// NewV0alpha1DependenciesExtensions creates a new V0alpha1DependenciesExtensions object. +func NewV0alpha1DependenciesExtensions() *V0alpha1DependenciesExtensions { + return &V0alpha1DependenciesExtensions{} +} + +// +k8s:openapi-gen=true +type V0alpha1RouteTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewV0alpha1RouteTokenAuth creates a new V0alpha1RouteTokenAuth object. +func NewV0alpha1RouteTokenAuth() *V0alpha1RouteTokenAuth { + return &V0alpha1RouteTokenAuth{} +} + +// +k8s:openapi-gen=true +type V0alpha1RouteJwtTokenAuth struct { + Url *string `json:"url,omitempty"` + // +listType=set + Scopes []string `json:"scopes,omitempty"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// NewV0alpha1RouteJwtTokenAuth creates a new V0alpha1RouteJwtTokenAuth object. +func NewV0alpha1RouteJwtTokenAuth() *V0alpha1RouteJwtTokenAuth { + return &V0alpha1RouteJwtTokenAuth{} +} + +// +k8s:openapi-gen=true +type V0alpha1RouteUrlParams struct { + Name *string `json:"name,omitempty"` + Content *string `json:"content,omitempty"` +} + +// NewV0alpha1RouteUrlParams creates a new V0alpha1RouteUrlParams object. +func NewV0alpha1RouteUrlParams() *V0alpha1RouteUrlParams { + return &V0alpha1RouteUrlParams{} +} + +// +k8s:openapi-gen=true +type V0alpha1IAMPermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewV0alpha1IAMPermissions creates a new V0alpha1IAMPermissions object. +func NewV0alpha1IAMPermissions() *V0alpha1IAMPermissions { + return &V0alpha1IAMPermissions{} +} + +// +k8s:openapi-gen=true +type V0alpha1RoleRolePermissions struct { + Action *string `json:"action,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// NewV0alpha1RoleRolePermissions creates a new V0alpha1RoleRolePermissions object. +func NewV0alpha1RoleRolePermissions() *V0alpha1RoleRolePermissions { + return &V0alpha1RoleRolePermissions{} +} + +// +k8s:openapi-gen=true +type V0alpha1RoleRole struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + // +listType=atomic + Permissions []V0alpha1RoleRolePermissions `json:"permissions,omitempty"` +} + +// NewV0alpha1RoleRole creates a new V0alpha1RoleRole object. +func NewV0alpha1RoleRole() *V0alpha1RoleRole { + return &V0alpha1RoleRole{} +} + +// +k8s:openapi-gen=true +type V0alpha1ExtensionsAddedComponents struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewV0alpha1ExtensionsAddedComponents creates a new V0alpha1ExtensionsAddedComponents object. +func NewV0alpha1ExtensionsAddedComponents() *V0alpha1ExtensionsAddedComponents { + return &V0alpha1ExtensionsAddedComponents{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type V0alpha1ExtensionsAddedLinks struct { + // +listType=set + Targets []string `json:"targets"` + Title string `json:"title"` + Description *string `json:"description,omitempty"` +} + +// NewV0alpha1ExtensionsAddedLinks creates a new V0alpha1ExtensionsAddedLinks object. +func NewV0alpha1ExtensionsAddedLinks() *V0alpha1ExtensionsAddedLinks { + return &V0alpha1ExtensionsAddedLinks{ + Targets: []string{}, + } +} + +// +k8s:openapi-gen=true +type V0alpha1ExtensionsExposedComponents struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewV0alpha1ExtensionsExposedComponents creates a new V0alpha1ExtensionsExposedComponents object. +func NewV0alpha1ExtensionsExposedComponents() *V0alpha1ExtensionsExposedComponents { + return &V0alpha1ExtensionsExposedComponents{} +} + +// +k8s:openapi-gen=true +type V0alpha1ExtensionsExtensionPoints struct { + Id string `json:"id"` + Title *string `json:"title,omitempty"` + Description *string `json:"description,omitempty"` +} + +// NewV0alpha1ExtensionsExtensionPoints creates a new V0alpha1ExtensionsExtensionPoints object. +func NewV0alpha1ExtensionsExtensionPoints() *V0alpha1ExtensionsExtensionPoints { + return &V0alpha1ExtensionsExtensionPoints{} +} + +// +k8s:openapi-gen=true +type IncludeType string + +const ( + IncludeTypeDashboard IncludeType = "dashboard" + IncludeTypePage IncludeType = "page" + IncludeTypePanel IncludeType = "panel" + IncludeTypeDatasource IncludeType = "datasource" +) + +// +k8s:openapi-gen=true +type IncludeRole string + +const ( + IncludeRoleAdmin IncludeRole = "Admin" + IncludeRoleEditor IncludeRole = "Editor" + IncludeRoleViewer IncludeRole = "Viewer" +) + +// +k8s:openapi-gen=true +type GetMetaType string + +const ( + GetMetaTypeApp GetMetaType = "app" + GetMetaTypeDatasource GetMetaType = "datasource" + GetMetaTypePanel GetMetaType = "panel" + GetMetaTypeRenderer GetMetaType = "renderer" +) + +// +k8s:openapi-gen=true +type GetMetaCategory string + +const ( + GetMetaCategoryTsdb GetMetaCategory = "tsdb" + GetMetaCategoryLogging GetMetaCategory = "logging" + GetMetaCategoryCloud GetMetaCategory = "cloud" + GetMetaCategoryTracing GetMetaCategory = "tracing" + GetMetaCategoryProfiling GetMetaCategory = "profiling" + GetMetaCategorySql GetMetaCategory = "sql" + GetMetaCategoryEnterprise GetMetaCategory = "enterprise" + GetMetaCategoryIot GetMetaCategory = "iot" + GetMetaCategoryOther GetMetaCategory = "other" +) + +// +k8s:openapi-gen=true +type GetMetaState string + +const ( + GetMetaStateAlpha GetMetaState = "alpha" + GetMetaStateBeta GetMetaState = "beta" +) + +// +k8s:openapi-gen=true +type V0alpha1DependenciesPluginsType string + +const ( + V0alpha1DependenciesPluginsTypeApp V0alpha1DependenciesPluginsType = "app" + V0alpha1DependenciesPluginsTypeDatasource V0alpha1DependenciesPluginsType = "datasource" + V0alpha1DependenciesPluginsTypePanel V0alpha1DependenciesPluginsType = "panel" +) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_metadata_gen.go similarity index 85% rename from apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go rename to apps/plugins/pkg/apis/plugins/v0alpha1/plugin_metadata_gen.go index 7d3b3c9c6b8..fe30fe5e614 100644 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_metadata_gen.go +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_metadata_gen.go @@ -9,7 +9,7 @@ import ( // metadata contains embedded CommonMetadata and can be extended with custom string fields // TODO: use CommonMetadata instead of redefining here; currently needs to be defined here // without external reference as using the CommonMetadata reference breaks thema codegen. -type PluginMetaMetadata struct { +type PluginMetadata struct { UpdateTimestamp time.Time `json:"updateTimestamp"` CreatedBy string `json:"createdBy"` Uid string `json:"uid"` @@ -22,9 +22,9 @@ type PluginMetaMetadata struct { Labels map[string]string `json:"labels"` } -// NewPluginMetaMetadata creates a new PluginMetaMetadata object. -func NewPluginMetaMetadata() *PluginMetaMetadata { - return &PluginMetaMetadata{ +// NewPluginMetadata creates a new PluginMetadata object. +func NewPluginMetadata() *PluginMetadata { + return &PluginMetadata{ Finalizers: []string{}, Labels: map[string]string{}, } diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_object_gen.go similarity index 69% rename from apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go rename to apps/plugins/pkg/apis/plugins/v0alpha1/plugin_object_gen.go index dac431ebf12..d2cdedba399 100644 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_object_gen.go +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_object_gen.go @@ -15,22 +15,22 @@ import ( ) // +k8s:openapi-gen=true -type PluginMeta struct { +type Plugin struct { metav1.TypeMeta `json:",inline" yaml:",inline"` metav1.ObjectMeta `json:"metadata" yaml:"metadata"` - // Spec is the spec of the PluginMeta - Spec PluginMetaSpec `json:"spec" yaml:"spec"` + // Spec is the spec of the Plugin + Spec PluginSpec `json:"spec" yaml:"spec"` - Status PluginMetaStatus `json:"status" yaml:"status"` + Status PluginStatus `json:"status" yaml:"status"` } -func (o *PluginMeta) GetSpec() any { +func (o *Plugin) GetSpec() any { return o.Spec } -func (o *PluginMeta) SetSpec(spec any) error { - cast, ok := spec.(PluginMetaSpec) +func (o *Plugin) SetSpec(spec any) error { + cast, ok := spec.(PluginSpec) if !ok { return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) } @@ -38,13 +38,13 @@ func (o *PluginMeta) SetSpec(spec any) error { return nil } -func (o *PluginMeta) GetSubresources() map[string]any { +func (o *Plugin) GetSubresources() map[string]any { return map[string]any{ "status": o.Status, } } -func (o *PluginMeta) GetSubresource(name string) (any, bool) { +func (o *Plugin) GetSubresource(name string) (any, bool) { switch name { case "status": return o.Status, true @@ -53,12 +53,12 @@ func (o *PluginMeta) GetSubresource(name string) (any, bool) { } } -func (o *PluginMeta) SetSubresource(name string, value any) error { +func (o *Plugin) SetSubresource(name string, value any) error { switch name { case "status": - cast, ok := value.(PluginMetaStatus) + cast, ok := value.(PluginStatus) if !ok { - return fmt.Errorf("cannot set status type %#v, not of type PluginMetaStatus", value) + return fmt.Errorf("cannot set status type %#v, not of type PluginStatus", value) } o.Status = cast return nil @@ -67,7 +67,7 @@ func (o *PluginMeta) SetSubresource(name string, value any) error { } } -func (o *PluginMeta) GetStaticMetadata() resource.StaticMetadata { +func (o *Plugin) GetStaticMetadata() resource.StaticMetadata { gvk := o.GroupVersionKind() return resource.StaticMetadata{ Name: o.ObjectMeta.Name, @@ -78,7 +78,7 @@ func (o *PluginMeta) GetStaticMetadata() resource.StaticMetadata { } } -func (o *PluginMeta) SetStaticMetadata(metadata resource.StaticMetadata) { +func (o *Plugin) SetStaticMetadata(metadata resource.StaticMetadata) { o.Name = metadata.Name o.Namespace = metadata.Namespace o.SetGroupVersionKind(schema.GroupVersionKind{ @@ -88,7 +88,7 @@ func (o *PluginMeta) SetStaticMetadata(metadata resource.StaticMetadata) { }) } -func (o *PluginMeta) GetCommonMetadata() resource.CommonMetadata { +func (o *Plugin) GetCommonMetadata() resource.CommonMetadata { dt := o.DeletionTimestamp var deletionTimestamp *time.Time if dt != nil { @@ -120,7 +120,7 @@ func (o *PluginMeta) GetCommonMetadata() resource.CommonMetadata { } } -func (o *PluginMeta) SetCommonMetadata(metadata resource.CommonMetadata) { +func (o *Plugin) SetCommonMetadata(metadata resource.CommonMetadata) { o.UID = types.UID(metadata.UID) o.ResourceVersion = metadata.ResourceVersion o.Generation = metadata.Generation @@ -165,7 +165,7 @@ func (o *PluginMeta) SetCommonMetadata(metadata resource.CommonMetadata) { } } -func (o *PluginMeta) GetCreatedBy() string { +func (o *Plugin) GetCreatedBy() string { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -173,7 +173,7 @@ func (o *PluginMeta) GetCreatedBy() string { return o.ObjectMeta.Annotations["grafana.com/createdBy"] } -func (o *PluginMeta) SetCreatedBy(createdBy string) { +func (o *Plugin) SetCreatedBy(createdBy string) { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -181,7 +181,7 @@ func (o *PluginMeta) SetCreatedBy(createdBy string) { o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy } -func (o *PluginMeta) GetUpdateTimestamp() time.Time { +func (o *Plugin) GetUpdateTimestamp() time.Time { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -190,7 +190,7 @@ func (o *PluginMeta) GetUpdateTimestamp() time.Time { return parsed } -func (o *PluginMeta) SetUpdateTimestamp(updateTimestamp time.Time) { +func (o *Plugin) SetUpdateTimestamp(updateTimestamp time.Time) { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -198,7 +198,7 @@ func (o *PluginMeta) SetUpdateTimestamp(updateTimestamp time.Time) { o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) } -func (o *PluginMeta) GetUpdatedBy() string { +func (o *Plugin) GetUpdatedBy() string { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -206,7 +206,7 @@ func (o *PluginMeta) GetUpdatedBy() string { return o.ObjectMeta.Annotations["grafana.com/updatedBy"] } -func (o *PluginMeta) SetUpdatedBy(updatedBy string) { +func (o *Plugin) SetUpdatedBy(updatedBy string) { if o.ObjectMeta.Annotations == nil { o.ObjectMeta.Annotations = make(map[string]string) } @@ -214,21 +214,21 @@ func (o *PluginMeta) SetUpdatedBy(updatedBy string) { o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy } -func (o *PluginMeta) Copy() resource.Object { +func (o *Plugin) Copy() resource.Object { return resource.CopyObject(o) } -func (o *PluginMeta) DeepCopyObject() runtime.Object { +func (o *Plugin) DeepCopyObject() runtime.Object { return o.Copy() } -func (o *PluginMeta) DeepCopy() *PluginMeta { - cpy := &PluginMeta{} +func (o *Plugin) DeepCopy() *Plugin { + cpy := &Plugin{} o.DeepCopyInto(cpy) return cpy } -func (o *PluginMeta) DeepCopyInto(dst *PluginMeta) { +func (o *Plugin) DeepCopyInto(dst *Plugin) { dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion dst.TypeMeta.Kind = o.TypeMeta.Kind o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) @@ -237,34 +237,34 @@ func (o *PluginMeta) DeepCopyInto(dst *PluginMeta) { } // Interface compliance compile-time check -var _ resource.Object = &PluginMeta{} +var _ resource.Object = &Plugin{} // +k8s:openapi-gen=true -type PluginMetaList struct { +type PluginList struct { metav1.TypeMeta `json:",inline" yaml:",inline"` metav1.ListMeta `json:"metadata" yaml:"metadata"` - Items []PluginMeta `json:"items" yaml:"items"` + Items []Plugin `json:"items" yaml:"items"` } -func (o *PluginMetaList) DeepCopyObject() runtime.Object { +func (o *PluginList) DeepCopyObject() runtime.Object { return o.Copy() } -func (o *PluginMetaList) Copy() resource.ListObject { - cpy := &PluginMetaList{ +func (o *PluginList) Copy() resource.ListObject { + cpy := &PluginList{ TypeMeta: o.TypeMeta, - Items: make([]PluginMeta, len(o.Items)), + Items: make([]Plugin, len(o.Items)), } o.ListMeta.DeepCopyInto(&cpy.ListMeta) for i := 0; i < len(o.Items); i++ { - if item, ok := o.Items[i].Copy().(*PluginMeta); ok { + if item, ok := o.Items[i].Copy().(*Plugin); ok { cpy.Items[i] = *item } } return cpy } -func (o *PluginMetaList) GetItems() []resource.Object { +func (o *PluginList) GetItems() []resource.Object { items := make([]resource.Object, len(o.Items)) for i := 0; i < len(o.Items); i++ { items[i] = &o.Items[i] @@ -272,48 +272,48 @@ func (o *PluginMetaList) GetItems() []resource.Object { return items } -func (o *PluginMetaList) SetItems(items []resource.Object) { - o.Items = make([]PluginMeta, len(items)) +func (o *PluginList) SetItems(items []resource.Object) { + o.Items = make([]Plugin, len(items)) for i := 0; i < len(items); i++ { - o.Items[i] = *items[i].(*PluginMeta) + o.Items[i] = *items[i].(*Plugin) } } -func (o *PluginMetaList) DeepCopy() *PluginMetaList { - cpy := &PluginMetaList{} +func (o *PluginList) DeepCopy() *PluginList { + cpy := &PluginList{} o.DeepCopyInto(cpy) return cpy } -func (o *PluginMetaList) DeepCopyInto(dst *PluginMetaList) { +func (o *PluginList) DeepCopyInto(dst *PluginList) { resource.CopyObjectInto(dst, o) } // Interface compliance compile-time check -var _ resource.ListObject = &PluginMetaList{} +var _ resource.ListObject = &PluginList{} // Copy methods for all subresource types // DeepCopy creates a full deep copy of Spec -func (s *PluginMetaSpec) DeepCopy() *PluginMetaSpec { - cpy := &PluginMetaSpec{} +func (s *PluginSpec) DeepCopy() *PluginSpec { + cpy := &PluginSpec{} s.DeepCopyInto(cpy) return cpy } // DeepCopyInto deep copies Spec into another Spec object -func (s *PluginMetaSpec) DeepCopyInto(dst *PluginMetaSpec) { +func (s *PluginSpec) DeepCopyInto(dst *PluginSpec) { resource.CopyObjectInto(dst, s) } -// DeepCopy creates a full deep copy of PluginMetaStatus -func (s *PluginMetaStatus) DeepCopy() *PluginMetaStatus { - cpy := &PluginMetaStatus{} +// DeepCopy creates a full deep copy of PluginStatus +func (s *PluginStatus) DeepCopy() *PluginStatus { + cpy := &PluginStatus{} s.DeepCopyInto(cpy) return cpy } -// DeepCopyInto deep copies PluginMetaStatus into another PluginMetaStatus object -func (s *PluginMetaStatus) DeepCopyInto(dst *PluginMetaStatus) { +// DeepCopyInto deep copies PluginStatus into another PluginStatus object +func (s *PluginStatus) DeepCopyInto(dst *PluginStatus) { resource.CopyObjectInto(dst, s) } diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_schema_gen.go new file mode 100644 index 00000000000..97024275a1c --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_schema_gen.go @@ -0,0 +1,34 @@ +// +// Code generated by grafana-app-sdk. DO NOT EDIT. +// + +package v0alpha1 + +import ( + "github.com/grafana/grafana-app-sdk/resource" +) + +// schema is unexported to prevent accidental overwrites +var ( + schemaPlugin = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &Plugin{}, &PluginList{}, resource.WithKind("Plugin"), + resource.WithPlural("plugins"), resource.WithScope(resource.NamespacedScope)) + kindPlugin = resource.Kind{ + Schema: schemaPlugin, + Codecs: map[resource.KindEncoding]resource.Codec{ + resource.KindEncodingJSON: &PluginJSONCodec{}, + }, + } +) + +// Kind returns a resource.Kind for this Schema with a JSON codec +func PluginKind() resource.Kind { + return kindPlugin +} + +// Schema returns a resource.SimpleSchema representation of Plugin +func PluginSchema() *resource.SimpleSchema { + return schemaPlugin +} + +// Interface compliance checks +var _ resource.Schema = kindPlugin diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_spec_gen.go new file mode 100644 index 00000000000..d46a0a4aed1 --- /dev/null +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_spec_gen.go @@ -0,0 +1,25 @@ +// Code generated - EDITING IS FUTILE. DO NOT EDIT. + +package v0alpha1 + +// +k8s:openapi-gen=true +type PluginSpec struct { + Id string `json:"id"` + Version string `json:"version"` + Url *string `json:"url,omitempty"` + Class PluginSpecClass `json:"class"` +} + +// NewPluginSpec creates a new PluginSpec object. +func NewPluginSpec() *PluginSpec { + return &PluginSpec{} +} + +// +k8s:openapi-gen=true +type PluginSpecClass string + +const ( + PluginSpecClassCore PluginSpecClass = "core" + PluginSpecClassExternal PluginSpecClass = "external" + PluginSpecClassCdn PluginSpecClass = "cdn" +) diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_status_gen.go similarity index 53% rename from apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_status_gen.go rename to apps/plugins/pkg/apis/plugins/v0alpha1/plugin_status_gen.go index b1b12ddfb33..55ecac4408f 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1/receiver_status_gen.go +++ b/apps/plugins/pkg/apis/plugins/v0alpha1/plugin_status_gen.go @@ -3,42 +3,42 @@ package v0alpha1 // +k8s:openapi-gen=true -type ReceiverstatusOperatorState struct { +type PluginstatusOperatorState struct { // lastEvaluation is the ResourceVersion last evaluated LastEvaluation string `json:"lastEvaluation"` // state describes the state of the lastEvaluation. // It is limited to three possible states for machine evaluation. - State ReceiverStatusOperatorStateState `json:"state"` + State PluginStatusOperatorStateState `json:"state"` // descriptiveState is an optional more descriptive state field which has no requirements on format DescriptiveState *string `json:"descriptiveState,omitempty"` // details contains any extra information that is operator-specific Details map[string]interface{} `json:"details,omitempty"` } -// NewReceiverstatusOperatorState creates a new ReceiverstatusOperatorState object. -func NewReceiverstatusOperatorState() *ReceiverstatusOperatorState { - return &ReceiverstatusOperatorState{} +// NewPluginstatusOperatorState creates a new PluginstatusOperatorState object. +func NewPluginstatusOperatorState() *PluginstatusOperatorState { + return &PluginstatusOperatorState{} } // +k8s:openapi-gen=true -type ReceiverStatus struct { +type PluginStatus struct { // 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 map[string]ReceiverstatusOperatorState `json:"operatorStates,omitempty"` + OperatorStates map[string]PluginstatusOperatorState `json:"operatorStates,omitempty"` // additionalFields is reserved for future use AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` } -// NewReceiverStatus creates a new ReceiverStatus object. -func NewReceiverStatus() *ReceiverStatus { - return &ReceiverStatus{} +// NewPluginStatus creates a new PluginStatus object. +func NewPluginStatus() *PluginStatus { + return &PluginStatus{} } // +k8s:openapi-gen=true -type ReceiverStatusOperatorStateState string +type PluginStatusOperatorStateState string const ( - ReceiverStatusOperatorStateStateSuccess ReceiverStatusOperatorStateState = "success" - ReceiverStatusOperatorStateStateInProgress ReceiverStatusOperatorStateState = "in_progress" - ReceiverStatusOperatorStateStateFailed ReceiverStatusOperatorStateState = "failed" + PluginStatusOperatorStateStateSuccess PluginStatusOperatorStateState = "success" + PluginStatusOperatorStateStateInProgress PluginStatusOperatorStateState = "in_progress" + PluginStatusOperatorStateStateFailed PluginStatusOperatorStateState = "failed" ) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_client_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_client_gen.go deleted file mode 100644 index 7733db871e0..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_client_gen.go +++ /dev/null @@ -1,99 +0,0 @@ -package v0alpha1 - -import ( - "context" - - "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type PluginInstallClient struct { - client *resource.TypedClient[*PluginInstall, *PluginInstallList] -} - -func NewPluginInstallClient(client resource.Client) *PluginInstallClient { - return &PluginInstallClient{ - client: resource.NewTypedClient[*PluginInstall, *PluginInstallList](client, PluginInstallKind()), - } -} - -func NewPluginInstallClientFromGenerator(generator resource.ClientGenerator) (*PluginInstallClient, error) { - c, err := generator.ClientFor(PluginInstallKind()) - if err != nil { - return nil, err - } - return NewPluginInstallClient(c), nil -} - -func (c *PluginInstallClient) Get(ctx context.Context, identifier resource.Identifier) (*PluginInstall, error) { - return c.client.Get(ctx, identifier) -} - -func (c *PluginInstallClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginInstallList, error) { - return c.client.List(ctx, namespace, opts) -} - -func (c *PluginInstallClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginInstallList, error) { - resp, err := c.client.List(ctx, namespace, resource.ListOptions{ - ResourceVersion: opts.ResourceVersion, - Limit: opts.Limit, - LabelFilters: opts.LabelFilters, - FieldSelectors: opts.FieldSelectors, - }) - if err != nil { - return nil, err - } - for resp.GetContinue() != "" { - page, err := c.client.List(ctx, namespace, resource.ListOptions{ - Continue: resp.GetContinue(), - ResourceVersion: opts.ResourceVersion, - Limit: opts.Limit, - LabelFilters: opts.LabelFilters, - FieldSelectors: opts.FieldSelectors, - }) - if err != nil { - return nil, err - } - resp.SetContinue(page.GetContinue()) - resp.SetResourceVersion(page.GetResourceVersion()) - resp.SetItems(append(resp.GetItems(), page.GetItems()...)) - } - return resp, nil -} - -func (c *PluginInstallClient) Create(ctx context.Context, obj *PluginInstall, opts resource.CreateOptions) (*PluginInstall, error) { - // Make sure apiVersion and kind are set - obj.APIVersion = GroupVersion.Identifier() - obj.Kind = PluginInstallKind().Kind() - return c.client.Create(ctx, obj, opts) -} - -func (c *PluginInstallClient) Update(ctx context.Context, obj *PluginInstall, opts resource.UpdateOptions) (*PluginInstall, error) { - return c.client.Update(ctx, obj, opts) -} - -func (c *PluginInstallClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*PluginInstall, error) { - return c.client.Patch(ctx, identifier, req, opts) -} - -func (c *PluginInstallClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus PluginInstallStatus, opts resource.UpdateOptions) (*PluginInstall, error) { - return c.client.Update(ctx, &PluginInstall{ - TypeMeta: metav1.TypeMeta{ - Kind: PluginInstallKind().Kind(), - APIVersion: GroupVersion.Identifier(), - }, - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: opts.ResourceVersion, - Namespace: identifier.Namespace, - Name: identifier.Name, - }, - Status: newStatus, - }, resource.UpdateOptions{ - Subresource: "status", - ResourceVersion: opts.ResourceVersion, - }) -} - -func (c *PluginInstallClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { - return c.client.Delete(ctx, identifier, opts) -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go deleted file mode 100644 index 48b7e98fd2f..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_codec_gen.go +++ /dev/null @@ -1,28 +0,0 @@ -// -// Code generated by grafana-app-sdk. DO NOT EDIT. -// - -package v0alpha1 - -import ( - "encoding/json" - "io" - - "github.com/grafana/grafana-app-sdk/resource" -) - -// PluginInstallJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding -type PluginInstallJSONCodec struct{} - -// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into` -func (*PluginInstallJSONCodec) Read(reader io.Reader, into resource.Object) error { - return json.NewDecoder(reader).Decode(into) -} - -// Write writes JSON-encoded bytes into `writer` marshaled from `from` -func (*PluginInstallJSONCodec) Write(writer io.Writer, from resource.Object) error { - return json.NewEncoder(writer).Encode(from) -} - -// Interface compliance checks -var _ resource.Codec = &PluginInstallJSONCodec{} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go deleted file mode 100644 index 0e944597301..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_metadata_gen.go +++ /dev/null @@ -1,31 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -import ( - time "time" -) - -// metadata contains embedded CommonMetadata and can be extended with custom string fields -// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here -// without external reference as using the CommonMetadata reference breaks thema codegen. -type PluginInstallMetadata struct { - UpdateTimestamp time.Time `json:"updateTimestamp"` - CreatedBy string `json:"createdBy"` - Uid string `json:"uid"` - CreationTimestamp time.Time `json:"creationTimestamp"` - DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` - Finalizers []string `json:"finalizers"` - ResourceVersion string `json:"resourceVersion"` - Generation int64 `json:"generation"` - UpdatedBy string `json:"updatedBy"` - Labels map[string]string `json:"labels"` -} - -// NewPluginInstallMetadata creates a new PluginInstallMetadata object. -func NewPluginInstallMetadata() *PluginInstallMetadata { - return &PluginInstallMetadata{ - Finalizers: []string{}, - Labels: map[string]string{}, - } -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go deleted file mode 100644 index 6ceaa603baa..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_object_gen.go +++ /dev/null @@ -1,319 +0,0 @@ -// -// Code generated by grafana-app-sdk. DO NOT EDIT. -// - -package v0alpha1 - -import ( - "fmt" - "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - "time" -) - -// +k8s:openapi-gen=true -type PluginInstall struct { - metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ObjectMeta `json:"metadata" yaml:"metadata"` - - // Spec is the spec of the PluginInstall - Spec PluginInstallSpec `json:"spec" yaml:"spec"` - - Status PluginInstallStatus `json:"status" yaml:"status"` -} - -func (o *PluginInstall) GetSpec() any { - return o.Spec -} - -func (o *PluginInstall) SetSpec(spec any) error { - cast, ok := spec.(PluginInstallSpec) - if !ok { - return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) - } - o.Spec = cast - return nil -} - -func (o *PluginInstall) GetSubresources() map[string]any { - return map[string]any{ - "status": o.Status, - } -} - -func (o *PluginInstall) GetSubresource(name string) (any, bool) { - switch name { - case "status": - return o.Status, true - default: - return nil, false - } -} - -func (o *PluginInstall) SetSubresource(name string, value any) error { - switch name { - case "status": - cast, ok := value.(PluginInstallStatus) - if !ok { - return fmt.Errorf("cannot set status type %#v, not of type PluginInstallStatus", value) - } - o.Status = cast - return nil - default: - return fmt.Errorf("subresource '%s' does not exist", name) - } -} - -func (o *PluginInstall) GetStaticMetadata() resource.StaticMetadata { - gvk := o.GroupVersionKind() - return resource.StaticMetadata{ - Name: o.ObjectMeta.Name, - Namespace: o.ObjectMeta.Namespace, - Group: gvk.Group, - Version: gvk.Version, - Kind: gvk.Kind, - } -} - -func (o *PluginInstall) SetStaticMetadata(metadata resource.StaticMetadata) { - o.Name = metadata.Name - o.Namespace = metadata.Namespace - o.SetGroupVersionKind(schema.GroupVersionKind{ - Group: metadata.Group, - Version: metadata.Version, - Kind: metadata.Kind, - }) -} - -func (o *PluginInstall) GetCommonMetadata() resource.CommonMetadata { - dt := o.DeletionTimestamp - var deletionTimestamp *time.Time - if dt != nil { - deletionTimestamp = &dt.Time - } - // Legacy ExtraFields support - extraFields := make(map[string]any) - if o.Annotations != nil { - extraFields["annotations"] = o.Annotations - } - if o.ManagedFields != nil { - extraFields["managedFields"] = o.ManagedFields - } - if o.OwnerReferences != nil { - extraFields["ownerReferences"] = o.OwnerReferences - } - return resource.CommonMetadata{ - UID: string(o.UID), - ResourceVersion: o.ResourceVersion, - Generation: o.Generation, - Labels: o.Labels, - CreationTimestamp: o.CreationTimestamp.Time, - DeletionTimestamp: deletionTimestamp, - Finalizers: o.Finalizers, - UpdateTimestamp: o.GetUpdateTimestamp(), - CreatedBy: o.GetCreatedBy(), - UpdatedBy: o.GetUpdatedBy(), - ExtraFields: extraFields, - } -} - -func (o *PluginInstall) SetCommonMetadata(metadata resource.CommonMetadata) { - o.UID = types.UID(metadata.UID) - o.ResourceVersion = metadata.ResourceVersion - o.Generation = metadata.Generation - o.Labels = metadata.Labels - o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp) - if metadata.DeletionTimestamp != nil { - dt := metav1.NewTime(*metadata.DeletionTimestamp) - o.DeletionTimestamp = &dt - } else { - o.DeletionTimestamp = nil - } - o.Finalizers = metadata.Finalizers - if o.Annotations == nil { - o.Annotations = make(map[string]string) - } - if !metadata.UpdateTimestamp.IsZero() { - o.SetUpdateTimestamp(metadata.UpdateTimestamp) - } - if metadata.CreatedBy != "" { - o.SetCreatedBy(metadata.CreatedBy) - } - if metadata.UpdatedBy != "" { - o.SetUpdatedBy(metadata.UpdatedBy) - } - // Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields - if metadata.ExtraFields != nil { - if annotations, ok := metadata.ExtraFields["annotations"]; ok { - if cast, ok := annotations.(map[string]string); ok { - o.Annotations = cast - } - } - if managedFields, ok := metadata.ExtraFields["managedFields"]; ok { - if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok { - o.ManagedFields = cast - } - } - if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok { - if cast, ok := ownerReferences.([]metav1.OwnerReference); ok { - o.OwnerReferences = cast - } - } - } -} - -func (o *PluginInstall) GetCreatedBy() string { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - return o.ObjectMeta.Annotations["grafana.com/createdBy"] -} - -func (o *PluginInstall) SetCreatedBy(createdBy string) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy -} - -func (o *PluginInstall) GetUpdateTimestamp() time.Time { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"]) - return parsed -} - -func (o *PluginInstall) SetUpdateTimestamp(updateTimestamp time.Time) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339) -} - -func (o *PluginInstall) GetUpdatedBy() string { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - return o.ObjectMeta.Annotations["grafana.com/updatedBy"] -} - -func (o *PluginInstall) SetUpdatedBy(updatedBy string) { - if o.ObjectMeta.Annotations == nil { - o.ObjectMeta.Annotations = make(map[string]string) - } - - o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy -} - -func (o *PluginInstall) Copy() resource.Object { - return resource.CopyObject(o) -} - -func (o *PluginInstall) DeepCopyObject() runtime.Object { - return o.Copy() -} - -func (o *PluginInstall) DeepCopy() *PluginInstall { - cpy := &PluginInstall{} - o.DeepCopyInto(cpy) - return cpy -} - -func (o *PluginInstall) DeepCopyInto(dst *PluginInstall) { - dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion - dst.TypeMeta.Kind = o.TypeMeta.Kind - o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta) - o.Spec.DeepCopyInto(&dst.Spec) - o.Status.DeepCopyInto(&dst.Status) -} - -// Interface compliance compile-time check -var _ resource.Object = &PluginInstall{} - -// +k8s:openapi-gen=true -type PluginInstallList struct { - metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ListMeta `json:"metadata" yaml:"metadata"` - Items []PluginInstall `json:"items" yaml:"items"` -} - -func (o *PluginInstallList) DeepCopyObject() runtime.Object { - return o.Copy() -} - -func (o *PluginInstallList) Copy() resource.ListObject { - cpy := &PluginInstallList{ - TypeMeta: o.TypeMeta, - Items: make([]PluginInstall, len(o.Items)), - } - o.ListMeta.DeepCopyInto(&cpy.ListMeta) - for i := 0; i < len(o.Items); i++ { - if item, ok := o.Items[i].Copy().(*PluginInstall); ok { - cpy.Items[i] = *item - } - } - return cpy -} - -func (o *PluginInstallList) GetItems() []resource.Object { - items := make([]resource.Object, len(o.Items)) - for i := 0; i < len(o.Items); i++ { - items[i] = &o.Items[i] - } - return items -} - -func (o *PluginInstallList) SetItems(items []resource.Object) { - o.Items = make([]PluginInstall, len(items)) - for i := 0; i < len(items); i++ { - o.Items[i] = *items[i].(*PluginInstall) - } -} - -func (o *PluginInstallList) DeepCopy() *PluginInstallList { - cpy := &PluginInstallList{} - o.DeepCopyInto(cpy) - return cpy -} - -func (o *PluginInstallList) DeepCopyInto(dst *PluginInstallList) { - resource.CopyObjectInto(dst, o) -} - -// Interface compliance compile-time check -var _ resource.ListObject = &PluginInstallList{} - -// Copy methods for all subresource types - -// DeepCopy creates a full deep copy of Spec -func (s *PluginInstallSpec) DeepCopy() *PluginInstallSpec { - cpy := &PluginInstallSpec{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies Spec into another Spec object -func (s *PluginInstallSpec) DeepCopyInto(dst *PluginInstallSpec) { - resource.CopyObjectInto(dst, s) -} - -// DeepCopy creates a full deep copy of PluginInstallStatus -func (s *PluginInstallStatus) DeepCopy() *PluginInstallStatus { - cpy := &PluginInstallStatus{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies PluginInstallStatus into another PluginInstallStatus object -func (s *PluginInstallStatus) DeepCopyInto(dst *PluginInstallStatus) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go deleted file mode 100644 index 85ea40cd157..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_schema_gen.go +++ /dev/null @@ -1,34 +0,0 @@ -// -// Code generated by grafana-app-sdk. DO NOT EDIT. -// - -package v0alpha1 - -import ( - "github.com/grafana/grafana-app-sdk/resource" -) - -// schema is unexported to prevent accidental overwrites -var ( - schemaPluginInstall = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &PluginInstall{}, &PluginInstallList{}, resource.WithKind("PluginInstall"), - resource.WithPlural("plugininstalls"), resource.WithScope(resource.NamespacedScope)) - kindPluginInstall = resource.Kind{ - Schema: schemaPluginInstall, - Codecs: map[resource.KindEncoding]resource.Codec{ - resource.KindEncodingJSON: &PluginInstallJSONCodec{}, - }, - } -) - -// Kind returns a resource.Kind for this Schema with a JSON codec -func PluginInstallKind() resource.Kind { - return kindPluginInstall -} - -// Schema returns a resource.SimpleSchema representation of PluginInstall -func PluginInstallSchema() *resource.SimpleSchema { - return schemaPluginInstall -} - -// Interface compliance checks -var _ resource.Schema = kindPluginInstall diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go deleted file mode 100644 index eeccd7f656d..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_spec_gen.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// +k8s:openapi-gen=true -type PluginInstallSpec struct { - Id string `json:"id"` - Version string `json:"version"` - Url *string `json:"url,omitempty"` - Class PluginInstallSpecClass `json:"class"` -} - -// NewPluginInstallSpec creates a new PluginInstallSpec object. -func NewPluginInstallSpec() *PluginInstallSpec { - return &PluginInstallSpec{} -} - -// +k8s:openapi-gen=true -type PluginInstallSpecClass string - -const ( - PluginInstallSpecClassCore PluginInstallSpecClass = "core" - PluginInstallSpecClassExternal PluginInstallSpecClass = "external" - PluginInstallSpecClassCdn PluginInstallSpecClass = "cdn" -) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go deleted file mode 100644 index 77fc07860b0..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/plugininstall_status_gen.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// +k8s:openapi-gen=true -type PluginInstallstatusOperatorState struct { - // lastEvaluation is the ResourceVersion last evaluated - LastEvaluation string `json:"lastEvaluation"` - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - State PluginInstallStatusOperatorStateState `json:"state"` - // descriptiveState is an optional more descriptive state field which has no requirements on format - DescriptiveState *string `json:"descriptiveState,omitempty"` - // details contains any extra information that is operator-specific - Details map[string]interface{} `json:"details,omitempty"` -} - -// NewPluginInstallstatusOperatorState creates a new PluginInstallstatusOperatorState object. -func NewPluginInstallstatusOperatorState() *PluginInstallstatusOperatorState { - return &PluginInstallstatusOperatorState{} -} - -// +k8s:openapi-gen=true -type PluginInstallStatus struct { - // 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 map[string]PluginInstallstatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` -} - -// NewPluginInstallStatus creates a new PluginInstallStatus object. -func NewPluginInstallStatus() *PluginInstallStatus { - return &PluginInstallStatus{} -} - -// +k8s:openapi-gen=true -type PluginInstallStatusOperatorStateState string - -const ( - PluginInstallStatusOperatorStateStateSuccess PluginInstallStatusOperatorStateState = "success" - PluginInstallStatusOperatorStateStateInProgress PluginInstallStatusOperatorStateState = "in_progress" - PluginInstallStatusOperatorStateStateFailed PluginInstallStatusOperatorStateState = "failed" -) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go deleted file mode 100644 index e7788e27a33..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_client_gen.go +++ /dev/null @@ -1,99 +0,0 @@ -package v0alpha1 - -import ( - "context" - - "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -type PluginMetaClient struct { - client *resource.TypedClient[*PluginMeta, *PluginMetaList] -} - -func NewPluginMetaClient(client resource.Client) *PluginMetaClient { - return &PluginMetaClient{ - client: resource.NewTypedClient[*PluginMeta, *PluginMetaList](client, PluginMetaKind()), - } -} - -func NewPluginMetaClientFromGenerator(generator resource.ClientGenerator) (*PluginMetaClient, error) { - c, err := generator.ClientFor(PluginMetaKind()) - if err != nil { - return nil, err - } - return NewPluginMetaClient(c), nil -} - -func (c *PluginMetaClient) Get(ctx context.Context, identifier resource.Identifier) (*PluginMeta, error) { - return c.client.Get(ctx, identifier) -} - -func (c *PluginMetaClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginMetaList, error) { - return c.client.List(ctx, namespace, opts) -} - -func (c *PluginMetaClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*PluginMetaList, error) { - resp, err := c.client.List(ctx, namespace, resource.ListOptions{ - ResourceVersion: opts.ResourceVersion, - Limit: opts.Limit, - LabelFilters: opts.LabelFilters, - FieldSelectors: opts.FieldSelectors, - }) - if err != nil { - return nil, err - } - for resp.GetContinue() != "" { - page, err := c.client.List(ctx, namespace, resource.ListOptions{ - Continue: resp.GetContinue(), - ResourceVersion: opts.ResourceVersion, - Limit: opts.Limit, - LabelFilters: opts.LabelFilters, - FieldSelectors: opts.FieldSelectors, - }) - if err != nil { - return nil, err - } - resp.SetContinue(page.GetContinue()) - resp.SetResourceVersion(page.GetResourceVersion()) - resp.SetItems(append(resp.GetItems(), page.GetItems()...)) - } - return resp, nil -} - -func (c *PluginMetaClient) Create(ctx context.Context, obj *PluginMeta, opts resource.CreateOptions) (*PluginMeta, error) { - // Make sure apiVersion and kind are set - obj.APIVersion = GroupVersion.Identifier() - obj.Kind = PluginMetaKind().Kind() - return c.client.Create(ctx, obj, opts) -} - -func (c *PluginMetaClient) Update(ctx context.Context, obj *PluginMeta, opts resource.UpdateOptions) (*PluginMeta, error) { - return c.client.Update(ctx, obj, opts) -} - -func (c *PluginMetaClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*PluginMeta, error) { - return c.client.Patch(ctx, identifier, req, opts) -} - -func (c *PluginMetaClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus PluginMetaStatus, opts resource.UpdateOptions) (*PluginMeta, error) { - return c.client.Update(ctx, &PluginMeta{ - TypeMeta: metav1.TypeMeta{ - Kind: PluginMetaKind().Kind(), - APIVersion: GroupVersion.Identifier(), - }, - ObjectMeta: metav1.ObjectMeta{ - ResourceVersion: opts.ResourceVersion, - Namespace: identifier.Namespace, - Name: identifier.Name, - }, - Status: newStatus, - }, resource.UpdateOptions{ - Subresource: "status", - ResourceVersion: opts.ResourceVersion, - }) -} - -func (c *PluginMetaClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { - return c.client.Delete(ctx, identifier, opts) -} diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go deleted file mode 100644 index a4022c8de97..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_schema_gen.go +++ /dev/null @@ -1,34 +0,0 @@ -// -// Code generated by grafana-app-sdk. DO NOT EDIT. -// - -package v0alpha1 - -import ( - "github.com/grafana/grafana-app-sdk/resource" -) - -// schema is unexported to prevent accidental overwrites -var ( - schemaPluginMeta = resource.NewSimpleSchema("plugins.grafana.app", "v0alpha1", &PluginMeta{}, &PluginMetaList{}, resource.WithKind("PluginMeta"), - resource.WithPlural("pluginmetas"), resource.WithScope(resource.NamespacedScope)) - kindPluginMeta = resource.Kind{ - Schema: schemaPluginMeta, - Codecs: map[resource.KindEncoding]resource.Codec{ - resource.KindEncodingJSON: &PluginMetaJSONCodec{}, - }, - } -) - -// Kind returns a resource.Kind for this Schema with a JSON codec -func PluginMetaKind() resource.Kind { - return kindPluginMeta -} - -// Schema returns a resource.SimpleSchema representation of PluginMeta -func PluginMetaSchema() *resource.SimpleSchema { - return schemaPluginMeta -} - -// Interface compliance checks -var _ resource.Schema = kindPluginMeta diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go deleted file mode 100644 index 8020e34efda..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_spec_gen.go +++ /dev/null @@ -1,477 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -import ( - time "time" -) - -// JSON configuration schema for Grafana plugins -// Converted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json -// +k8s:openapi-gen=true -type PluginMetaJSONData struct { - // Unique name of the plugin - Id string `json:"id"` - // Plugin type - Type PluginMetaJSONDataType `json:"type"` - // Human-readable name of the plugin - Name string `json:"name"` - // Metadata for the plugin - Info PluginMetaInfo `json:"info"` - // Dependency information - Dependencies PluginMetaDependencies `json:"dependencies"` - // Optional fields - Alerting *bool `json:"alerting,omitempty"` - Annotations *bool `json:"annotations,omitempty"` - AutoEnabled *bool `json:"autoEnabled,omitempty"` - Backend *bool `json:"backend,omitempty"` - BuildMode *string `json:"buildMode,omitempty"` - BuiltIn *bool `json:"builtIn,omitempty"` - Category *PluginMetaJSONDataCategory `json:"category,omitempty"` - EnterpriseFeatures *PluginMetaEnterpriseFeatures `json:"enterpriseFeatures,omitempty"` - Executable *string `json:"executable,omitempty"` - HideFromList *bool `json:"hideFromList,omitempty"` - // +listType=atomic - Includes []PluginMetaInclude `json:"includes,omitempty"` - Logs *bool `json:"logs,omitempty"` - Metrics *bool `json:"metrics,omitempty"` - MultiValueFilterOperators *bool `json:"multiValueFilterOperators,omitempty"` - PascalName *string `json:"pascalName,omitempty"` - Preload *bool `json:"preload,omitempty"` - QueryOptions *PluginMetaQueryOptions `json:"queryOptions,omitempty"` - // +listType=atomic - Routes []PluginMetaRoute `json:"routes,omitempty"` - SkipDataQuery *bool `json:"skipDataQuery,omitempty"` - State *PluginMetaJSONDataState `json:"state,omitempty"` - Streaming *bool `json:"streaming,omitempty"` - Tracing *bool `json:"tracing,omitempty"` - Iam *PluginMetaIAM `json:"iam,omitempty"` - // +listType=atomic - Roles []PluginMetaRole `json:"roles,omitempty"` - Extensions *PluginMetaExtensions `json:"extensions,omitempty"` -} - -// NewPluginMetaJSONData creates a new PluginMetaJSONData object. -func NewPluginMetaJSONData() *PluginMetaJSONData { - return &PluginMetaJSONData{ - Info: *NewPluginMetaInfo(), - Dependencies: *NewPluginMetaDependencies(), - } -} - -// +k8s:openapi-gen=true -type PluginMetaInfo struct { - // Required fields - // +listType=set - Keywords []string `json:"keywords"` - Logos PluginMetaV0alpha1InfoLogos `json:"logos"` - Updated time.Time `json:"updated"` - Version string `json:"version"` - // Optional fields - Author *PluginMetaV0alpha1InfoAuthor `json:"author,omitempty"` - Description *string `json:"description,omitempty"` - // +listType=atomic - Links []PluginMetaV0alpha1InfoLinks `json:"links,omitempty"` - // +listType=atomic - Screenshots []PluginMetaV0alpha1InfoScreenshots `json:"screenshots,omitempty"` -} - -// NewPluginMetaInfo creates a new PluginMetaInfo object. -func NewPluginMetaInfo() *PluginMetaInfo { - return &PluginMetaInfo{ - Keywords: []string{}, - Logos: *NewPluginMetaV0alpha1InfoLogos(), - } -} - -// +k8s:openapi-gen=true -type PluginMetaDependencies struct { - // Required field - GrafanaDependency string `json:"grafanaDependency"` - // Optional fields - GrafanaVersion *string `json:"grafanaVersion,omitempty"` - // +listType=set - // +listMapKey=id - Plugins []PluginMetaV0alpha1DependenciesPlugins `json:"plugins,omitempty"` - Extensions *PluginMetaV0alpha1DependenciesExtensions `json:"extensions,omitempty"` -} - -// NewPluginMetaDependencies creates a new PluginMetaDependencies object. -func NewPluginMetaDependencies() *PluginMetaDependencies { - return &PluginMetaDependencies{} -} - -// +k8s:openapi-gen=true -type PluginMetaEnterpriseFeatures struct { - // Allow additional properties - HealthDiagnosticsErrors *bool `json:"healthDiagnosticsErrors,omitempty"` -} - -// NewPluginMetaEnterpriseFeatures creates a new PluginMetaEnterpriseFeatures object. -func NewPluginMetaEnterpriseFeatures() *PluginMetaEnterpriseFeatures { - return &PluginMetaEnterpriseFeatures{ - HealthDiagnosticsErrors: (func(input bool) *bool { return &input })(false), - } -} - -// +k8s:openapi-gen=true -type PluginMetaInclude struct { - Uid *string `json:"uid,omitempty"` - Type *PluginMetaIncludeType `json:"type,omitempty"` - Name *string `json:"name,omitempty"` - Component *string `json:"component,omitempty"` - Role *PluginMetaIncludeRole `json:"role,omitempty"` - Action *string `json:"action,omitempty"` - Path *string `json:"path,omitempty"` - AddToNav *bool `json:"addToNav,omitempty"` - DefaultNav *bool `json:"defaultNav,omitempty"` - Icon *string `json:"icon,omitempty"` -} - -// NewPluginMetaInclude creates a new PluginMetaInclude object. -func NewPluginMetaInclude() *PluginMetaInclude { - return &PluginMetaInclude{} -} - -// +k8s:openapi-gen=true -type PluginMetaQueryOptions struct { - MaxDataPoints *bool `json:"maxDataPoints,omitempty"` - MinInterval *bool `json:"minInterval,omitempty"` - CacheTimeout *bool `json:"cacheTimeout,omitempty"` -} - -// NewPluginMetaQueryOptions creates a new PluginMetaQueryOptions object. -func NewPluginMetaQueryOptions() *PluginMetaQueryOptions { - return &PluginMetaQueryOptions{} -} - -// +k8s:openapi-gen=true -type PluginMetaRoute struct { - Path *string `json:"path,omitempty"` - Method *string `json:"method,omitempty"` - Url *string `json:"url,omitempty"` - ReqSignedIn *bool `json:"reqSignedIn,omitempty"` - ReqRole *string `json:"reqRole,omitempty"` - ReqAction *string `json:"reqAction,omitempty"` - // +listType=atomic - Headers []string `json:"headers,omitempty"` - Body map[string]interface{} `json:"body,omitempty"` - TokenAuth *PluginMetaV0alpha1RouteTokenAuth `json:"tokenAuth,omitempty"` - JwtTokenAuth *PluginMetaV0alpha1RouteJwtTokenAuth `json:"jwtTokenAuth,omitempty"` - // +listType=atomic - UrlParams []PluginMetaV0alpha1RouteUrlParams `json:"urlParams,omitempty"` -} - -// NewPluginMetaRoute creates a new PluginMetaRoute object. -func NewPluginMetaRoute() *PluginMetaRoute { - return &PluginMetaRoute{} -} - -// +k8s:openapi-gen=true -type PluginMetaIAM struct { - // +listType=atomic - Permissions []PluginMetaV0alpha1IAMPermissions `json:"permissions,omitempty"` -} - -// NewPluginMetaIAM creates a new PluginMetaIAM object. -func NewPluginMetaIAM() *PluginMetaIAM { - return &PluginMetaIAM{} -} - -// +k8s:openapi-gen=true -type PluginMetaRole struct { - Role *PluginMetaV0alpha1RoleRole `json:"role,omitempty"` - // +listType=set - Grants []string `json:"grants,omitempty"` -} - -// NewPluginMetaRole creates a new PluginMetaRole object. -func NewPluginMetaRole() *PluginMetaRole { - return &PluginMetaRole{} -} - -// +k8s:openapi-gen=true -type PluginMetaExtensions struct { - // +listType=atomic - AddedComponents []PluginMetaV0alpha1ExtensionsAddedComponents `json:"addedComponents,omitempty"` - // +listType=atomic - AddedLinks []PluginMetaV0alpha1ExtensionsAddedLinks `json:"addedLinks,omitempty"` - // +listType=set - // +listMapKey=id - ExposedComponents []PluginMetaV0alpha1ExtensionsExposedComponents `json:"exposedComponents,omitempty"` - // +listType=set - // +listMapKey=id - ExtensionPoints []PluginMetaV0alpha1ExtensionsExtensionPoints `json:"extensionPoints,omitempty"` -} - -// NewPluginMetaExtensions creates a new PluginMetaExtensions object. -func NewPluginMetaExtensions() *PluginMetaExtensions { - return &PluginMetaExtensions{} -} - -// +k8s:openapi-gen=true -type PluginMetaSpec struct { - PluginJSON PluginMetaJSONData `json:"pluginJSON"` -} - -// NewPluginMetaSpec creates a new PluginMetaSpec object. -func NewPluginMetaSpec() *PluginMetaSpec { - return &PluginMetaSpec{ - PluginJSON: *NewPluginMetaJSONData(), - } -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1InfoLogos struct { - Small string `json:"small"` - Large string `json:"large"` -} - -// NewPluginMetaV0alpha1InfoLogos creates a new PluginMetaV0alpha1InfoLogos object. -func NewPluginMetaV0alpha1InfoLogos() *PluginMetaV0alpha1InfoLogos { - return &PluginMetaV0alpha1InfoLogos{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1InfoAuthor struct { - Name *string `json:"name,omitempty"` - Email *string `json:"email,omitempty"` - Url *string `json:"url,omitempty"` -} - -// NewPluginMetaV0alpha1InfoAuthor creates a new PluginMetaV0alpha1InfoAuthor object. -func NewPluginMetaV0alpha1InfoAuthor() *PluginMetaV0alpha1InfoAuthor { - return &PluginMetaV0alpha1InfoAuthor{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1InfoLinks struct { - Name *string `json:"name,omitempty"` - Url *string `json:"url,omitempty"` -} - -// NewPluginMetaV0alpha1InfoLinks creates a new PluginMetaV0alpha1InfoLinks object. -func NewPluginMetaV0alpha1InfoLinks() *PluginMetaV0alpha1InfoLinks { - return &PluginMetaV0alpha1InfoLinks{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1InfoScreenshots struct { - Name *string `json:"name,omitempty"` - Path *string `json:"path,omitempty"` -} - -// NewPluginMetaV0alpha1InfoScreenshots creates a new PluginMetaV0alpha1InfoScreenshots object. -func NewPluginMetaV0alpha1InfoScreenshots() *PluginMetaV0alpha1InfoScreenshots { - return &PluginMetaV0alpha1InfoScreenshots{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1DependenciesPlugins struct { - Id string `json:"id"` - Type PluginMetaV0alpha1DependenciesPluginsType `json:"type"` - Name string `json:"name"` -} - -// NewPluginMetaV0alpha1DependenciesPlugins creates a new PluginMetaV0alpha1DependenciesPlugins object. -func NewPluginMetaV0alpha1DependenciesPlugins() *PluginMetaV0alpha1DependenciesPlugins { - return &PluginMetaV0alpha1DependenciesPlugins{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1DependenciesExtensions struct { - // +listType=set - ExposedComponents []string `json:"exposedComponents,omitempty"` -} - -// NewPluginMetaV0alpha1DependenciesExtensions creates a new PluginMetaV0alpha1DependenciesExtensions object. -func NewPluginMetaV0alpha1DependenciesExtensions() *PluginMetaV0alpha1DependenciesExtensions { - return &PluginMetaV0alpha1DependenciesExtensions{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RouteTokenAuth struct { - Url *string `json:"url,omitempty"` - // +listType=set - Scopes []string `json:"scopes,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` -} - -// NewPluginMetaV0alpha1RouteTokenAuth creates a new PluginMetaV0alpha1RouteTokenAuth object. -func NewPluginMetaV0alpha1RouteTokenAuth() *PluginMetaV0alpha1RouteTokenAuth { - return &PluginMetaV0alpha1RouteTokenAuth{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RouteJwtTokenAuth struct { - Url *string `json:"url,omitempty"` - // +listType=set - Scopes []string `json:"scopes,omitempty"` - Params map[string]interface{} `json:"params,omitempty"` -} - -// NewPluginMetaV0alpha1RouteJwtTokenAuth creates a new PluginMetaV0alpha1RouteJwtTokenAuth object. -func NewPluginMetaV0alpha1RouteJwtTokenAuth() *PluginMetaV0alpha1RouteJwtTokenAuth { - return &PluginMetaV0alpha1RouteJwtTokenAuth{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RouteUrlParams struct { - Name *string `json:"name,omitempty"` - Content *string `json:"content,omitempty"` -} - -// NewPluginMetaV0alpha1RouteUrlParams creates a new PluginMetaV0alpha1RouteUrlParams object. -func NewPluginMetaV0alpha1RouteUrlParams() *PluginMetaV0alpha1RouteUrlParams { - return &PluginMetaV0alpha1RouteUrlParams{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1IAMPermissions struct { - Action *string `json:"action,omitempty"` - Scope *string `json:"scope,omitempty"` -} - -// NewPluginMetaV0alpha1IAMPermissions creates a new PluginMetaV0alpha1IAMPermissions object. -func NewPluginMetaV0alpha1IAMPermissions() *PluginMetaV0alpha1IAMPermissions { - return &PluginMetaV0alpha1IAMPermissions{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RoleRolePermissions struct { - Action *string `json:"action,omitempty"` - Scope *string `json:"scope,omitempty"` -} - -// NewPluginMetaV0alpha1RoleRolePermissions creates a new PluginMetaV0alpha1RoleRolePermissions object. -func NewPluginMetaV0alpha1RoleRolePermissions() *PluginMetaV0alpha1RoleRolePermissions { - return &PluginMetaV0alpha1RoleRolePermissions{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1RoleRole struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - // +listType=atomic - Permissions []PluginMetaV0alpha1RoleRolePermissions `json:"permissions,omitempty"` -} - -// NewPluginMetaV0alpha1RoleRole creates a new PluginMetaV0alpha1RoleRole object. -func NewPluginMetaV0alpha1RoleRole() *PluginMetaV0alpha1RoleRole { - return &PluginMetaV0alpha1RoleRole{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1ExtensionsAddedComponents struct { - // +listType=set - Targets []string `json:"targets"` - Title string `json:"title"` - Description *string `json:"description,omitempty"` -} - -// NewPluginMetaV0alpha1ExtensionsAddedComponents creates a new PluginMetaV0alpha1ExtensionsAddedComponents object. -func NewPluginMetaV0alpha1ExtensionsAddedComponents() *PluginMetaV0alpha1ExtensionsAddedComponents { - return &PluginMetaV0alpha1ExtensionsAddedComponents{ - Targets: []string{}, - } -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1ExtensionsAddedLinks struct { - // +listType=set - Targets []string `json:"targets"` - Title string `json:"title"` - Description *string `json:"description,omitempty"` -} - -// NewPluginMetaV0alpha1ExtensionsAddedLinks creates a new PluginMetaV0alpha1ExtensionsAddedLinks object. -func NewPluginMetaV0alpha1ExtensionsAddedLinks() *PluginMetaV0alpha1ExtensionsAddedLinks { - return &PluginMetaV0alpha1ExtensionsAddedLinks{ - Targets: []string{}, - } -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1ExtensionsExposedComponents struct { - Id string `json:"id"` - Title *string `json:"title,omitempty"` - Description *string `json:"description,omitempty"` -} - -// NewPluginMetaV0alpha1ExtensionsExposedComponents creates a new PluginMetaV0alpha1ExtensionsExposedComponents object. -func NewPluginMetaV0alpha1ExtensionsExposedComponents() *PluginMetaV0alpha1ExtensionsExposedComponents { - return &PluginMetaV0alpha1ExtensionsExposedComponents{} -} - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1ExtensionsExtensionPoints struct { - Id string `json:"id"` - Title *string `json:"title,omitempty"` - Description *string `json:"description,omitempty"` -} - -// NewPluginMetaV0alpha1ExtensionsExtensionPoints creates a new PluginMetaV0alpha1ExtensionsExtensionPoints object. -func NewPluginMetaV0alpha1ExtensionsExtensionPoints() *PluginMetaV0alpha1ExtensionsExtensionPoints { - return &PluginMetaV0alpha1ExtensionsExtensionPoints{} -} - -// +k8s:openapi-gen=true -type PluginMetaJSONDataType string - -const ( - PluginMetaJSONDataTypeApp PluginMetaJSONDataType = "app" - PluginMetaJSONDataTypeDatasource PluginMetaJSONDataType = "datasource" - PluginMetaJSONDataTypePanel PluginMetaJSONDataType = "panel" - PluginMetaJSONDataTypeRenderer PluginMetaJSONDataType = "renderer" -) - -// +k8s:openapi-gen=true -type PluginMetaJSONDataCategory string - -const ( - PluginMetaJSONDataCategoryTsdb PluginMetaJSONDataCategory = "tsdb" - PluginMetaJSONDataCategoryLogging PluginMetaJSONDataCategory = "logging" - PluginMetaJSONDataCategoryCloud PluginMetaJSONDataCategory = "cloud" - PluginMetaJSONDataCategoryTracing PluginMetaJSONDataCategory = "tracing" - PluginMetaJSONDataCategoryProfiling PluginMetaJSONDataCategory = "profiling" - PluginMetaJSONDataCategorySql PluginMetaJSONDataCategory = "sql" - PluginMetaJSONDataCategoryEnterprise PluginMetaJSONDataCategory = "enterprise" - PluginMetaJSONDataCategoryIot PluginMetaJSONDataCategory = "iot" - PluginMetaJSONDataCategoryOther PluginMetaJSONDataCategory = "other" -) - -// +k8s:openapi-gen=true -type PluginMetaJSONDataState string - -const ( - PluginMetaJSONDataStateAlpha PluginMetaJSONDataState = "alpha" - PluginMetaJSONDataStateBeta PluginMetaJSONDataState = "beta" -) - -// +k8s:openapi-gen=true -type PluginMetaIncludeType string - -const ( - PluginMetaIncludeTypeDashboard PluginMetaIncludeType = "dashboard" - PluginMetaIncludeTypePage PluginMetaIncludeType = "page" - PluginMetaIncludeTypePanel PluginMetaIncludeType = "panel" - PluginMetaIncludeTypeDatasource PluginMetaIncludeType = "datasource" -) - -// +k8s:openapi-gen=true -type PluginMetaIncludeRole string - -const ( - PluginMetaIncludeRoleAdmin PluginMetaIncludeRole = "Admin" - PluginMetaIncludeRoleEditor PluginMetaIncludeRole = "Editor" - PluginMetaIncludeRoleViewer PluginMetaIncludeRole = "Viewer" -) - -// +k8s:openapi-gen=true -type PluginMetaV0alpha1DependenciesPluginsType string - -const ( - PluginMetaV0alpha1DependenciesPluginsTypeApp PluginMetaV0alpha1DependenciesPluginsType = "app" - PluginMetaV0alpha1DependenciesPluginsTypeDatasource PluginMetaV0alpha1DependenciesPluginsType = "datasource" - PluginMetaV0alpha1DependenciesPluginsTypePanel PluginMetaV0alpha1DependenciesPluginsType = "panel" -) diff --git a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go b/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go deleted file mode 100644 index 60fa37dbb32..00000000000 --- a/apps/plugins/pkg/apis/plugins/v0alpha1/pluginmeta_status_gen.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v0alpha1 - -// +k8s:openapi-gen=true -type PluginMetastatusOperatorState struct { - // lastEvaluation is the ResourceVersion last evaluated - LastEvaluation string `json:"lastEvaluation"` - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - State PluginMetaStatusOperatorStateState `json:"state"` - // descriptiveState is an optional more descriptive state field which has no requirements on format - DescriptiveState *string `json:"descriptiveState,omitempty"` - // details contains any extra information that is operator-specific - Details map[string]interface{} `json:"details,omitempty"` -} - -// NewPluginMetastatusOperatorState creates a new PluginMetastatusOperatorState object. -func NewPluginMetastatusOperatorState() *PluginMetastatusOperatorState { - return &PluginMetastatusOperatorState{} -} - -// +k8s:openapi-gen=true -type PluginMetaStatus struct { - // 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 map[string]PluginMetastatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` -} - -// NewPluginMetaStatus creates a new PluginMetaStatus object. -func NewPluginMetaStatus() *PluginMetaStatus { - return &PluginMetaStatus{} -} - -// +k8s:openapi-gen=true -type PluginMetaStatusOperatorStateState string - -const ( - PluginMetaStatusOperatorStateStateSuccess PluginMetaStatusOperatorStateState = "success" - PluginMetaStatusOperatorStateStateInProgress PluginMetaStatusOperatorStateState = "in_progress" - PluginMetaStatusOperatorStateStateFailed PluginMetaStatusOperatorStateState = "failed" -) diff --git a/apps/plugins/pkg/apis/plugins_manifest.go b/apps/plugins/pkg/apis/plugins_manifest.go index 84a55555c2b..428971aa0b5 100644 --- a/apps/plugins/pkg/apis/plugins_manifest.go +++ b/apps/plugins/pkg/apis/plugins_manifest.go @@ -20,12 +20,9 @@ import ( ) var ( - rawSchemaPluginMetav0alpha1 = []byte(`{"Dependencies":{"additionalProperties":false,"properties":{"extensions":{"additionalProperties":false,"properties":{"exposedComponents":{"description":"+listType=set","items":{"type":"string"},"type":"array"}},"type":"object"},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"enum":["app","datasource","panel"],"type":"string"}},"required":["id","type","name"],"type":"object"},"type":"array"}},"required":["grafanaDependency"],"type":"object"},"EnterpriseFeatures":{"additionalProperties":false,"properties":{"healthDiagnosticsErrors":{"default":false,"description":"Allow additional properties","type":"boolean"}},"type":"object"},"Extensions":{"additionalProperties":false,"properties":{"addedComponents":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"addedLinks":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"title":{"type":"string"}},"required":["targets","title"],"type":"object"},"type":"array"},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","items":{"additionalProperties":false,"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"required":["id"],"type":"object"},"type":"array"}},"type":"object"},"IAM":{"additionalProperties":false,"properties":{"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"Include":{"additionalProperties":false,"properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"enum":["Admin","Editor","Viewer"],"type":"string"},"type":{"enum":["dashboard","page","panel","datasource"],"type":"string"},"uid":{"type":"string"}},"type":"object"},"Info":{"additionalProperties":false,"properties":{"author":{"additionalProperties":false,"description":"Optional fields","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","items":{"type":"string"},"type":"array"},"links":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"},"logos":{"additionalProperties":false,"properties":{"large":{"type":"string"},"small":{"type":"string"}},"required":["small","large"],"type":"object"},"screenshots":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"path":{"type":"string"}},"type":"object"},"type":"array"},"updated":{"format":"date-time","type":"string"},"version":{"type":"string"}},"required":["keywords","logos","updated","version"],"type":"object"},"JSONData":{"additionalProperties":false,"description":"JSON configuration schema for Grafana plugins\nConverted from: https://github.com/grafana/grafana/blob/main/docs/sources/developers/plugins/plugin.schema.json","properties":{"alerting":{"description":"Optional fields","type":"boolean"},"annotations":{"type":"boolean"},"autoEnabled":{"type":"boolean"},"backend":{"type":"boolean"},"buildMode":{"type":"string"},"builtIn":{"type":"boolean"},"category":{"enum":["tsdb","logging","cloud","tracing","profiling","sql","enterprise","iot","other"],"type":"string"},"dependencies":{"$ref":"#/components/schemas/Dependencies","description":"Dependency information"},"enterpriseFeatures":{"$ref":"#/components/schemas/EnterpriseFeatures"},"executable":{"type":"string"},"extensions":{"$ref":"#/components/schemas/Extensions"},"hideFromList":{"type":"boolean"},"iam":{"$ref":"#/components/schemas/IAM"},"id":{"description":"Unique name of the plugin","type":"string"},"includes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Include"},"type":"array"},"info":{"$ref":"#/components/schemas/Info","description":"Metadata for the plugin"},"logs":{"type":"boolean"},"metrics":{"type":"boolean"},"multiValueFilterOperators":{"type":"boolean"},"name":{"description":"Human-readable name of the plugin","type":"string"},"pascalName":{"type":"string"},"preload":{"type":"boolean"},"queryOptions":{"$ref":"#/components/schemas/QueryOptions"},"roles":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Role"},"type":"array"},"routes":{"description":"+listType=atomic","items":{"$ref":"#/components/schemas/Route"},"type":"array"},"skipDataQuery":{"type":"boolean"},"state":{"enum":["alpha","beta"],"type":"string"},"streaming":{"type":"boolean"},"tracing":{"type":"boolean"},"type":{"description":"Plugin type","enum":["app","datasource","panel","renderer"],"type":"string"}},"required":["id","type","name","info","dependencies"],"type":"object"},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"PluginMeta":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"QueryOptions":{"additionalProperties":false,"properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"type":"object"},"Role":{"additionalProperties":false,"properties":{"grants":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"role":{"additionalProperties":false,"properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"action":{"type":"string"},"scope":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"},"Route":{"additionalProperties":false,"properties":{"body":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"headers":{"description":"+listType=atomic","items":{"type":"string"},"type":"array"},"jwtTokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"additionalProperties":false,"properties":{"params":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"scopes":{"description":"+listType=set","items":{"type":"string"},"type":"array"},"url":{"type":"string"}},"type":"object"},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","items":{"additionalProperties":false,"properties":{"content":{"type":"string"},"name":{"type":"string"}},"type":"object"},"type":"array"}},"type":"object"},"spec":{"additionalProperties":false,"properties":{"pluginJSON":{"$ref":"#/components/schemas/JSONData"}},"required":["pluginJSON"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) - versionSchemaPluginMetav0alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaPluginMetav0alpha1, &versionSchemaPluginMetav0alpha1) - rawSchemaPluginInstallv0alpha1 = []byte(`{"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"PluginInstall":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"class":{"enum":["core","external","cdn"],"type":"string"},"id":{"type":"string"},"url":{"type":"string"},"version":{"type":"string"}},"required":["id","version","class"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) - versionSchemaPluginInstallv0alpha1 app.VersionSchema - _ = json.Unmarshal(rawSchemaPluginInstallv0alpha1, &versionSchemaPluginInstallv0alpha1) + rawSchemaPluginv0alpha1 = []byte(`{"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"Plugin":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"getMetaDependencies":{"type":"object","required":["grafanaDependency"],"properties":{"extensions":{"type":"object","properties":{"exposedComponents":{"description":"+listType=set","type":"array","items":{"type":"string"}}},"additionalProperties":false},"grafanaDependency":{"description":"Required field","type":"string"},"grafanaVersion":{"description":"Optional fields","type":"string"},"plugins":{"description":"+listType=set\n+listMapKey=id","type":"array","items":{"type":"object","required":["id","type","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["app","datasource","panel"]}},"additionalProperties":false}}},"additionalProperties":false},"getMetaEnterpriseFeatures":{"type":"object","properties":{"healthDiagnosticsErrors":{"description":"Allow additional properties","type":"boolean","default":false}},"additionalProperties":false},"getMetaExtensions":{"type":"object","properties":{"addedComponents":{"description":"+listType=atomic","type":"array","items":{"type":"object","required":["targets","title"],"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","type":"array","items":{"type":"string"}},"title":{"type":"string"}},"additionalProperties":false}},"addedLinks":{"description":"+listType=atomic","type":"array","items":{"type":"object","required":["targets","title"],"properties":{"description":{"type":"string"},"targets":{"description":"+listType=set","type":"array","items":{"type":"string"}},"title":{"type":"string"}},"additionalProperties":false}},"exposedComponents":{"description":"+listType=set\n+listMapKey=id","type":"array","items":{"type":"object","required":["id"],"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"additionalProperties":false}},"extensionPoints":{"description":"+listType=set\n+listMapKey=id","type":"array","items":{"type":"object","required":["id"],"properties":{"description":{"type":"string"},"id":{"type":"string"},"title":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false},"getMetaIAM":{"type":"object","properties":{"permissions":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"scope":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false},"getMetaInclude":{"type":"object","properties":{"action":{"type":"string"},"addToNav":{"type":"boolean"},"component":{"type":"string"},"defaultNav":{"type":"boolean"},"icon":{"type":"string"},"name":{"type":"string"},"path":{"type":"string"},"role":{"type":"string","enum":["Admin","Editor","Viewer"]},"type":{"type":"string","enum":["dashboard","page","panel","datasource"]},"uid":{"type":"string"}},"additionalProperties":false},"getMetaInfo":{"type":"object","required":["keywords","logos","updated","version"],"properties":{"author":{"description":"Optional fields","type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"}},"additionalProperties":false},"description":{"type":"string"},"keywords":{"description":"Required fields\n+listType=set","type":"array","items":{"type":"string"}},"links":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string"}},"additionalProperties":false}},"logos":{"type":"object","required":["small","large"],"properties":{"large":{"type":"string"},"small":{"type":"string"}},"additionalProperties":false},"screenshots":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"path":{"type":"string"}},"additionalProperties":false}},"updated":{"type":"string","format":"date-time"},"version":{"type":"string"}},"additionalProperties":false},"getMetaQueryOptions":{"type":"object","properties":{"cacheTimeout":{"type":"boolean"},"maxDataPoints":{"type":"boolean"},"minInterval":{"type":"boolean"}},"additionalProperties":false},"getMetaRole":{"type":"object","properties":{"grants":{"description":"+listType=set","type":"array","items":{"type":"string"}},"role":{"type":"object","properties":{"description":{"type":"string"},"name":{"type":"string"},"permissions":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"scope":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false}},"additionalProperties":false},"getMetaRoute":{"type":"object","properties":{"body":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"headers":{"description":"+listType=atomic","type":"array","items":{"type":"string"}},"jwtTokenAuth":{"type":"object","properties":{"params":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"scopes":{"description":"+listType=set","type":"array","items":{"type":"string"}},"url":{"type":"string"}},"additionalProperties":false},"method":{"type":"string"},"path":{"type":"string"},"reqAction":{"type":"string"},"reqRole":{"type":"string"},"reqSignedIn":{"type":"boolean"},"tokenAuth":{"type":"object","properties":{"params":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"scopes":{"description":"+listType=set","type":"array","items":{"type":"string"}},"url":{"type":"string"}},"additionalProperties":false},"url":{"type":"string"},"urlParams":{"description":"+listType=atomic","type":"array","items":{"type":"object","properties":{"content":{"type":"string"},"name":{"type":"string"}},"additionalProperties":false}}},"additionalProperties":false},"spec":{"additionalProperties":false,"properties":{"class":{"enum":["core","external","cdn"],"type":"string"},"id":{"type":"string"},"url":{"type":"string"},"version":{"type":"string"}},"required":["id","version","class"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`) + versionSchemaPluginv0alpha1 app.VersionSchema + _ = json.Unmarshal(rawSchemaPluginv0alpha1, &versionSchemaPluginv0alpha1) ) var appManifestData = app.ManifestData{ @@ -35,22 +32,237 @@ var appManifestData = app.ManifestData{ Versions: []app.ManifestVersion{ { Name: "v0alpha1", - Served: false, + Served: true, Kinds: []app.ManifestVersionKind{ { - Kind: "PluginMeta", - Plural: "PluginMetas", + Kind: "Plugin", + Plural: "Plugins", Scope: "Namespaced", Conversion: false, - Schema: &versionSchemaPluginMetav0alpha1, - }, + Schema: &versionSchemaPluginv0alpha1, + Routes: map[string]spec3.PathProps{ + "/meta": { + Get: &spec3.Operation{ + OperationProps: spec3.OperationProps{ - { - Kind: "PluginInstall", - Plural: "PluginInstalls", - Scope: "Namespaced", - Conversion: false, - Schema: &versionSchemaPluginInstallv0alpha1, + OperationId: "getMeta", + + Responses: &spec3.Responses{ + ResponsesProps: spec3.ResponsesProps{ + Default: &spec3.Response{ + ResponseProps: spec3.ResponseProps{ + Description: "Default OK response", + Content: map[string]*spec3.MediaType{ + "application/json": { + MediaTypeProps: spec3.MediaTypeProps{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "alerting": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Description: "Optional fields", + }, + }, + "annotations": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "autoEnabled": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "backend": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "buildMode": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "builtIn": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "category": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Enum: []interface{}{ + "tsdb", + "logging", + "cloud", + "tracing", + "profiling", + "sql", + "enterprise", + "iot", + "other", + }, + }, + }, + "dependencies": { + SchemaProps: spec.SchemaProps{ + + Description: "Dependency information", + Ref: spec.MustCreateRef("#/components/schemas/getMetaDependencies"), + }, + }, + "enterpriseFeatures": { + SchemaProps: spec.SchemaProps{ + + Ref: spec.MustCreateRef("#/components/schemas/getMetaEnterpriseFeatures"), + }, + }, + "executable": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "extensions": { + SchemaProps: spec.SchemaProps{ + + Ref: spec.MustCreateRef("#/components/schemas/getMetaExtensions"), + }, + }, + "hideFromList": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "iam": { + SchemaProps: spec.SchemaProps{ + + Ref: spec.MustCreateRef("#/components/schemas/getMetaIAM"), + }, + }, + "id": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "Unique name of the plugin", + }, + }, + "includes": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Description: "+listType=atomic", + }, + }, + "info": { + SchemaProps: spec.SchemaProps{ + + Description: "Metadata for the plugin", + Ref: spec.MustCreateRef("#/components/schemas/getMetaInfo"), + }, + }, + "logs": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "metrics": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "multiValueFilterOperators": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "Human-readable name of the plugin", + }, + }, + "pascalName": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + }, + }, + "preload": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "queryOptions": { + SchemaProps: spec.SchemaProps{ + + Ref: spec.MustCreateRef("#/components/schemas/getMetaQueryOptions"), + }, + }, + "roles": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Description: "+listType=atomic", + }, + }, + "routes": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Description: "+listType=atomic", + }, + }, + "skipDataQuery": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Enum: []interface{}{ + "alpha", + "beta", + }, + }, + }, + "streaming": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "tracing": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Description: "Plugin type", + Enum: []interface{}{ + "app", + "datasource", + "panel", + "renderer", + }, + }, + }, + }, + Required: []string{ + "id", + "type", + "name", + "info", + "dependencies", + }, + }}, + }}, + }, + }, + }, + }}, + }, + }, + }, + }, }, }, Routes: app.ManifestVersionRoutes{ @@ -71,8 +283,7 @@ func RemoteManifest() app.Manifest { } var kindVersionToGoType = map[string]resource.Kind{ - "PluginMeta/v0alpha1": v0alpha1.PluginMetaKind(), - "PluginInstall/v0alpha1": v0alpha1.PluginInstallKind(), + "Plugin/v0alpha1": v0alpha1.PluginKind(), } // ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists. @@ -82,7 +293,9 @@ func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exist return goType, exists } -var customRouteToGoResponseType = map[string]any{} +var customRouteToGoResponseType = map[string]any{ + "v0alpha1|Plugin|meta|GET": v0alpha1.GetMeta{}, +} // ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. // kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. diff --git a/apps/plugins/pkg/app/app.go b/apps/plugins/pkg/app/app.go index 5503126704b..903e2580ab0 100644 --- a/apps/plugins/pkg/app/app.go +++ b/apps/plugins/pkg/app/app.go @@ -2,8 +2,11 @@ package app import ( "context" + "net/http" "github.com/grafana/grafana-app-sdk/app" + "github.com/grafana/grafana-app-sdk/k8s" + "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/operator" "github.com/grafana/grafana-app-sdk/resource" "github.com/grafana/grafana-app-sdk/simple" @@ -11,16 +14,15 @@ import ( "k8s.io/klog/v2" pluginsapi "github.com/grafana/grafana/apps/plugins/pkg/apis" + pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" ) func New(cfg app.Config) (app.App, error) { - managedKinds := []simple.AppManagedKind{} - for _, kinds := range GetKinds() { - for _, k := range kinds { - managedKinds = append(managedKinds, simple.AppManagedKind{ - Kind: k, - }) - } + cfg.KubeConfig.APIPath = "apis" + clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()) + client, err := pluginsv0alpha1.NewPluginClientFromGenerator(clientGenerator) + if err != nil { + return nil, err } simpleConfig := simple.AppConfig{ @@ -33,7 +35,32 @@ func New(cfg app.Config) (app.App, error) { }, }, }, - ManagedKinds: managedKinds, + ManagedKinds: []simple.AppManagedKind{ + { + Kind: pluginsv0alpha1.PluginKind(), + CustomRoutes: simple.AppCustomRouteHandlers{ + simple.AppCustomRoute{ + Method: http.MethodGet, + Path: "meta", + }: func(ctx context.Context, w app.CustomRouteResponseWriter, req *app.CustomRouteRequest) error { + plugin, err := client.Get(ctx, resource.Identifier{ + Namespace: req.ResourceIdentifier.Namespace, + Name: req.ResourceIdentifier.Name, + }) + if err != nil { + return err + } + logging.DefaultLogger.Debug("fetched plugin", "plugin", plugin) + // TODO: Implement this in future PR + w.WriteHeader(http.StatusNotImplemented) + if _, err := w.Write([]byte("Not implemented")); err != nil { + return err + } + return nil + }, + }, + }, + }, } a, err := simple.NewApp(simpleConfig) diff --git a/apps/plugins/pkg/app/install/registrar.go b/apps/plugins/pkg/app/install/registrar.go index efb7de11080..98d7c02b94b 100644 --- a/apps/plugins/pkg/app/install/registrar.go +++ b/apps/plugins/pkg/app/install/registrar.go @@ -41,12 +41,12 @@ type PluginInstall struct { Source Source } -func (p *PluginInstall) ToPluginInstallV0Alpha1(namespace string) *pluginsv0alpha1.PluginInstall { +func (p *PluginInstall) ToPluginInstallV0Alpha1(namespace string) *pluginsv0alpha1.Plugin { var url *string = nil if p.URL != "" { url = &p.URL } - return &pluginsv0alpha1.PluginInstall{ + return &pluginsv0alpha1.Plugin{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: p.ID, @@ -54,16 +54,16 @@ func (p *PluginInstall) ToPluginInstallV0Alpha1(namespace string) *pluginsv0alph PluginInstallSourceAnnotation: p.Source, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{ + Spec: pluginsv0alpha1.PluginSpec{ Id: p.ID, Version: p.Version, Url: url, - Class: pluginsv0alpha1.PluginInstallSpecClass(p.Class), + Class: pluginsv0alpha1.PluginSpecClass(p.Class), }, } } -func (p *PluginInstall) ShouldUpdate(existing *pluginsv0alpha1.PluginInstall) bool { +func (p *PluginInstall) ShouldUpdate(existing *pluginsv0alpha1.Plugin) bool { update := p.ToPluginInstallV0Alpha1(existing.Namespace) if source, ok := existing.Annotations[PluginInstallSourceAnnotation]; ok && source != p.Source { return true @@ -92,7 +92,7 @@ func equalStringPointers(a, b *string) bool { type InstallRegistrar struct { clientGenerator resource.ClientGenerator - client *pluginsv0alpha1.PluginInstallClient + client *pluginsv0alpha1.PluginClient clientOnce sync.Once } @@ -103,9 +103,9 @@ func NewInstallRegistrar(clientGenerator resource.ClientGenerator) *InstallRegis } } -func (r *InstallRegistrar) GetClient() (*pluginsv0alpha1.PluginInstallClient, error) { +func (r *InstallRegistrar) GetClient() (*pluginsv0alpha1.PluginClient, error) { r.clientOnce.Do(func() { - client, err := pluginsv0alpha1.NewPluginInstallClientFromGenerator(r.clientGenerator) + client, err := pluginsv0alpha1.NewPluginClientFromGenerator(r.clientGenerator) if err != nil { r.client = nil return diff --git a/apps/plugins/pkg/app/storage.go b/apps/plugins/pkg/app/storage.go deleted file mode 100644 index 9fca277abfc..00000000000 --- a/apps/plugins/pkg/app/storage.go +++ /dev/null @@ -1,75 +0,0 @@ -package app - -import ( - "context" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/registry/rest" - - claims "github.com/grafana/authlib/types" - pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" -) - -var ( - _ rest.Scoper = (*PluginMetaStorage)(nil) - _ rest.SingularNameProvider = (*PluginMetaStorage)(nil) - _ rest.Getter = (*PluginMetaStorage)(nil) - _ rest.Lister = (*PluginMetaStorage)(nil) - _ rest.Storage = (*PluginMetaStorage)(nil) - _ rest.TableConvertor = (*PluginMetaStorage)(nil) -) - -type PluginMetaStorage struct { - gr schema.GroupResource - namespacer claims.NamespaceFormatter - tableConverter rest.TableConvertor -} - -func NewPluginMetaStorage( - namespacer claims.NamespaceFormatter, -) *PluginMetaStorage { - gr := schema.GroupResource{ - Group: pluginsv0alpha1.PluginMetaKind().Group(), - Resource: strings.ToLower(pluginsv0alpha1.PluginMetaKind().Plural()), - } - return &PluginMetaStorage{ - gr: gr, - namespacer: namespacer, - tableConverter: rest.NewDefaultTableConvertor(gr), - } -} - -func (s *PluginMetaStorage) New() runtime.Object { - return pluginsv0alpha1.PluginMetaKind().ZeroValue() -} - -func (s *PluginMetaStorage) Destroy() {} - -func (s *PluginMetaStorage) NamespaceScoped() bool { - return true -} - -func (s *PluginMetaStorage) GetSingularName() string { - return strings.ToLower(pluginsv0alpha1.PluginMetaKind().Kind()) -} - -func (s *PluginMetaStorage) NewList() runtime.Object { - return pluginsv0alpha1.PluginMetaKind().ZeroListValue() -} - -func (s *PluginMetaStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) -} - -func (s *PluginMetaStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - return s.NewList(), nil -} - -func (s *PluginMetaStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - return nil, apierrors.NewNotFound(s.gr, name) -} diff --git a/apps/shorturl/pkg/app/app.go b/apps/shorturl/pkg/app/app.go index 4c85ef97435..bb97db78a93 100644 --- a/apps/shorturl/pkg/app/app.go +++ b/apps/shorturl/pkg/app/app.go @@ -30,11 +30,12 @@ var ( func New(cfg app.Config) (app.App, error) { cfg.KubeConfig.APIPath = "apis" - client, err := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()). + tmp, err := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig()). ClientFor(shorturlv1alpha1.ShortURLKind()) if err != nil { return nil, fmt.Errorf("unable to create client") } + client := shorturlv1alpha1.NewShortURLClient(tmp) simpleConfig := simple.AppConfig{ Name: "shorturl", @@ -81,8 +82,8 @@ func New(cfg app.Config) (app.App, error) { Name: req.ResourceIdentifier.Name, } - info := &shorturlv1alpha1.ShortURL{} - if err := client.GetInto(ctx, id, info); err != nil { + info, err := client.Get(ctx, id) + if err != nil { return err } @@ -93,7 +94,12 @@ func New(cfg app.Config) (app.App, error) { if err != nil { logging.FromContext(ctx).Warn("unable to create background identity", "err", err) } else { - _, _ = client.Update(ctx, id, info, resource.UpdateOptions{}) + _, err = client.UpdateStatus(ctx, id, info.Status, resource.UpdateOptions{ + ResourceVersion: info.ResourceVersion, + }) + if err != nil { + logging.FromContext(ctx).Warn("unable to update status", "err", err) + } } }() diff --git a/conf/defaults.ini b/conf/defaults.ini index 977d0fa8783..61d0ca7e318 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -2203,6 +2203,16 @@ resource_storage_type = "db" # Current key provider used for envelope encryption encryption_provider = secret_key.v1 +# These flags are required in on-prem installations for GitSync to work +# +# Whether to register the MT CRUD API +register_api_server = true +# Whether to create the MT secrets management database +run_secrets_db_migrations = true +# Whether to run the data key id migration. Requires that RunSecretsDBMigrations is also true. +run_data_key_migration = true + + [secrets_manager.encryption.secret_key.v1] # Used to encrypt data keys secret_key = SW2YcwTIb9zpOOhoPsMm diff --git a/conf/sample.ini b/conf/sample.ini index 5518290525e..bee580aaa98 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -2101,12 +2101,22 @@ default_datasource_uid = ###################################### Secrets Manager ###################################### [secrets_manager] -# Used for signing +# Current key provider used for envelope encryption +;encryption_provider = secret_key.v1 + +# These flags are required in on-prem installations for GitSync to work +# +# Whether to register the MT CRUD API +;register_api_server = true +# Whether to create the MT secrets management database +;run_secrets_db_migrations = true +# Whether to run the data key id migration. Requires that RunSecretsDBMigrations is also true. +;run_data_key_migration = true + +[secrets_manager.encryption.secret_key.v1] +# Used to encrypt data keys ;secret_key = SW2YcwTIb9zpOOhoPsMm -# Current key provider used for envelope encryption, default to static value specified by secret_key -;encryption_provider = secretKey.v1 -# List of configured key providers, space separated (Enterprise only): e.g., awskms.v1 azurekv.v1 -;available_encryption_providers = + ################################## Frontend development configuration ################################### # Warning! Any settings placed in this section will be available on `process.env.frontend_dev_{foo}` within frontend code diff --git a/contribute/developer-guide.md b/contribute/developer-guide.md index eaa8334926c..65dadfae463 100644 --- a/contribute/developer-guide.md +++ b/contribute/developer-guide.md @@ -18,7 +18,7 @@ We recommend using [Homebrew](https://brew.sh/) for installing any missing depen ``` brew install git brew install go -brew install node@22 +brew install node@24 ``` ### Windows diff --git a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json index 3337c794279..4725580cf0e 100644 --- a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json +++ b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json @@ -204,7 +204,27 @@ "value": 80 } ] - } + }, + "actions": [ + { + "fetch": { + "body": "{}", + "headers": [["Content-Type", "application/json"]], + "method": "GET", + "queryParams": [], + "url": "/api/health" + }, + "title": "Get instance health", + "type": "fetch" + } + ], + "links": [ + { + "targetBlank": true, + "title": "Google Grafana", + "url": "https://google.com/search?q=grafana" + } + ] }, "overrides": [] }, diff --git a/docs/sources/administration/plugin-management/_index.md b/docs/sources/administration/plugin-management/_index.md index fce84ec643e..6736e15d176 100644 --- a/docs/sources/administration/plugin-management/_index.md +++ b/docs/sources/administration/plugin-management/_index.md @@ -20,7 +20,7 @@ Plugins enhance your Grafana experience with new ways to connect to and visualiz Read on for an overview on how to get started with plugins: -- Plugins are available in the [plugin catalog](#plugin-catalog). They can be built by Grafana Labs, commercial partners, our community, or you can [build a plugin yourself](/developers/plugin-tools). +- Plugins are available in the [plugin catalog](#access-the-plugin-catalog). They can be built by Grafana Labs, commercial partners, our community, or you can [build a plugin yourself](/developers/plugin-tools). - There are three [types of plugins](#types-of-plugins): panel, data source, and app plugins. - Learn [how to install](#install-a-plugin), [update](#update-a-plugin) and [verify](#verify-your-plugins) your plugins. @@ -40,11 +40,11 @@ Grafana supports three types of plugins: Read more in [Types of plugins](plugin-types). -## Plugin catalog +## Access the Plugin catalog -The Grafana plugin catalog allows you to browse and manage plugins from within Grafana. Only Grafana server administrators and Organization administrators can access and use the plugin catalog. For more information about Grafana roles and permissions, refer to [Roles and permissions](../roles-and-permissions/). +You can install and manage plugins from within Grafana. You need to have a Grafana Server administrator or Organization administrator role to access and use the plugin catalog. For more information about Grafana roles and permissions, refer to [Roles and permissions](../roles-and-permissions/). -The following access rules apply depending on the user role: +For app plugins, the following access rules apply: - If you are an **Org Admin**, you can configure app plugins, but you can't install, uninstall, or update them. - If you are a **Server Admin**, you can't configure app plugins, but you can install, uninstall, or update them. @@ -58,6 +58,8 @@ To browse for available plugins: 1. Use the search box to filter based on name, keywords, organization and other metadata. 1. Click the **Data sources**, **Panels**, or **Applications** buttons to filter by plugin type. +If you're not logged in, you can also access the list of available plugins in the [Plugin catalog](https://grafana.com/grafana/plugins/). + ## Manage your plugins We strongly recommend running the latest plugin version. Use [Grafana Advisor](https://grafana.com/docs/grafana//administration/grafana-advisor) to check the status of your data sources and plugins. diff --git a/docs/sources/alerting/alerting-rules/alerting-migration.md b/docs/sources/alerting/alerting-rules/alerting-migration.md index 2e63db00136..3afa3aec453 100644 --- a/docs/sources/alerting/alerting-rules/alerting-migration.md +++ b/docs/sources/alerting/alerting-rules/alerting-migration.md @@ -65,6 +65,10 @@ The copied rules are converted to Grafana-managed rules, preserving their behavi The rule query offset is taken from the `query_offset` value in the rule group configuration. If empty, it defaults to the [`rule_query_offset` configuration setting](ref:configure-grafana-rule_query_offset), which is `1m` by default. +- **Rule query conversion** + + For alert rules, adds `prometheus_math` and `threshold` expressions to preserve Prometheus no data behavior, ensuring the alert stays in **Normal** state when `query` returns no data. + - **Missing series evaluations to resolve** The [Missing series evaluations to resolve](ref:missing_series_evaluations_to_resolve) setting is set to `1` to replicate Prometheus’s alert eviction behavior. diff --git a/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md b/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md index 64b1e83ef54..91b5f217315 100644 --- a/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md +++ b/docs/sources/alerting/fundamentals/notifications/group-alert-notifications.md @@ -159,7 +159,9 @@ Once the first notification has been sent for a new group of alerts, the group i When the group interval timer elapses, the system resets the group interval timer and sends a notification only if there were group changes. This process repeats until there are no more alerts. -It's important to note that an alert instance exits the group after being resolved and notified of its state change. When no alerts remain, the group is deleted, and then the group wait timer handles the first notification for the next incoming alert once again. +It's important to note that an alert instance exits the group after being resolved and notified of its state change. + +When the group interval timer elapses and no alerts remain, the group is deleted. The [group wait timer](#group-wait) will then start again the next time a new alert arrives. ### Repeat interval diff --git a/docs/sources/breaking-changes/breaking-changes-v11-0.md b/docs/sources/breaking-changes/breaking-changes-v11-0.md index 313015b275d..898673c4be5 100644 --- a/docs/sources/breaking-changes/breaking-changes-v11-0.md +++ b/docs/sources/breaking-changes/breaking-changes-v11-0.md @@ -57,7 +57,7 @@ In Grafana v11, support for the deprecated AngularJS framework is turned off by #### Migration/mitigation -To avoid disruption, ensure all plugins are up to date and migrate from any remaining AngularJS plugins to a React-based alternative. If a plugin relies on AngularJS, a warning icon and message will be displayed in the [plugins catalog](https://grafana.com/docs/grafana//administration/plugin-management/#plugin-catalog) in Grafana and any dashboard panel where it's used. Additionally, a warning banner will appear in any impacted dashboards. A list of all impacted dashboards can also be generated using the [`detect-angular-dashboards`](https://github.com/grafana/detect-angular-dashboards) tool. +To avoid disruption, ensure all plugins are up to date and migrate from any remaining AngularJS plugins to a React-based alternative. If a plugin relies on AngularJS, a warning icon and message will be displayed in the [plugins catalog](https://grafana.com/docs/grafana//administration/plugin-management/#access-the-plugin-catalog) in Grafana and any dashboard panel where it's used. Additionally, a warning banner will appear in any impacted dashboards. A list of all impacted dashboards can also be generated using the [`detect-angular-dashboards`](https://github.com/grafana/detect-angular-dashboards) tool. Our [documentation](https://grafana.com/docs/grafana//developers/angular_deprecation/angular-plugins/) lists all known public plugins and provides migration advice when possible. diff --git a/docs/sources/datasources/_index.md b/docs/sources/datasources/_index.md index 7ba5e1756a5..e7da61ea2f5 100644 --- a/docs/sources/datasources/_index.md +++ b/docs/sources/datasources/_index.md @@ -71,9 +71,8 @@ After you add and configure a data source, you can use it as an input for many o This documentation describes how to manage data sources in general, and how to configure or query the built-in data sources. -For other data sources, refer to the list of [datasource plugins](/grafana/plugins/). -To develop a custom plugin, refer to [Create a data source plugin](#create-a-data-source-plugin). +For other available plugins, refer to the list of [documented plugins](https://grafana.com/docs/plugins/) or browse the [Plugin catalog](/grafana/plugins/). To develop a custom plugin, refer to [Create a data source plugin](#create-a-data-source-plugin). ## Manage data sources diff --git a/docs/sources/observability-as-code/_index.md b/docs/sources/observability-as-code/_index.md index 42303dd62f9..33a2c6aeb4d 100644 --- a/docs/sources/observability-as-code/_index.md +++ b/docs/sources/observability-as-code/_index.md @@ -12,6 +12,7 @@ labels: products: - enterprise - oss + - cloud title: Observability as Code weight: 100 cards: @@ -32,11 +33,11 @@ cards: height: 24 href: ./foundation-sdk/ description: The Grafana Foundation SDK is a set of tools, types, and libraries that let you define Grafana dashboards and resources using strongly typed code. - - title: Git Sync (experimental) + - title: Git Sync (private preview) height: 24 href: ./provision-resources/intro-git-sync/ description: Git Sync is an experimental feature that lets you store your dashboard files in a GitHub repository and synchronize those changes with your Grafana instance. - - title: File provisioning (experimental) + - title: File provisioning (private preview) height: 24 href: ./provision-resources/ description: File provisioning in Grafana lets you include resources, including folders and dashboard JSON files, that are stored in a local file system. diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index f3d3dd89b45..ea83a3bb553 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -2559,7 +2559,7 @@ Available to Grafana administrators only, enables installing, uninstalling, and Set to `true` by default. Setting it to `false` hides the controls. -For more information, refer to [Plugin catalog](../../administration/plugin-management/#plugin-catalog). +For more information, refer to [Plugin catalog](../../administration/plugin-management/#access-the-plugin-catalog). #### `plugin_admin_external_manage_enabled` 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 d6029c860b2..f035ad7db6d 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -78,6 +78,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `alertingImportYAMLUI` | Enables a UI feature for importing rules from a Prometheus file to Grafana-managed rules | Yes | | `unifiedNavbars` | Enables unified navbars | | | `grafanaAssistantInProfilesDrilldown` | Enables integration with Grafana Assistant in Profiles Drilldown | Yes | +| `sharingDashboardImage` | Enables image sharing functionality for dashboards | Yes | | `tabularNumbers` | Use fixed-width numbers globally in the UI | | | `dashboardDsAdHocFiltering` | Enables adhoc filtering support for the dashboard datasource | Yes | | `adhocFiltersInTooltips` | Enable adhoc filter buttons in visualization tooltips | Yes | diff --git a/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md b/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md index 42cea78cf57..69dbf4a2547 100644 --- a/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md +++ b/docs/sources/visualizations/dashboards/variables/add-template-variables/index.md @@ -161,19 +161,10 @@ Query expressions are different for each data source. For more information, refe 1. [Enter general options](#enter-general-options). 1. Under the **Query options** section of the page, select a target data source in the **Data source** drop-down list. - You can also click **Open advanced data source picker** to see more options, including adding a data source (Admins only). For more information about data sources, refer to [Add a data source](ref:add-a-data-source). -1. In the **Query type** drop-down list, select one of the following options: - - **Label names** - - **Label values** - - **Metrics** - - **Query result** - - **Series query** - - **Classic query** - -1. In the **Query** field, enter a query. +1. In the **Query type** drop-down list, select an option and fill in the query fields accordingly. - The query field varies according to your data source. Some data sources have custom query editors. - Each data source defines how the variable values are extracted. The typical implementation uses every string value returned from the data source response as a variable value. Make sure to double-check the documentation for the data source. - Some data sources let you provide custom "display names" for the values. For instance, the PostgreSQL, MySQL, and Microsoft SQL Server plugins handle this by looking for fields named `__text` and `__value` in the result. Other data sources may look for `text` and `value` or use a different approach. Always remember to double-check the documentation for the data source. @@ -185,9 +176,15 @@ Query expressions are different for each data source. For more information, refe - **On dashboard load** - Queries the data source every time the dashboard loads. This slows down dashboard loading, because the variable query needs to be completed before dashboard can be initialized. - **On time range change** - Queries the data source every time the dashboard loads and when the dashboard time range changes. Use this option if your variable options query contains a time range filter or is dependent on the dashboard time range. +1. (Optional) In the **Static options** section of the page, toggle on the **Use static options** switch to add custom options in addition to the query results: + - Make entries in the **Value** and **Display text** fields. + - Click **+ Add new option** to add another static option. + - Repeat these steps as many times as needed. + 1. (Optional) Configure the settings in the [Selection Options](#configure-variable-selection-options) section: - **Multi-value** - Enables multiple values to be selected at the same time. - - **Include All option** - Enables an option to include all variables. + - **Allow custom values** - Enables users to add custom values to the list. + - **Include All option** - Enables an option to include all variables. Enter a value in the **Custom all value** field to set your own "all" option. 1. In the **Preview of values** section, Grafana displays a list of the current variable values. Review them to ensure they match what you expect. 1. Click **Save dashboard**. diff --git a/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md b/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md index 0c7e80fc41b..eefc59ac6da 100644 --- a/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md +++ b/docs/sources/visualizations/panels-visualizations/query-transform-data/_index.md @@ -131,7 +131,7 @@ It also helps you avoid having several users build the same queries for the same You can see a list of these queries in the **Saved queries** drawer: -{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-saved-queries-v12.2png.png" max-width="550px" alt="List of saved queries and the edit query form" caption="The Saved queries drawer accessed from Dashboards" >}} +{{< figure src="/media/docs/grafana/dashboards/screenshot-saved-queries-v12.3.png" max-width="550px" alt="List of saved queries and the edit query form" caption="The Saved queries drawer accessed from Dashboards" >}} When you first open the drawer, the list of queries in the **All** tab is filtered by the data source of the panel. However, you can clear that filter to display all saved queries. @@ -150,8 +150,6 @@ In the **Saved queries** drawer, you can: - Edit a query title, description, tags, or the availability of the query to other users in your organization. By default, saved queries are locked for editing. - When you access the **Saved queries** drawer from Explore, you can use the **Edit in Explore** option to edit the body of a query. -Access the duplicate, lock, unlock, and delete query options through the menu in the top-right corner of the query form next to the **Edit** button. - To access your saved queries, click **+ Add from saved queries** in the query editor: {{< figure src="/media/docs/grafana/panels-visualizations/screenshot-add-from-saved-2-v12.2.png" max-width="750px" alt="Add a saved query" >}} diff --git a/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md index 241622708db..209df209a77 100644 --- a/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/geomap/index.md @@ -212,23 +212,20 @@ You can add multiple layers of data to a single geomap in order to create rich, #### Layer type -There are seven map layer types to choose from in a geomap. +There are eight map layer types to choose from in a geomap. - [Markers](#markers-layer) renders a marker at each data point. - [Heatmap](#heatmap-layer) visualizes a heatmap of the data. - [GeoJSON](#geojson-layer) renders static data from a GeoJSON file. - [Night / Day](#night--day-layer) renders a night / day region. -- [Route (Beta)](#route-layer-beta) render data points as a route. -- [Photos (Beta)](#photos-layer-beta) renders a photo at each data point. -- [Network (Beta)](#network-layer-beta) visualizes a network graph from the data. +- [Route](#route-layer) render data points as a route. +- [Photos](#photos-layer) renders a photo at each data point. +- [Network](#network-layer) visualizes a network graph from the data. - [Open Street Map](#open-street-map-layer) adds a map from a collaborative free geographic world database. - [CARTO basemap](#carto-basemap-layer) adds a layer from CARTO Raster basemaps. - [ArcGIS MapServer](#arcgis-mapserver-layer) adds a layer from an ESRI ArcGIS MapServer. - [XYZ Tile layer](#xyz-tile-layer) adds a map from a generic tile layer. - -{{< admonition type="note" >}} -Beta is equivalent to the [public preview](/docs/release-life-cycle/) release stage. -{{< /admonition >}} +- [MapLibre Style layer](#maplibre-style-layer) adds a map from a MapLibre/Mapbox style URL. There are also two experimental (or alpha) layer types. @@ -361,11 +358,7 @@ The Night / Day layer displays night and day regions based on the current time r [Extensions for OpenLayers - DayNight](https://viglino.github.io/ol-ext/examples/layer/map.daynight.html) -#### Route layer (Beta) - -{{< admonition type="caution" >}} -The Route layer is currently in [public preview](/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available. -{{< /admonition >}} +#### Route layer The Route layer renders data points as a route. @@ -390,11 +383,7 @@ The layer can also render a route with arrows. [Extensions for OpenLayers - Flow Line Style](http://viglino.github.io/ol-ext/examples/style/map.style.gpxline.html) -#### Photos layer (Beta) - -{{< admonition type="caution" >}} -The Photos layer is currently in [public preview](/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available. -{{< /admonition >}} +#### Photos layer The Photos layer renders a photo at each data point. @@ -417,11 +406,7 @@ The Photos layer renders a photo at each data point. [Extensions for OpenLayers - Image Photo Style](http://viglino.github.io/ol-ext/examples/style/map.style.photo.html) -#### Network layer (Beta) - -{{< admonition type="caution" >}} -The Network layer is currently in [public preview](/docs/release-life-cycle/). Grafana Labs offers limited support, and breaking changes might occur prior to the feature being made generally available. -{{< /admonition >}} +#### Network layer The Network layer renders a network graph. This layer supports the same [data format supported by the node graph visualization](ref:data-format) with the addition of [geospatial data](#location-mode) included in the nodes data. The geospatial data is used to locate and render the nodes on the map. @@ -534,6 +519,13 @@ The XYZ Tile layer is a map from a generic tile layer. - [Tiled Web Map Wikipedia](https://en.wikipedia.org/wiki/Tiled_web_map) - [List of Open Street Map Tile Servers](https://wiki.openstreetmap.org/wiki/Tile_servers) +#### MapLibre Style layer + +The MapLibre Style Layer is a map defined using a MapLibre/Mapbox `style.json` URL. The style contains the URL to the tiles, layer definitions, and more. Typically, they're based on vector tiles as opposed to raster tiles. + +- **URL template** - Set a valid style URL. For example: `https://demotiles.maplibre.org/style.json` +- **Access Token** - An API token for mapbox maps. Only works for `mapbox://` URLs. Refer to [mapbox access tokens documentation](https://docs.mapbox.com/help/dive-deeper/access-tokens/) for more information. In other cases, you might have to include the token in the URL. For example: `https://example.com/map/style.json?key=XXX`. + ### Basemap layer options A basemap layer provides the visual foundation for a mapping application. It typically contains data with global coverage. Several base layer options @@ -541,12 +533,13 @@ are available each with specific configuration options to style the base map. Basemap layer types can also be added as layers. You can specify an opacity. -There are four basemap layer types to choose from in a geomap. +There are five basemap layer types to choose from in a geomap. - [Open Street Map](#open-street-map-layer) adds a map from a collaborative free geographic world database. - [CARTO basemap](#carto-basemap-layer) adds a layer from CARTO Raster basemaps. - [ArcGIS MapServer](#arcgis-mapserver-layer) adds a layer from an ESRI ArcGIS MapServer. - [XYZ Tile layer](#xyz-tile-layer) adds a map from a generic tile layer. +- [MapLibre Style layer](#maplibre-style-layer) adds a map from a MapLibre/Mapbox style URL. The default basemap layer uses the CARTO map. You can define custom default base layers in the `.ini` configuration file. @@ -556,7 +549,7 @@ The default basemap layer uses the CARTO map. You can define custom default base You can configure the default base map using config files with Grafana’s provisioning system. For more information on all the settings, refer to the [provisioning docs page](ref:provisioning-docs-page). -Use the JSON configuration option `default_baselayer_config` to define the default base map. There are currently four base map options to choose from: `carto`, `esri-xyz`, `osm-standard`, `xyz`. Here are some provisioning examples for each base map option. +Use the JSON configuration option `default_baselayer_config` to define the default base map. There are currently five base map options to choose from: `carto`, `esri-xyz`, `osm-standard`, `xyz`, `maplibre`. Here are some provisioning examples for each base map option. - **carto** loads the CartoDB tile server. You can choose from `auto`, `dark`, and `light` theme for the base map and can be set as shown below. The `showLabels` tag determines whether or not Grafana shows the Country details on top of the map. Here is an example: @@ -622,6 +615,17 @@ default_baselayer_config = `{ }` ``` +- **maplibre** loads a custom tile server defined by the user. Set a valid style `url` for this option to properly load a default base map. Here's an example: + +```ini +default_baselayer_config = `{ + "type": "maplibre", + "config": { + "url": "https://demotiles.maplibre.org/style.json" + } +}` +``` + `enable_custom_baselayers` allows you to enable or disable custom open source base maps that are already implemented. The default is `true`. ### Map controls options @@ -709,7 +713,7 @@ Displays debug information in the upper right corner. This can be useful for deb #### Tooltip -Tooltips are supported for the **Markers**, **Heatmap**, **Photos** (beta) layers. +Tooltips are supported for the **Markers**, **Heatmap**, **Photos** layers. For these layer types, choose from the following tooltip options: - **None** displays tooltips only when a data point is clicked. diff --git a/docs/sources/visualizations/panels-visualizations/visualizations/logs/index.md b/docs/sources/visualizations/panels-visualizations/visualizations/logs/index.md index 91f179e9979..3af0eeda0d5 100644 --- a/docs/sources/visualizations/panels-visualizations/visualizations/logs/index.md +++ b/docs/sources/visualizations/panels-visualizations/visualizations/logs/index.md @@ -33,7 +33,20 @@ _Logs_ are structured records of events or messages generated by a system or app The logs visualization displays these records from data sources that support logs, such as Elastic, Influx, and Loki. The logs visualization has colored indicators of log status, as well as collapsible log events that help you analyze the information generated. -![Logs visualization](/media/docs/grafana/panels-visualizations/screenshot-logs-v11.3.png) +{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-logs-v12.3.png" max-width="750px" alt="Logs visualization" >}} + +When the log line details are open, you have access to the following additional options: + +- Explain log line in Assistant +- Copy to clipboard +- Anchor to the right +- Close log details + +{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-logs-options-v12.3.png" max-width="750px" alt="Log icons" >}} + +You can access the first three options from the log line details or from the log line menu: + +{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-log-line-menu-v12.3.png" max-width="300px" alt="The log line menu" >}} {{< docs/play title="Logs Panel" url="https://play.grafana.org/d/6NmftOxZz/" >}} @@ -53,7 +66,7 @@ You can also build log-formatted data from other data sources as long as the fir The second field is used as the log record title regardless of whether it’s a time, numeric, or string field. Usually the second field is a text field containing multiple string elements, but if the message level (or `lvl`) is present, the visualization uses the values in it to add colors to the record, as described in [Log levels integration](ref:log-levels). -Subsequent fields are collapsed inside of each log record and you can open them by clicking the expand (`>`) icon. +Subsequent fields are collapsed inside of each log record. To limit the number of log lines rendered in the visualization, you can use the **Max data points** setting in the panel **Query options**. If that option isn't set, then the data source typically enforces its own default limit. @@ -65,11 +78,11 @@ To limit the number of log lines rendered in the visualization, you can use the | 2023-02-01 11:30:00 | title=Log1 lvl=error | 1 | server2 | 2023-02-01 11:00:00 | | 2023-02-01 11:00:00 | title=Log1 lvl=trace | 1 | server2 | 2023-02-01 11:00:00 | -![Logs Example](/media/docs/grafana/panels-visualizations/screenshot-grafana-12.1-logs-example.png 'Logs Example') +{{< figure src="/media/docs/grafana/panels-visualizations/screenshot-logs-example2-v12.3.png" max-width="750px" alt="Logs example" >}} ## Log level -For logs where a **level** label is specified, we use the value of the label to determine the log level and update color accordingly. If the log doesn't have a level label specified, we try to find out if its content matches any of the supported expressions (see below for more information). The log level is always determined by the first match. In case Grafana is not able to determine a log level, it will be visualized with **unknown** log level. See [supported log levels and mappings of log level abbreviation and expressions](ref:log-levels). +For logs where a `level` label is specified, we use the value of the label to determine the log level and update color accordingly. If the log doesn't have a level label specified, we try to find out if its content matches any of the supported expressions (see below for more information). The log level is always determined by the first match. In case Grafana is not able to determine a log level, it will be visualized with **unknown** log level. See [supported log levels and mappings of log level abbreviation and expressions](ref:log-levels). ## Configuration options @@ -85,14 +98,18 @@ Use these settings to refine your visualization: -| Option | Description | -| ------ | ----------- | +| Option | Description | +| --------------- | --------------- | | Time | Show or hide the time column. This is the timestamp associated with the log line as reported from the data source. | | Unique labels | Show or hide the unique labels column, which shows only non-common labels. | | Common labels | Show or hide the common labels. | | Wrap lines | Turn line wrapping on or off. | -| Prettify JSON | Toggle the switch on to pretty print all JSON logs. This setting does not affect logs in any format other than JSON. | +| Enable logs highlighting | Experimental. Use a predefined coloring scheme to highlight relevant parts of the log lines. Subtle colors are added to the log lines to improve readability and help with identifying important information faster. | | Enable log details | Toggle the switch on to see an extendable area with log details including labels and detected fields. Each field or label has a stats icon to display ad-hoc statistics in relation to all displayed logs. The default setting is on. | +| Log details panel mode | Choose to display the log details in a sidebar panel or inline, below the log line. The default mode depends on viewport size: the default mode for smaller viewports is inline, while for larger ones, it's sidebar. You can also change mode dynamically in the panel by clicking the mode control. | +| Enable infinite scrolling | Request more results by scrolling to the bottom of the logs list. When you reach the bottom of the list of logs, if you continue scrolling and the displayed logs are within the selected time interval, you can request to load more logs. When the sort order is **Newest first**, you receive older logs, and when the sort order is **Oldest first** you get newer logs. | +| Show controls | Display controls to jump to the last or first log line, and filter by log level. | +| Font size | Select between the **Default** font size and **Small** font sizes.| | Deduplication | Hide log messages that are duplicates of others shown, according to your selected criteria. Choose from:
  • **Exact** - Ignoring ISO datetimes.
  • **Numerical** - Ignoring only those that differ by numbers such as IPs or latencies.
  • **Signatures** - Removing successive lines with identical punctuation and white space.
| | Order | Set whether to show results **Newest first** or **Oldest first**. | diff --git a/docs/sources/whatsnew/whats-new-in-v8-0.md b/docs/sources/whatsnew/whats-new-in-v8-0.md index cf3792d8a48..7559f646678 100644 --- a/docs/sources/whatsnew/whats-new-in-v8-0.md +++ b/docs/sources/whatsnew/whats-new-in-v8-0.md @@ -160,7 +160,7 @@ Log navigation in Explore has been significantly improved. We added pagination t You can now use the Plugin catalog app to easily manage your plugins from within Grafana. Install, update, and uninstall plugins without requiring a server restart. -[Plugin catalog](../../administration/plugin-management/#plugin-catalog) was added as a result of this feature. +[Plugin catalog](../../administration/plugin-management/#access-the-plugin-catalog) was added as a result of this feature. ### Performance improvements diff --git a/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts b/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts index c2bde611cdf..7ef4c67d709 100644 --- a/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts +++ b/e2e-playwright/dashboard-new-layouts/dashboard-group-panels.spec.ts @@ -18,7 +18,8 @@ test.use({ viewport: { width: 1920, height: 1080 }, }); -test.describe( +// Skipping due to failing test in CI +test.skip( 'Grouping panels', { tag: ['@dashboards'], diff --git a/e2e-playwright/dashboards-suite/dashboard-templating.spec.ts b/e2e-playwright/dashboards-suite/dashboard-templating.spec.ts index 1b3f8a326fa..ded0abd61ef 100644 --- a/e2e-playwright/dashboards-suite/dashboard-templating.spec.ts +++ b/e2e-playwright/dashboards-suite/dashboard-templating.spec.ts @@ -19,9 +19,7 @@ test.describe( // Open dashboard global variables and interpolation await gotoDashboardPage({ uid: DASHBOARD_UID }); - // Get the actual timezone from the browser context (should be Pacific/Easter due to test.use) - const timeZone = await page.evaluate(() => Intl.DateTimeFormat().resolvedOptions().timeZone); - const example = `Example: from=now-6h&to=now&timezone=${encodeURIComponent(timeZone)}`; + const example = `Example: from=now-6h&to=now&timezone=browser`; const expectedItems: string[] = [ '__dashboard = Templating - Global variables and interpolation', @@ -68,10 +66,7 @@ test.describe( // Check link interpolation is working correctly const exampleLink = page.locator(`a:has-text("${example}")`); - await expect(exampleLink).toHaveAttribute( - 'href', - `https://example.com/?from=now-6h&to=now&timezone=${encodeURIComponent(timeZone)}` - ); + await expect(exampleLink).toHaveAttribute('href', `https://example.com/?from=now-6h&to=now&timezone=browser`); }); } ); diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json index e1960c9515e..12829268e71 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@grafana/plugin-configs": "workspace:*", "@types/lodash": "4.17.7", - "@types/node": "22.15.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.4", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/ExposedComponents.tsx b/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/ExposedComponents.tsx index 28775391881..6b284e171bc 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/ExposedComponents.tsx +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/pages/ExposedComponents.tsx @@ -10,6 +10,7 @@ export function ExposedComponents() { const { component: ReusableComponent } = usePluginComponent( 'grafana-extensionexample1-app/reusable-component/v1' ); + const { component: AddToDashboardForm } = usePluginComponent('grafana/add-to-dashboard-form/v1'); if (!ReusableComponent) { return null; @@ -20,6 +21,22 @@ export function ExposedComponents() {
+ {AddToDashboardForm && ( +
+

Save to dashboard (exposed form)

+ ({ + type: 'timeseries', + title: 'E2E Add to Dashboard Panel', + targets: [], + })} + // Ensure navigation works correctly from plugin page + options={{ useAbsolutePath: true }} + onClose={() => {}} + /> +
+ )} ); } diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json b/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json index 90978c2c8a2..57fa87dcde5 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/plugin.json @@ -80,7 +80,7 @@ "grafanaDependency": ">=10.4.0", "plugins": [], "extensions": { - "exposedComponents": ["grafana-extensionexample1-app/reusable-component/v1"] + "exposedComponents": ["grafana-extensionexample1-app/reusable-component/v1", "grafana/add-to-dashboard-form/v1"] } } } diff --git a/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/useExposedComponent.spec.ts b/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/useExposedComponent.spec.ts index 9f3d3f0bf35..501c929b612 100644 --- a/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/useExposedComponent.spec.ts +++ b/e2e-playwright/test-plugins/grafana-extensionstest-app/tests/useExposedComponent.spec.ts @@ -1,6 +1,7 @@ import { test, expect } from '@grafana/plugin-e2e'; import { testIds } from '../testIds'; import pluginJson from '../plugin.json'; +import { ensureExtensionRegistryIsPopulated } from './utils'; test.describe( 'grafana-extensionstest-app', @@ -12,5 +13,34 @@ test.describe( await page.goto(`/a/${pluginJson.id}/exposed-components`); await expect(page.getByTestId(testIds.appB.exposedComponent)).toHaveText('Hello World!'); }); + + test('exposed add-to-dashboard form saves to a new dashboard', async ({ page }) => { + await page.goto(`/a/${pluginJson.id}/exposed-components`); + await ensureExtensionRegistryIsPopulated(page); + + // Wait for the exposed form section to be ready + await expect(page.getByRole('heading', { name: 'Save to dashboard (exposed form)' })).toBeVisible(); + + // Wait for any of the form buttons to render (lazy load) before clicking + const openInNewTab = page.getByRole('button', { name: 'Open in new tab' }); + const cancelBtn = page.getByRole('button', { name: 'Cancel' }); + await Promise.race([expect(openInNewTab).toBeVisible(), expect(cancelBtn).toBeVisible()]); + + // Now wait for the submit button to be visible, then click (role or text) + const openDashboardByRole = page.getByRole('button', { name: 'Open dashboard' }); + const openDashboardByText = page.getByText('Open dashboard'); + if (await openDashboardByRole.isVisible().catch(() => false)) { + await openDashboardByRole.click(); + } else { + await expect(openDashboardByText.first()).toBeVisible(); + await openDashboardByText.first().click(); + } + + // Navigates to /dashboard/new and prepopulates a panel from local storage + await expect(page).toHaveURL(/\/dashboard\/new/); + + // Panel should be created with our custom title + await expect(page.getByText('E2E Add to Dashboard Panel').first()).toBeVisible(); + }); } ); diff --git a/e2e-playwright/test-plugins/grafana-test-datasource/package.json b/e2e-playwright/test-plugins/grafana-test-datasource/package.json index 71b032b1d88..e62edcbaad6 100644 --- a/e2e-playwright/test-plugins/grafana-test-datasource/package.json +++ b/e2e-playwright/test-plugins/grafana-test-datasource/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@grafana/plugin-configs": "workspace:*", "@types/lodash": "4.17.7", - "@types/node": "22.15.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.4", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/eslint-suppressions.json b/eslint-suppressions.json index fdcf848a354..e068ca60eff 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -539,11 +539,6 @@ "count": 2 } }, - "packages/grafana-sql/src/components/ErrorBoundary.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "packages/grafana-sql/src/components/configuration/ConnectionLimits.tsx": { "no-restricted-syntax": { "count": 2 @@ -666,11 +661,6 @@ "count": 3 } }, - "packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "packages/grafana-ui/src/components/FormField/FormField.tsx": { "no-restricted-syntax": { "count": 1 @@ -1278,21 +1268,11 @@ "count": 1 } }, - "public/app/core/components/Select/ServiceAccountPicker.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/core/components/Select/TeamPicker.tsx": { "react-prefer-function-component/react-prefer-function-component": { "count": 1 } }, - "public/app/core/components/Select/UserPicker.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/core/components/SharedPreferences/SharedPreferences.tsx": { "no-restricted-syntax": { "count": 8 @@ -1342,11 +1322,6 @@ "count": 2 } }, - "public/app/core/components/TimelineChart/TimelineChart.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/core/config.ts": { "no-barrel-files/no-barrel-files": { "count": 2 @@ -1483,25 +1458,17 @@ "public/app/features/admin/UserLdapSyncInfo.tsx": { "no-restricted-syntax": { "count": 3 - }, - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 } }, "public/app/features/admin/UserOrgs.tsx": { "no-restricted-syntax": { "count": 2 }, - "react-prefer-function-component/react-prefer-function-component": { - "count": 3 - } - }, - "public/app/features/admin/UserProfile.tsx": { "react-prefer-function-component/react-prefer-function-component": { "count": 1 } }, - "public/app/features/admin/UserSessions.tsx": { + "public/app/features/admin/UserProfile.tsx": { "react-prefer-function-component/react-prefer-function-component": { "count": 1 } @@ -2110,11 +2077,6 @@ "count": 1 } }, - "public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx": { - "no-restricted-syntax": { - "count": 4 - } - }, "public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx": { "no-restricted-syntax": { "count": 1 @@ -2404,9 +2366,6 @@ "public/app/features/dashboard/components/DashboardSettings/TimePickerSettings.tsx": { "no-restricted-syntax": { "count": 5 - }, - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 } }, "public/app/features/dashboard/components/DashboardSettings/VersionsSettings.tsx": { @@ -2757,11 +2716,6 @@ "count": 1 } }, - "public/app/features/datasources/components/DataSourcePluginSettings.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/features/datasources/components/DataSourcePluginState.tsx": { "no-restricted-syntax": { "count": 3 @@ -2877,11 +2831,6 @@ "count": 1 } }, - "public/app/features/explore/ExploreTimeControls.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/features/explore/Logs/LiveLogs.tsx": { "react-prefer-function-component/react-prefer-function-component": { "count": 1 @@ -2982,11 +2931,6 @@ "count": 1 } }, - "public/app/features/explore/TraceView/components/common/SearchBarInput.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/features/explore/TraceView/components/demo/trace-generators.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -3123,11 +3067,6 @@ "count": 2 } }, - "public/app/features/live/LiveConnectionWarning.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/features/live/centrifuge/LiveDataStream.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -3379,11 +3318,6 @@ "count": 2 } }, - "public/app/features/provisioning/File/FileStatusPage.tsx": { - "@typescript-eslint/consistent-type-assertions": { - "count": 2 - } - }, "public/app/features/provisioning/Shared/BranchValidationError.tsx": { "react/no-unescaped-entities": { "count": 26 @@ -3588,11 +3522,6 @@ "count": 1 } }, - "public/app/features/transformers/editors/ConcatenateTransformerEditor.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/features/transformers/editors/ConvertFieldTypeTransformerEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -3710,11 +3639,6 @@ "count": 2 } }, - "public/app/features/variables/constant/ConstantVariableEditor.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/features/variables/constant/reducer.ts": { "@typescript-eslint/consistent-type-assertions": { "count": 1 @@ -3797,9 +3721,6 @@ "public/app/features/variables/pickers/shared/VariableInput.tsx": { "no-restricted-syntax": { "count": 1 - }, - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 } }, "public/app/features/variables/pickers/shared/VariableOptions.tsx": { @@ -4014,11 +3935,6 @@ "count": 1 } }, - "public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/plugins/datasource/cloud-monitoring/components/Fields.tsx": { "no-restricted-syntax": { "count": 1 @@ -4217,11 +4133,6 @@ "count": 1 } }, - "public/app/plugins/datasource/grafana-testdata-datasource/components/CSVWaveEditor.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/plugins/datasource/grafana-testdata-datasource/components/RandomWalkEditor.tsx": { "@typescript-eslint/consistent-type-assertions": { "count": 2 @@ -4930,11 +4841,6 @@ "count": 1 } }, - "public/app/plugins/panel/stat/StatPanel.tsx": { - "react-prefer-function-component/react-prefer-function-component": { - "count": 1 - } - }, "public/app/plugins/panel/state-timeline/migrations.ts": { "@typescript-eslint/no-explicit-any": { "count": 2 diff --git a/go.mod b/go.mod index ecaaeeafdce..e988024fcdb 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/dgraph-io/badger/v4 v4.7.0 // @grafana/grafana-search-and-storage github.com/dlmiddlecote/sqlstats v1.0.2 // @grafana/grafana-backend-group github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e // @grafana/grafana-datasources-core-services - github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // @grafana/grafana-datasources-core-services + github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 // @grafana/grafana-datasources-core-services github.com/dustin/go-humanize v1.0.1 // @grafana/observability-traces-and-profiling github.com/emicklei/go-restful/v3 v3.13.0 // @grafana/grafana-app-platform-squad github.com/fatih/color v1.18.0 // @grafana/grafana-backend-group @@ -406,7 +406,6 @@ require ( github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect - github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad // indirect github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect github.com/dolthub/maphash v0.1.0 // indirect github.com/edsrzf/mmap-go v1.2.0 // indirect @@ -585,7 +584,6 @@ require ( github.com/stoewer/go-strcase v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tetratelabs/wazero v1.8.2 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect @@ -680,6 +678,10 @@ exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible // lock for mysql tsdb compat replace github.com/go-sql-driver/mysql => github.com/go-sql-driver/mysql v1.7.1 +// Use our fork of dolthub/go-mysql-server which makes non-cgo the default +// since using a build tag is not sufficient for some use cases (e.g. developers tests in IDE). +replace github.com/dolthub/go-mysql-server => github.com/grafana/go-mysql-server v0.20.1-grafana1 + // v1.* versions were retracted, we need to stick with v0.*. This should work // without the exclude, but this otherwise gets pulled in as a transitive // dependency. diff --git a/go.sum b/go.sum index 799c4a97b4a..a6d4d890765 100644 --- a/go.sum +++ b/go.sum @@ -1120,16 +1120,12 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= -github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad h1:66ZPawHszNu37VPQckdhX1BPPVzREsGgNxQeefnlm3g= -github.com/dolthub/go-icu-regex v0.0.0-20250327004329-6799764f2dad/go.mod h1:ylU4XjUpsMcvl/BKeRRMXSH7e7WBrPXdSLvnRJYrxEA= -github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e h1:7pAttAqWaudUAsM9iHASi/4eFBK+qn4qeaNto7g8bK4= -github.com/dolthub/go-mysql-server v0.19.1-0.20250410182021-5632d67cd46e/go.mod h1:KZyoO3jngyZCLyCf100FEQTrwAHj33AIMj4Zv4u3MNE= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= github.com/dolthub/maphash v0.1.0 h1:bsQ7JsF4FkkWyrP3oCnFJgrCUAFbFf3kOl4L/QxPDyQ= github.com/dolthub/maphash v0.1.0/go.mod h1:gkg4Ch4CdCDu5h6PMriVLawB7koZ+5ijb9puGMV50a4= -github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 h1:LGTt2LtYX8vaai32d+c9L0sMcP+Dg9w1kO6+lbsxxYg= -github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= +github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 h1:ScHTwNbcVC6JH1OSyXzj8S4w67BIpRXwTSjrac3/PSw= +github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33/go.mod h1:8pvvk5OLaLN9LLxghyczUapn/97l+mBgIb10qC1LG84= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -1617,6 +1613,8 @@ github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz2 github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4= github.com/grafana/e2e v0.1.1 h1:/b6xcv5BtoBnx8cZnCiey9DbjEc8z7gXHO5edoeRYxc= github.com/grafana/e2e v0.1.1/go.mod h1:RpNLgae5VT+BUHvPE+/zSypmOXKwEu4t+tnEMS1ATaE= +github.com/grafana/go-mysql-server v0.20.1-grafana1 h1:yA4Mzt+tTdIlQutBUaiPnepULPQ7CS4hMu2GOpHqT6s= +github.com/grafana/go-mysql-server v0.20.1-grafana1/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8= github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f h1:5xkjl5Y/j2QefJKOtTfyD1wXlVsQ2yEXmd0u82h5obs= github.com/grafana/gofpdf v0.0.0-20250307124105-3b9c5d35577f/go.mod h1:+O5QxOwwgP10jedZHapzXY+IPKTnzHBtIs5UUb9G+kI= github.com/grafana/gomemcache v0.0.0-20250318131618-74242eea118d h1:oXRJlb9UjVsl6LhqBdbyAQ9YFhExwsj4bjh5vwMNRZY= @@ -2463,8 +2461,6 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4= -github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs= github.com/thanos-io/objstore v0.0.0-20240818203309-0363dadfdfb1 h1:z0v9BB/p7s4J6R//+0a5M3wCld8KzNjrGRLIwXfrAZk= github.com/thanos-io/objstore v0.0.0-20240818203309-0363dadfdfb1/go.mod h1:3ukSkG4rIRUGkKM4oIz+BSuUx2e3RlQVVv3Cc3W+Tv4= github.com/thejerf/slogassert v0.3.4 h1:VoTsXixRbXMrRSSxDjYTiEDCM4VWbsYPW5rB/hX24kM= diff --git a/go.work.sum b/go.work.sum index adb7d5ecb4d..8ca02b58a7f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -576,8 +576,7 @@ github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9X github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY= github.com/dolthub/swiss v0.2.1 h1:gs2osYs5SJkAaH5/ggVJqXQxRXtWshF6uE0lgR/Y3Gw= github.com/dolthub/swiss v0.2.1/go.mod h1:8AhKZZ1HK7g18j7v7k6c5cYIGEZJcPn0ARsai8cUrh0= -github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33 h1:ScHTwNbcVC6JH1OSyXzj8S4w67BIpRXwTSjrac3/PSw= -github.com/dolthub/vitess v0.0.0-20250930230441-70c2c6a98e33/go.mod h1:8pvvk5OLaLN9LLxghyczUapn/97l+mBgIb10qC1LG84= +github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4/go.mod h1:1gQZs/byeHLMSul3Lvl3MzioMtOW1je79QYGyi2fd70= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415 h1:q1oJaUPdmpDm/VyXosjgPgr6wS7c5iV2p0PwJD73bUI= @@ -751,6 +750,9 @@ github.com/grafana/dskit v0.0.0-20250611075409-46f51e1ce914/go.mod h1:OiN4P4aC6L github.com/grafana/dskit v0.0.0-20250818234656-8ff9c6532e85/go.mod h1:kImsvJ1xnmeT9Z6StK+RdEKLzlpzBsKwJbEQfmBJdFs= github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak= github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90= +github.com/grafana/go-mysql-server v0.20.1-0.20251027172658-317a8d46ffa4/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8= +github.com/grafana/go-mysql-server v0.20.1-grafana1 h1:yA4Mzt+tTdIlQutBUaiPnepULPQ7CS4hMu2GOpHqT6s= +github.com/grafana/go-mysql-server v0.20.1-grafana1/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8= github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0= github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU= github.com/grafana/grafana-app-sdk/logging v0.48.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index 13c2b4f25f6..e32b8b6725e 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -666,6 +666,10 @@ lineage: schemas: [{ // Controls if the timeFrom or timeShift overrides are shown in the panel header hideTimeOverride?: bool + // Compare the current time range with a previous period + // For example "1d" to compare current period but shifted back 1 day + timeCompare?: string + // Dynamically load the panel libraryPanel?: #LibraryPanelRef diff --git a/package.json b/package.json index 37df4fb1c32..672ca9639ed 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@grafana/test-utils": "workspace:*", "@manypkg/get-packages": "^3.0.0", "@npmcli/package-json": "^6.0.0", - "@playwright/test": "1.54.1", + "@playwright/test": "1.56.1", "@pmmmwh/react-refresh-webpack-plugin": "0.6.1", "@react-types/button": "3.13.0", "@react-types/menu": "3.10.3", @@ -136,7 +136,7 @@ "@types/lodash": "4.17.20", "@types/logfmt": "^1.2.3", "@types/lucene": "^2", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/node-forge": "^1", "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.3.0", "@types/pluralize": "^0.0.33", @@ -296,8 +296,8 @@ "@grafana/plugin-ui": "^0.10.10", "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", - "@grafana/scenes": "^6.39.8", - "@grafana/scenes-react": "^6.39.8", + "@grafana/scenes": "^6.41.0", + "@grafana/scenes-react": "^6.41.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", @@ -383,6 +383,7 @@ "node-forge": "^1.3.1", "ol": "10.6.1", "ol-ext": "4.0.35", + "ol-mapbox-style": "^13.0.1", "pluralize": "^8.0.0", "prismjs": "1.30.0", "rc-slider": "11.1.9", diff --git a/packages/grafana-api-clients/package.json b/packages/grafana-api-clients/package.json index 3aa5bd4dc6c..9741c97303b 100644 --- a/packages/grafana-api-clients/package.json +++ b/packages/grafana-api-clients/package.json @@ -48,6 +48,10 @@ "import": "./src/clients/rtkq/iam/v0alpha1/index.ts", "require": "./src/clients/rtkq/iam/v0alpha1/index.ts" }, + "./rtkq/legacy": { + "import": "./src/clients/rtkq/legacy/index.ts", + "require": "./src/clients/rtkq/legacy/index.ts" + }, "./rtkq/legacy/migrate-to-cloud": { "import": "./src/clients/rtkq/migrate-to-cloud/index.ts", "require": "./src/clients/rtkq/migrate-to-cloud/index.ts" diff --git a/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts index d054b90a9f4..4b4de61be87 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/advisor/v0alpha1/endpoints.gen.ts @@ -1,11 +1,15 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Check', 'CheckType'] as const; +export const addTagTypes = ['API Discovery', 'Check', 'CheckType'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/advisor.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), listCheck: build.query({ query: (queryArg) => ({ url: `/checks`, @@ -39,6 +43,29 @@ const injectedRtkApi = api }), invalidatesTags: ['Check'], }), + deletecollectionCheck: build.mutation({ + query: (queryArg) => ({ + url: `/checks`, + 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: ['Check'], + }), getCheck: build.query({ query: (queryArg) => ({ url: `/checks/${queryArg.name}`, @@ -48,6 +75,20 @@ const injectedRtkApi = api }), providesTags: ['Check'], }), + replaceCheck: build.mutation({ + query: (queryArg) => ({ + url: `/checks/${queryArg.name}`, + method: 'PUT', + body: queryArg.check, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Check'], + }), deleteCheck: build.mutation({ query: (queryArg) => ({ url: `/checks/${queryArg.name}`, @@ -78,6 +119,44 @@ const injectedRtkApi = api }), invalidatesTags: ['Check'], }), + getCheckStatus: build.query({ + query: (queryArg) => ({ + url: `/checks/${queryArg.name}/status`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Check'], + }), + replaceCheckStatus: build.mutation({ + query: (queryArg) => ({ + url: `/checks/${queryArg.name}/status`, + method: 'PUT', + body: queryArg.check, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Check'], + }), + updateCheckStatus: build.mutation({ + query: (queryArg) => ({ + url: `/checks/${queryArg.name}/status`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Check'], + }), listCheckType: build.query({ query: (queryArg) => ({ url: `/checktypes`, @@ -97,6 +176,81 @@ const injectedRtkApi = api }), providesTags: ['CheckType'], }), + createCheckType: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes`, + method: 'POST', + body: queryArg.checkType, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['CheckType'], + }), + deletecollectionCheckType: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes`, + 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: ['CheckType'], + }), + getCheckType: build.query({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['CheckType'], + }), + replaceCheckType: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}`, + method: 'PUT', + body: queryArg.checkType, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['CheckType'], + }), + deleteCheckType: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['CheckType'], + }), updateCheckType: build.mutation({ query: (queryArg) => ({ url: `/checktypes/${queryArg.name}`, @@ -112,10 +266,50 @@ const injectedRtkApi = api }), invalidatesTags: ['CheckType'], }), + getCheckTypeStatus: build.query({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}/status`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['CheckType'], + }), + replaceCheckTypeStatus: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}/status`, + method: 'PUT', + body: queryArg.checkType, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['CheckType'], + }), + updateCheckTypeStatus: build.mutation({ + query: (queryArg) => ({ + url: `/checktypes/${queryArg.name}/status`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['CheckType'], + }), }), overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; export type ListCheckApiResponse = /** status 200 OK */ CheckList; export type ListCheckApiArg = { /** 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). */ @@ -176,6 +370,57 @@ export type CreateCheckApiArg = { fieldValidation?: string; check: Check; }; +export type DeletecollectionCheckApiResponse = /** status 200 OK */ Status; +export type DeletecollectionCheckApiArg = { + /** 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 GetCheckApiResponse = /** status 200 OK */ Check; export type GetCheckApiArg = { /** name of the Check */ @@ -183,6 +428,20 @@ export type GetCheckApiArg = { /** 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 ReplaceCheckApiResponse = /** status 200 OK */ Check | /** status 201 Created */ Check; +export type ReplaceCheckApiArg = { + /** name of the Check */ + name: 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; + check: Check; +}; export type DeleteCheckApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; export type DeleteCheckApiArg = { /** name of the Check */ @@ -216,6 +475,43 @@ export type UpdateCheckApiArg = { force?: boolean; patch: Patch; }; +export type GetCheckStatusApiResponse = /** status 200 OK */ Check; +export type GetCheckStatusApiArg = { + /** name of the Check */ + name: 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 ReplaceCheckStatusApiResponse = /** status 200 OK */ Check | /** status 201 Created */ Check; +export type ReplaceCheckStatusApiArg = { + /** name of the Check */ + name: 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; + check: Check; +}; +export type UpdateCheckStatusApiResponse = /** status 200 OK */ Check | /** status 201 Created */ Check; +export type UpdateCheckStatusApiArg = { + /** name of the Check */ + name: 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; + patch: Patch; +}; export type ListCheckTypeApiResponse = /** status 200 OK */ CheckTypeList; export type ListCheckTypeApiArg = { /** 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). */ @@ -261,6 +557,110 @@ export type ListCheckTypeApiArg = { /** 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 CreateCheckTypeApiResponse = /** status 200 OK */ + | CheckType + | /** status 201 Created */ CheckType + | /** status 202 Accepted */ CheckType; +export type CreateCheckTypeApiArg = { + /** 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; + checkType: CheckType; +}; +export type DeletecollectionCheckTypeApiResponse = /** status 200 OK */ Status; +export type DeletecollectionCheckTypeApiArg = { + /** 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 GetCheckTypeApiResponse = /** status 200 OK */ CheckType; +export type GetCheckTypeApiArg = { + /** name of the CheckType */ + name: 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 ReplaceCheckTypeApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType; +export type ReplaceCheckTypeApiArg = { + /** name of the CheckType */ + name: 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; + checkType: CheckType; +}; +export type DeleteCheckTypeApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteCheckTypeApiArg = { + /** name of the CheckType */ + name: 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 UpdateCheckTypeApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType; export type UpdateCheckTypeApiArg = { /** name of the CheckType */ @@ -277,6 +677,75 @@ export type UpdateCheckTypeApiArg = { force?: boolean; patch: Patch; }; +export type GetCheckTypeStatusApiResponse = /** status 200 OK */ CheckType; +export type GetCheckTypeStatusApiArg = { + /** name of the CheckType */ + name: 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 ReplaceCheckTypeStatusApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType; +export type ReplaceCheckTypeStatusApiArg = { + /** name of the CheckType */ + name: 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; + checkType: CheckType; +}; +export type UpdateCheckTypeStatusApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType; +export type UpdateCheckTypeStatusApiArg = { + /** name of the CheckType */ + name: 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; + patch: Patch; +}; +export type ApiResource = { + /** 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 ApiResourceList = { + /** 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: ApiResource[]; +}; export type Time = string; export type FieldsV1 = object; export type ManagedFieldsEntry = { @@ -537,3 +1006,33 @@ export type CheckTypeList = { kind?: string; metadata: ListMeta; }; +export const { + useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, + useListCheckQuery, + useLazyListCheckQuery, + useCreateCheckMutation, + useDeletecollectionCheckMutation, + useGetCheckQuery, + useLazyGetCheckQuery, + useReplaceCheckMutation, + useDeleteCheckMutation, + useUpdateCheckMutation, + useGetCheckStatusQuery, + useLazyGetCheckStatusQuery, + useReplaceCheckStatusMutation, + useUpdateCheckStatusMutation, + useListCheckTypeQuery, + useLazyListCheckTypeQuery, + useCreateCheckTypeMutation, + useDeletecollectionCheckTypeMutation, + useGetCheckTypeQuery, + useLazyGetCheckTypeQuery, + useReplaceCheckTypeMutation, + useDeleteCheckTypeMutation, + useUpdateCheckTypeMutation, + useGetCheckTypeStatusQuery, + useLazyGetCheckTypeStatusQuery, + useReplaceCheckTypeStatusMutation, + useUpdateCheckTypeStatusMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts index ef81f37a246..6cfab61cdba 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/correlations/v0alpha1/endpoints.gen.ts @@ -509,3 +509,16 @@ export type Status = { status?: string; }; export type Patch = object; +export const { + useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, + useListCorrelationQuery, + useLazyListCorrelationQuery, + useCreateCorrelationMutation, + useDeletecollectionCorrelationMutation, + useGetCorrelationQuery, + useLazyGetCorrelationQuery, + useReplaceCorrelationMutation, + useDeleteCorrelationMutation, + useUpdateCorrelationMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts index 5abfbe269e8..6ca3505d067 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/dashboard/v0alpha1/endpoints.gen.ts @@ -1,11 +1,240 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Search'] as const; +export const addTagTypes = ['API Discovery', 'Dashboard', 'LibraryPanel', 'Search'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/dashboard.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), + listDashboard: build.query({ + query: (queryArg) => ({ + url: `/dashboards`, + 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: ['Dashboard'], + }), + createDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards`, + method: 'POST', + body: queryArg.dashboard, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Dashboard'], + }), + deletecollectionDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards`, + 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: ['Dashboard'], + }), + getDashboard: build.query({ + query: (queryArg) => ({ + url: `/dashboards/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Dashboard'], + }), + replaceDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/${queryArg.name}`, + method: 'PUT', + body: queryArg.dashboard, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Dashboard'], + }), + deleteDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['Dashboard'], + }), + updateDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Dashboard'], + }), + getDashboardDto: build.query({ + query: (queryArg) => ({ url: `/dashboards/${queryArg.name}/dto` }), + providesTags: ['Dashboard'], + }), + listLibraryPanel: build.query({ + query: (queryArg) => ({ + url: `/librarypanels`, + 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: ['LibraryPanel'], + }), + createLibraryPanel: build.mutation({ + query: (queryArg) => ({ + url: `/librarypanels`, + method: 'POST', + body: queryArg.libraryPanel, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['LibraryPanel'], + }), + deletecollectionLibraryPanel: build.mutation< + DeletecollectionLibraryPanelApiResponse, + DeletecollectionLibraryPanelApiArg + >({ + query: (queryArg) => ({ + url: `/librarypanels`, + 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: ['LibraryPanel'], + }), + getLibraryPanel: build.query({ + query: (queryArg) => ({ + url: `/librarypanels/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['LibraryPanel'], + }), + replaceLibraryPanel: build.mutation({ + query: (queryArg) => ({ + url: `/librarypanels/${queryArg.name}`, + method: 'PUT', + body: queryArg.libraryPanel, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['LibraryPanel'], + }), + deleteLibraryPanel: build.mutation({ + query: (queryArg) => ({ + url: `/librarypanels/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['LibraryPanel'], + }), + updateLibraryPanel: build.mutation({ + query: (queryArg) => ({ + url: `/librarypanels/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['LibraryPanel'], + }), getSearch: build.query({ query: (queryArg) => ({ url: `/search`, @@ -22,10 +251,351 @@ const injectedRtkApi = api }), providesTags: ['Search'], }), + getSearchSortable: build.query({ + query: () => ({ url: `/search/sortable` }), + providesTags: ['Search'], + }), }), overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; +export type ListDashboardApiResponse = /** status 200 OK */ DashboardList; +export type ListDashboardApiArg = { + /** 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 CreateDashboardApiResponse = /** status 200 OK */ + | Dashboard + | /** status 201 Created */ Dashboard + | /** status 202 Accepted */ Dashboard; +export type CreateDashboardApiArg = { + /** 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; + dashboard: Dashboard; +}; +export type DeletecollectionDashboardApiResponse = /** status 200 OK */ Status; +export type DeletecollectionDashboardApiArg = { + /** 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 GetDashboardApiResponse = /** status 200 OK */ Dashboard; +export type GetDashboardApiArg = { + /** name of the Dashboard */ + name: 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 ReplaceDashboardApiResponse = /** status 200 OK */ Dashboard | /** status 201 Created */ Dashboard; +export type ReplaceDashboardApiArg = { + /** name of the Dashboard */ + name: 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; + dashboard: Dashboard; +}; +export type DeleteDashboardApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteDashboardApiArg = { + /** name of the Dashboard */ + name: 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 UpdateDashboardApiResponse = /** status 200 OK */ Dashboard | /** status 201 Created */ Dashboard; +export type UpdateDashboardApiArg = { + /** name of the Dashboard */ + name: 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; + patch: Patch; +}; +export type GetDashboardDtoApiResponse = /** status 200 OK */ DashboardWithAccessInfo; +export type GetDashboardDtoApiArg = { + /** name of the DashboardWithAccessInfo */ + name: string; +}; +export type ListLibraryPanelApiResponse = /** status 200 OK */ LibraryPanelList; +export type ListLibraryPanelApiArg = { + /** 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 CreateLibraryPanelApiResponse = /** status 200 OK */ + | LibraryPanel + | /** status 201 Created */ LibraryPanel + | /** status 202 Accepted */ LibraryPanel; +export type CreateLibraryPanelApiArg = { + /** 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; + libraryPanel: LibraryPanel; +}; +export type DeletecollectionLibraryPanelApiResponse = /** status 200 OK */ Status; +export type DeletecollectionLibraryPanelApiArg = { + /** 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 GetLibraryPanelApiResponse = /** status 200 OK */ LibraryPanel; +export type GetLibraryPanelApiArg = { + /** name of the LibraryPanel */ + name: 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 ReplaceLibraryPanelApiResponse = /** status 200 OK */ LibraryPanel | /** status 201 Created */ LibraryPanel; +export type ReplaceLibraryPanelApiArg = { + /** name of the LibraryPanel */ + name: 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; + libraryPanel: LibraryPanel; +}; +export type DeleteLibraryPanelApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteLibraryPanelApiArg = { + /** name of the LibraryPanel */ + name: 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 UpdateLibraryPanelApiResponse = /** status 200 OK */ LibraryPanel | /** status 201 Created */ LibraryPanel; +export type UpdateLibraryPanelApiArg = { + /** name of the LibraryPanel */ + name: 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; + patch: Patch; +}; export type GetSearchApiResponse = /** status 200 undefined */ SearchResults; export type GetSearchApiArg = { /** user query string */ @@ -45,6 +615,390 @@ export type GetSearchApiArg = { /** add debugging info that may help explain why the result matched */ explain?: boolean; }; +export type GetSearchSortableApiResponse = /** status 200 undefined */ { + /** 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; + /** Sortable fields (depends on backend support) */ + fields: any[]; + /** 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; +}; +export type GetSearchSortableApiArg = void; +export type ApiResource = { + /** 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 ApiResourceList = { + /** 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: ApiResource[]; +}; +export type Time = string; +export type FieldsV1 = object; +export type ManagedFieldsEntry = { + /** 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?: FieldsV1; + /** 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?: Time; +}; +export type OwnerReference = { + /** 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 ObjectMeta = { + /** 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?: Time; + /** 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?: Time; + /** 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?: ManagedFieldsEntry[]; + /** 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?: OwnerReference[]; + /** 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 Unstructured = { + [key: string]: any; +}; +export type DashboardConversionStatus = { + /** The error message from the conversion. Empty if the conversion has not failed. */ + error?: string; + /** Whether from another version has failed. If true, means that the dashboard is not valid, and the caller should instead fetch the stored version. */ + failed: boolean; + /** The original value map[string]any */ + source?: object; + /** The version which was stored when the dashboard was created / updated. Fetching this version should always succeed. */ + storedVersion?: string; +}; +export type DashboardStatus = { + /** Optional conversion status. */ + conversion?: DashboardConversionStatus; +}; +export type Dashboard = { + /** 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: ObjectMeta; + /** Spec is the spec of the Dashboard */ + spec: Unstructured; + status: DashboardStatus; +}; +export type ListMeta = { + /** 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 DashboardList = { + /** 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: Dashboard[]; + /** 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: ListMeta; +}; +export type StatusCause = { + /** 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 StatusDetails = { + /** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */ + causes?: StatusCause[]; + /** 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 Status = { + /** 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?: StatusDetails; + /** 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?: ListMeta; + /** 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 Patch = object; +export type AnnotationActions = { + canAdd: boolean; + canDelete: boolean; + canEdit: boolean; +}; +export type AnnotationPermission = { + dashboard: AnnotationActions; + organization: AnnotationActions; +}; +export type DashboardAccess = { + annotationsPermissions: AnnotationPermission; + canAdmin: boolean; + canDelete: boolean; + canEdit: boolean; + /** The permissions part */ + canSave: boolean; + canStar: boolean; + /** Metadata fields */ + slug?: string; + url?: string; +}; +export type DashboardWithAccessInfo = { + access: DashboardAccess; + /** 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: ObjectMeta; + /** Spec is the spec of the Dashboard */ + spec: Unstructured; + status: DashboardStatus; +}; +export type DataSourceRef = { + /** The apiserver version */ + apiVersion?: string; + /** The datasource plugin type */ + type: string; + /** Datasource UID (NOTE: name in k8s) */ + uid?: string; +}; +export type GridPos = { + h: number; + w: number; + x: number; + y: number; +}; +export type DataQuery = { + /** The datasource */ + datasource?: { + /** The apiserver version */ + apiVersion?: string; + /** The datasource plugin type */ + type: string; + /** Datasource UID (NOTE: name in k8s) */ + uid?: string; + }; + /** true if query is disabled (ie should not be returned to the dashboard) + NOTE: this does not always imply that the query should not be executed since + the results from a hidden query may be used as the input to other queries (SSE etc) */ + hide?: boolean; + /** Interval is the suggested duration between time points in a time series query. + NOTE: the values for intervalMs is not saved in the query model. It is typically calculated + from the interval required to fill a pixels in the visualization */ + intervalMs?: number; + /** MaxDataPoints is the maximum number of data points that should be returned from a time series query. + NOTE: the values for maxDataPoints is not saved in the query model. It is typically calculated + from the number of pixels visible in a visualization */ + maxDataPoints?: number; + /** QueryType is an optional identifier for the type of query. + It can be used to distinguish different types of queries. */ + queryType?: string; + /** RefID is the unique identifier of the query, set by the frontend call. */ + refId?: string; + /** Optionally define expected query result behavior */ + resultAssertions?: { + /** Maximum frame count */ + maxFrames?: number; + /** Type asserts that the frame matches a known type structure. + + + Possible enum values: + - `""` + - `"timeseries-wide"` + - `"timeseries-long"` + - `"timeseries-many"` + - `"timeseries-multi"` + - `"directory-listing"` + - `"table"` + - `"numeric-wide"` + - `"numeric-multi"` + - `"numeric-long"` + - `"log-lines"` */ + type?: + | '' + | 'timeseries-wide' + | 'timeseries-long' + | 'timeseries-many' + | 'timeseries-multi' + | 'directory-listing' + | 'table' + | 'numeric-wide' + | 'numeric-multi' + | 'numeric-long' + | 'log-lines'; + /** TypeVersion is the version of the Type property. Versions greater than 0.0 correspond to the dataplane + contract documentation https://grafana.github.io/dataplane/contract/. */ + typeVersion: number[]; + }; + /** TimeRange represents the query range + NOTE: unlike generic /ds/query, we can now send explicit time values in each query + NOTE: the values for timeRange are not saved in a dashboard, they are constructed on the fly */ + timeRange?: { + /** From is the start time of the query. */ + from: string; + /** To is the end time of the query. */ + to: string; + }; + [key: string]: any; +}; +export type LibraryPanelSpec = { + /** The default datasource type */ + datasource?: DataSourceRef; + /** Library panel description */ + description?: string; + /** The fieldConfig schema depends on the panel type */ + fieldConfig: Unstructured; + /** The grid position */ + gridPos?: GridPos; + /** The links for the panel */ + links?: Unstructured[]; + /** The options schema depends on the panel type */ + options: Unstructured; + /** The title of the panel when displayed in the dashboard */ + panelTitle?: string; + /** The panel type */ + pluginVersion?: string; + /** The datasource queries */ + targets?: DataQuery[]; + /** The title of the library panel */ + title?: string; + /** Whether the panel is transparent */ + transparent?: boolean; + /** The panel type */ + type: string; +}; +export type LibraryPanelStatus = { + /** The properties previously stored in SQL that are not included in this model */ + missing?: Unstructured; + /** Translation warnings (mostly things that were in SQL columns but not found in the saved body) */ + warnings?: string[]; +}; +export type LibraryPanel = { + /** 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; + /** Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + metadata?: ObjectMeta; + /** Panel properties */ + spec: LibraryPanelSpec; + /** Status will show errors */ + status?: LibraryPanelStatus; +}; +export type LibraryPanelList = { + /** 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: LibraryPanel[]; + /** 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?: ListMeta; +}; export type TermFacet = { count?: number; term?: string; @@ -105,3 +1059,31 @@ export type SearchResults = { /** The number of matching results */ totalHits: number; }; +export const { + useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, + useListDashboardQuery, + useLazyListDashboardQuery, + useCreateDashboardMutation, + useDeletecollectionDashboardMutation, + useGetDashboardQuery, + useLazyGetDashboardQuery, + useReplaceDashboardMutation, + useDeleteDashboardMutation, + useUpdateDashboardMutation, + useGetDashboardDtoQuery, + useLazyGetDashboardDtoQuery, + useListLibraryPanelQuery, + useLazyListLibraryPanelQuery, + useCreateLibraryPanelMutation, + useDeletecollectionLibraryPanelMutation, + useGetLibraryPanelQuery, + useLazyGetLibraryPanelQuery, + useReplaceLibraryPanelMutation, + useDeleteLibraryPanelMutation, + useUpdateLibraryPanelMutation, + useGetSearchQuery, + useLazyGetSearchQuery, + useGetSearchSortableQuery, + useLazyGetSearchSortableQuery, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts index ae8bba67c4b..9ef9e4ebfab 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/folder/v1beta1/endpoints.gen.ts @@ -560,3 +560,24 @@ export type FolderInfoList = { kind?: string; metadata?: ListMeta; }; +export const { + useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, + useListFolderQuery, + useLazyListFolderQuery, + useCreateFolderMutation, + useDeletecollectionFolderMutation, + useGetFolderQuery, + useLazyGetFolderQuery, + useReplaceFolderMutation, + useDeleteFolderMutation, + useUpdateFolderMutation, + useGetFolderAccessQuery, + useLazyGetFolderAccessQuery, + useGetFolderChildrenQuery, + useLazyGetFolderChildrenQuery, + useGetFolderCountsQuery, + useLazyGetFolderCountsQuery, + useGetFolderParentsQuery, + useLazyGetFolderParentsQuery, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts index d59b1d4cd52..3645b4b3373 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/iam/v0alpha1/endpoints.gen.ts @@ -1,11 +1,23 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Display'] as const; +export const addTagTypes = [ + 'API Discovery', + 'Display', + 'ServiceAccount', + 'SSOSetting', + 'TeamBinding', + 'Team', + 'User', +] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/iam.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), getDisplayMapping: build.query({ query: (queryArg) => ({ url: `/display`, @@ -15,15 +27,1353 @@ const injectedRtkApi = api }), providesTags: ['Display'], }), + listServiceAccount: build.query({ + query: (queryArg) => ({ + url: `/serviceaccounts`, + 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: ['ServiceAccount'], + }), + createServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts`, + method: 'POST', + body: queryArg.serviceAccount, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['ServiceAccount'], + }), + deletecollectionServiceAccount: build.mutation< + DeletecollectionServiceAccountApiResponse, + DeletecollectionServiceAccountApiArg + >({ + query: (queryArg) => ({ + url: `/serviceaccounts`, + 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: ['ServiceAccount'], + }), + getServiceAccount: build.query({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['ServiceAccount'], + }), + replaceServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.name}`, + method: 'PUT', + body: queryArg.serviceAccount, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['ServiceAccount'], + }), + deleteServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['ServiceAccount'], + }), + updateServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['ServiceAccount'], + }), + getServiceAccountTokens: build.query({ + query: (queryArg) => ({ url: `/serviceaccounts/${queryArg.name}/tokens` }), + providesTags: ['ServiceAccount'], + }), + listSsoSetting: build.query({ + query: (queryArg) => ({ + url: `/ssosettings`, + params: { + allowWatchBookmarks: queryArg.allowWatchBookmarks, + continue: queryArg['continue'], + fieldSelector: queryArg.fieldSelector, + labelSelector: queryArg.labelSelector, + limit: queryArg.limit, + pretty: queryArg.pretty, + resourceVersion: queryArg.resourceVersion, + resourceVersionMatch: queryArg.resourceVersionMatch, + sendInitialEvents: queryArg.sendInitialEvents, + timeoutSeconds: queryArg.timeoutSeconds, + watch: queryArg.watch, + }, + }), + providesTags: ['SSOSetting'], + }), + getSsoSetting: build.query({ + query: (queryArg) => ({ + url: `/ssosettings/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['SSOSetting'], + }), + replaceSsoSetting: build.mutation({ + query: (queryArg) => ({ + url: `/ssosettings/${queryArg.name}`, + method: 'PUT', + body: queryArg.ssoSetting, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['SSOSetting'], + }), + deleteSsoSetting: build.mutation({ + query: (queryArg) => ({ + url: `/ssosettings/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['SSOSetting'], + }), + updateSsoSetting: build.mutation({ + query: (queryArg) => ({ + url: `/ssosettings/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['SSOSetting'], + }), + listTeamBinding: build.query({ + query: (queryArg) => ({ + url: `/teambindings`, + 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: ['TeamBinding'], + }), + createTeamBinding: build.mutation({ + query: (queryArg) => ({ + url: `/teambindings`, + method: 'POST', + body: queryArg.teamBinding, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TeamBinding'], + }), + deletecollectionTeamBinding: build.mutation< + DeletecollectionTeamBindingApiResponse, + DeletecollectionTeamBindingApiArg + >({ + query: (queryArg) => ({ + url: `/teambindings`, + 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: ['TeamBinding'], + }), + getTeamBinding: build.query({ + query: (queryArg) => ({ + url: `/teambindings/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['TeamBinding'], + }), + replaceTeamBinding: build.mutation({ + query: (queryArg) => ({ + url: `/teambindings/${queryArg.name}`, + method: 'PUT', + body: queryArg.teamBinding, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['TeamBinding'], + }), + deleteTeamBinding: build.mutation({ + query: (queryArg) => ({ + url: `/teambindings/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['TeamBinding'], + }), + updateTeamBinding: build.mutation({ + query: (queryArg) => ({ + url: `/teambindings/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['TeamBinding'], + }), + listTeam: build.query({ + query: (queryArg) => ({ + url: `/teams`, + 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: ['Team'], + }), + createTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams`, + method: 'POST', + body: queryArg.team, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Team'], + }), + deletecollectionTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams`, + 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: ['Team'], + }), + getTeam: build.query({ + query: (queryArg) => ({ + url: `/teams/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Team'], + }), + replaceTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.name}`, + method: 'PUT', + body: queryArg.team, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Team'], + }), + deleteTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['Team'], + }), + updateTeam: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Team'], + }), + getTeamMembers: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.name}/members` }), + providesTags: ['Team'], + }), + listUser: build.query({ + query: (queryArg) => ({ + url: `/users`, + 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: ['User'], + }), + createUser: build.mutation({ + query: (queryArg) => ({ + url: `/users`, + method: 'POST', + body: queryArg.user, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['User'], + }), + deletecollectionUser: build.mutation({ + query: (queryArg) => ({ + url: `/users`, + 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: ['User'], + }), + getUser: build.query({ + query: (queryArg) => ({ + url: `/users/${queryArg.name}`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['User'], + }), + replaceUser: build.mutation({ + query: (queryArg) => ({ + url: `/users/${queryArg.name}`, + method: 'PUT', + body: queryArg.user, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['User'], + }), + deleteUser: build.mutation({ + query: (queryArg) => ({ + url: `/users/${queryArg.name}`, + method: 'DELETE', + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + gracePeriodSeconds: queryArg.gracePeriodSeconds, + ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential, + orphanDependents: queryArg.orphanDependents, + propagationPolicy: queryArg.propagationPolicy, + }, + }), + invalidatesTags: ['User'], + }), + updateUser: build.mutation({ + query: (queryArg) => ({ + url: `/users/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['User'], + }), + getUserTeams: build.query({ + query: (queryArg) => ({ url: `/users/${queryArg.name}/teams` }), + providesTags: ['User'], + }), }), overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; export type GetDisplayMappingApiResponse = /** status 200 undefined */ DisplayList; export type GetDisplayMappingApiArg = { /** Display keys */ key: string[]; }; +export type ListServiceAccountApiResponse = /** status 200 OK */ ServiceAccountList; +export type ListServiceAccountApiArg = { + /** 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 CreateServiceAccountApiResponse = /** status 200 OK */ + | ServiceAccount + | /** status 201 Created */ ServiceAccount + | /** status 202 Accepted */ ServiceAccount; +export type CreateServiceAccountApiArg = { + /** 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; + serviceAccount: ServiceAccount; +}; +export type DeletecollectionServiceAccountApiResponse = /** status 200 OK */ Status; +export type DeletecollectionServiceAccountApiArg = { + /** 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 GetServiceAccountApiResponse = /** status 200 OK */ ServiceAccount; +export type GetServiceAccountApiArg = { + /** name of the ServiceAccount */ + name: 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 ReplaceServiceAccountApiResponse = /** status 200 OK */ + | ServiceAccount + | /** status 201 Created */ ServiceAccount; +export type ReplaceServiceAccountApiArg = { + /** name of the ServiceAccount */ + name: 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; + serviceAccount: ServiceAccount; +}; +export type DeleteServiceAccountApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteServiceAccountApiArg = { + /** name of the ServiceAccount */ + name: 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 UpdateServiceAccountApiResponse = /** status 200 OK */ + | ServiceAccount + | /** status 201 Created */ ServiceAccount; +export type UpdateServiceAccountApiArg = { + /** name of the ServiceAccount */ + name: 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; + patch: Patch; +}; +export type GetServiceAccountTokensApiResponse = /** status 200 OK */ ServiceAccountTokenList; +export type GetServiceAccountTokensApiArg = { + /** name of the ServiceAccountTokenList */ + name: string; +}; +export type ListSsoSettingApiResponse = /** status 200 OK */ SsoSettingList; +export type ListSsoSettingApiArg = { + /** 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; + /** 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; + /** 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 GetSsoSettingApiResponse = /** status 200 OK */ SsoSetting; +export type GetSsoSettingApiArg = { + /** name of the SSOSetting */ + name: 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 ReplaceSsoSettingApiResponse = /** status 200 OK */ SsoSetting | /** status 201 Created */ SsoSetting; +export type ReplaceSsoSettingApiArg = { + /** name of the SSOSetting */ + name: 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; + ssoSetting: SsoSetting; +}; +export type DeleteSsoSettingApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteSsoSettingApiArg = { + /** name of the SSOSetting */ + name: 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 UpdateSsoSettingApiResponse = /** status 200 OK */ SsoSetting | /** status 201 Created */ SsoSetting; +export type UpdateSsoSettingApiArg = { + /** name of the SSOSetting */ + name: 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; + patch: Patch; +}; +export type ListTeamBindingApiResponse = /** status 200 OK */ TeamBindingList; +export type ListTeamBindingApiArg = { + /** 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 CreateTeamBindingApiResponse = /** status 200 OK */ + | TeamBinding + | /** status 201 Created */ TeamBinding + | /** status 202 Accepted */ TeamBinding; +export type CreateTeamBindingApiArg = { + /** 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; + teamBinding: TeamBinding; +}; +export type DeletecollectionTeamBindingApiResponse = /** status 200 OK */ Status; +export type DeletecollectionTeamBindingApiArg = { + /** 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 GetTeamBindingApiResponse = /** status 200 OK */ TeamBinding; +export type GetTeamBindingApiArg = { + /** name of the TeamBinding */ + name: 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 ReplaceTeamBindingApiResponse = /** status 200 OK */ TeamBinding | /** status 201 Created */ TeamBinding; +export type ReplaceTeamBindingApiArg = { + /** name of the TeamBinding */ + name: 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; + teamBinding: TeamBinding; +}; +export type DeleteTeamBindingApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteTeamBindingApiArg = { + /** name of the TeamBinding */ + name: 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 UpdateTeamBindingApiResponse = /** status 200 OK */ TeamBinding | /** status 201 Created */ TeamBinding; +export type UpdateTeamBindingApiArg = { + /** name of the TeamBinding */ + name: 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; + patch: Patch; +}; +export type ListTeamApiResponse = /** status 200 OK */ TeamList; +export type ListTeamApiArg = { + /** 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 CreateTeamApiResponse = /** status 200 OK */ + | Team + | /** status 201 Created */ Team + | /** status 202 Accepted */ Team; +export type CreateTeamApiArg = { + /** 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; + team: Team; +}; +export type DeletecollectionTeamApiResponse = /** status 200 OK */ Status; +export type DeletecollectionTeamApiArg = { + /** 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 GetTeamApiResponse = /** status 200 OK */ Team; +export type GetTeamApiArg = { + /** name of the Team */ + name: 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 ReplaceTeamApiResponse = /** status 200 OK */ Team | /** status 201 Created */ Team; +export type ReplaceTeamApiArg = { + /** name of the Team */ + name: 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; + team: Team; +}; +export type DeleteTeamApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteTeamApiArg = { + /** name of the Team */ + name: 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 UpdateTeamApiResponse = /** status 200 OK */ Team | /** status 201 Created */ Team; +export type UpdateTeamApiArg = { + /** name of the Team */ + name: 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; + patch: Patch; +}; +export type GetTeamMembersApiResponse = /** status 200 OK */ TeamMemberList; +export type GetTeamMembersApiArg = { + /** name of the TeamMemberList */ + name: string; +}; +export type ListUserApiResponse = /** status 200 OK */ UserList; +export type ListUserApiArg = { + /** 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 CreateUserApiResponse = /** status 200 OK */ + | User + | /** status 201 Created */ User + | /** status 202 Accepted */ User; +export type CreateUserApiArg = { + /** 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; + user: User; +}; +export type DeletecollectionUserApiResponse = /** status 200 OK */ Status; +export type DeletecollectionUserApiArg = { + /** 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 GetUserApiResponse = /** status 200 OK */ User; +export type GetUserApiArg = { + /** name of the User */ + name: 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 ReplaceUserApiResponse = /** status 200 OK */ User | /** status 201 Created */ User; +export type ReplaceUserApiArg = { + /** name of the User */ + name: 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; + user: User; +}; +export type DeleteUserApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status; +export type DeleteUserApiArg = { + /** name of the User */ + name: 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 UpdateUserApiResponse = /** status 200 OK */ User | /** status 201 Created */ User; +export type UpdateUserApiArg = { + /** name of the User */ + name: 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; + patch: Patch; +}; +export type GetUserTeamsApiResponse = /** status 200 OK */ UserTeamList; +export type GetUserTeamsApiArg = { + /** name of the UserTeamList */ + name: string; +}; +export type ApiResource = { + /** 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 ApiResourceList = { + /** 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: ApiResource[]; +}; export type IdentityRef = { /** Name is the unique identifier for identity, guaranteed to be a unique value for the type within a namespace. */ name: string; @@ -62,3 +1412,377 @@ export type DisplayList = { kind?: string; metadata?: ListMeta; }; +export type Time = string; +export type FieldsV1 = object; +export type ManagedFieldsEntry = { + /** 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?: FieldsV1; + /** 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?: Time; +}; +export type OwnerReference = { + /** 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 ObjectMeta = { + /** 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?: Time; + /** 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?: Time; + /** 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?: ManagedFieldsEntry[]; + /** 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?: OwnerReference[]; + /** 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 ServiceAccountSpec = { + disabled: boolean; + plugin: string; + role: string; + title: string; +}; +export type ServiceAccount = { + /** 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: ObjectMeta; + /** Spec is the spec of the ServiceAccount */ + spec: ServiceAccountSpec; +}; +export type ServiceAccountList = { + /** 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: ServiceAccount[]; + /** 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: ListMeta; +}; +export type StatusCause = { + /** 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 StatusDetails = { + /** The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes. */ + causes?: StatusCause[]; + /** 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 Status = { + /** 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?: StatusDetails; + /** 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?: ListMeta; + /** 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 Patch = object; +export type ServiceAccountToken = { + created: Time; + expires?: Time; + lastUsed?: Time; + name?: string; + revoked?: boolean; +}; +export type ServiceAccountTokenList = { + /** 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: ServiceAccountToken[]; + /** 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?: ListMeta; +}; +export type Unstructured = { + [key: string]: any; +}; +export type SsoSettingSpec = { + settings: Unstructured; + /** Possible enum values: + - `"db"` + - `"system"` system is from config file, env or argument */ + source: 'db' | 'system'; +}; +export type SsoSetting = { + /** 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; + /** Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */ + metadata?: ObjectMeta; + spec?: SsoSettingSpec; +}; +export type SsoSettingList = { + /** 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: SsoSetting[]; + /** 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?: ListMeta; +}; +export type TeamBindingspecSubject = { + /** uid of the identity */ + name: string; +}; +export type TeamBindingTeamRef = { + /** Name is the unique identifier for a team. */ + name: string; +}; +export type TeamBindingSpec = { + external: boolean; + /** permission of the identity in the team */ + permission: string; + subject: TeamBindingspecSubject; + teamRef: TeamBindingTeamRef; +}; +export type TeamBinding = { + /** 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: ObjectMeta; + /** Spec is the spec of the TeamBinding */ + spec: TeamBindingSpec; +}; +export type TeamBindingList = { + /** 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: TeamBinding[]; + /** 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: ListMeta; +}; +export type TeamSpec = { + email: string; + externalUID: string; + provisioned: boolean; + title: string; +}; +export type Team = { + /** 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: ObjectMeta; + /** Spec is the spec of the Team */ + spec: TeamSpec; +}; +export type TeamList = { + /** 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: Team[]; + /** 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: ListMeta; +}; +export type TeamMember = { + /** AvatarURL is the url where we can get the avatar for identity */ + avatarURL?: string; + /** Display name for identity. */ + displayName: string; + /** External is set if member ship was synced from external IDP. */ + external?: boolean; + identity: IdentityRef; + /** InternalID is the legacy numeric id for identity, Deprecated: use the identityRef where possible */ + internalId?: number; + /** Permission member has in team. + + Possible enum values: + - `"admin"` + - `"member"` */ + permission?: 'admin' | 'member'; +}; +export type TeamMemberList = { + /** 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: TeamMember[]; + /** 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?: ListMeta; +}; +export type UserSpec = { + disabled: boolean; + email: string; + emailVerified: boolean; + grafanaAdmin: boolean; + login: string; + provisioned: boolean; + role: string; + title: string; +}; +export type User = { + /** 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: ObjectMeta; + /** Spec is the spec of the User */ + spec: UserSpec; +}; +export type UserList = { + /** 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: User[]; + /** 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: ListMeta; +}; +export type TeamRef = { + /** Name is the unique identifier for a team. */ + name?: string; +}; +export type UserTeam = { + /** Possible enum values: + - `"admin"` + - `"member"` */ + permission?: 'admin' | 'member'; + teamRef?: TeamRef; + title?: string; +}; +export type UserTeamList = { + /** 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: UserTeam[]; + /** 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?: ListMeta; +}; +export const { + useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, + useGetDisplayMappingQuery, + useLazyGetDisplayMappingQuery, + useListServiceAccountQuery, + useLazyListServiceAccountQuery, + useCreateServiceAccountMutation, + useDeletecollectionServiceAccountMutation, + useGetServiceAccountQuery, + useLazyGetServiceAccountQuery, + useReplaceServiceAccountMutation, + useDeleteServiceAccountMutation, + useUpdateServiceAccountMutation, + useGetServiceAccountTokensQuery, + useLazyGetServiceAccountTokensQuery, + useListSsoSettingQuery, + useLazyListSsoSettingQuery, + useGetSsoSettingQuery, + useLazyGetSsoSettingQuery, + useReplaceSsoSettingMutation, + useDeleteSsoSettingMutation, + useUpdateSsoSettingMutation, + useListTeamBindingQuery, + useLazyListTeamBindingQuery, + useCreateTeamBindingMutation, + useDeletecollectionTeamBindingMutation, + useGetTeamBindingQuery, + useLazyGetTeamBindingQuery, + useReplaceTeamBindingMutation, + useDeleteTeamBindingMutation, + useUpdateTeamBindingMutation, + useListTeamQuery, + useLazyListTeamQuery, + useCreateTeamMutation, + useDeletecollectionTeamMutation, + useGetTeamQuery, + useLazyGetTeamQuery, + useReplaceTeamMutation, + useDeleteTeamMutation, + useUpdateTeamMutation, + useGetTeamMembersQuery, + useLazyGetTeamMembersQuery, + useListUserQuery, + useLazyListUserQuery, + useCreateUserMutation, + useDeletecollectionUserMutation, + useGetUserQuery, + useLazyGetUserQuery, + useReplaceUserMutation, + useDeleteUserMutation, + useUpdateUserMutation, + useGetUserTeamsQuery, + useLazyGetUserTeamsQuery, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/baseAPI.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/baseAPI.ts new file mode 100644 index 00000000000..a9a3a0c84bb --- /dev/null +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/baseAPI.ts @@ -0,0 +1,9 @@ +import { createApi } from '@reduxjs/toolkit/query/react'; + +import { createBaseQuery } from '../createBaseQuery'; + +export const api = createApi({ + reducerPath: 'legacyAPI', + baseQuery: createBaseQuery({ baseURL: '/api' }), + endpoints: () => ({}), +}); diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts new file mode 100644 index 00000000000..bd6ff32f5bf --- /dev/null +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/endpoints.gen.ts @@ -0,0 +1,6963 @@ +import { api } from './baseAPI'; +export const addTagTypes = [ + 'enterprise', + 'access_control', + 'ldap_debug', + 'admin_ldap', + 'access_control_provisioning', + 'admin_provisioning', + 'admin', + 'admin_users', + 'quota', + 'annotations', + 'devices', + 'migrations', + 'convert_prometheus', + 'dashboards', + 'snapshots', + 'dashboard_public', + 'permissions', + 'versions', + 'datasources', + 'correlations', + 'health', + 'folders', + 'group_attribute_sync', + 'library_elements', + 'licensing', + 'saml', + 'org', + 'invites', + 'preferences', + 'orgs', + 'playlists', + 'query_history', + 'recording_rules', + 'reports', + 'search', + 'service_accounts', + 'signing_keys', + 'teams', + 'sync_team_groups', + 'signed_in_user', + 'user', + 'users', + 'provisioning', + 'sso_settings', +] as const; +const injectedRtkApi = api + .enhanceEndpoints({ + addTagTypes, + }) + .injectEndpoints({ + endpoints: (build) => ({ + searchResult: build.mutation({ + query: () => ({ url: `/access-control/assignments/search`, method: 'POST' }), + invalidatesTags: ['enterprise'], + }), + listRoles: build.query({ + query: (queryArg) => ({ + url: `/access-control/roles`, + params: { + delegatable: queryArg.delegatable, + includeHidden: queryArg.includeHidden, + }, + }), + providesTags: ['access_control', 'enterprise'], + }), + createRole: build.mutation({ + query: (queryArg) => ({ url: `/access-control/roles`, method: 'POST', body: queryArg.createRoleForm }), + invalidatesTags: ['access_control', 'enterprise'], + }), + deleteRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/roles/${queryArg.roleUid}`, + method: 'DELETE', + params: { + force: queryArg.force, + global: queryArg['global'], + }, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + getRole: build.query({ + query: (queryArg) => ({ url: `/access-control/roles/${queryArg.roleUid}` }), + providesTags: ['access_control', 'enterprise'], + }), + updateRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/roles/${queryArg.roleUid}`, + method: 'PUT', + body: queryArg.updateRoleCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + getRoleAssignments: build.query({ + query: (queryArg) => ({ url: `/access-control/roles/${queryArg.roleUid}/assignments` }), + providesTags: ['access_control', 'enterprise'], + }), + setRoleAssignments: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/roles/${queryArg.roleUid}/assignments`, + method: 'PUT', + body: queryArg.setRoleAssignmentsCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + getAccessControlStatus: build.query({ + query: () => ({ url: `/access-control/status` }), + providesTags: ['access_control', 'enterprise'], + }), + listTeamsRoles: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/teams/roles/search`, + method: 'POST', + body: queryArg.rolesSearchQuery, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + listTeamRoles: build.query({ + query: (queryArg) => ({ url: `/access-control/teams/${queryArg.teamId}/roles` }), + providesTags: ['access_control', 'enterprise'], + }), + addTeamRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/teams/${queryArg.teamId}/roles`, + method: 'POST', + body: queryArg.addTeamRoleCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + setTeamRoles: build.mutation({ + query: (queryArg) => ({ url: `/access-control/teams/${queryArg.teamId}/roles`, method: 'PUT' }), + invalidatesTags: ['access_control', 'enterprise'], + }), + removeTeamRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/teams/${queryArg.teamId}/roles/${queryArg.roleUid}`, + method: 'DELETE', + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + listUsersRoles: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/users/roles/search`, + method: 'POST', + body: queryArg.rolesSearchQuery, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + listUserRoles: build.query({ + query: (queryArg) => ({ url: `/access-control/users/${queryArg.userId}/roles` }), + providesTags: ['access_control', 'enterprise'], + }), + addUserRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/users/${queryArg.userId}/roles`, + method: 'POST', + body: queryArg.addUserRoleCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + setUserRoles: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/users/${queryArg.userId}/roles`, + method: 'PUT', + body: queryArg.setUserRolesCommand, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + removeUserRole: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/users/${queryArg.userId}/roles/${queryArg.roleUid}`, + method: 'DELETE', + params: { + global: queryArg['global'], + }, + }), + invalidatesTags: ['access_control', 'enterprise'], + }), + getResourceDescription: build.query({ + query: (queryArg) => ({ url: `/access-control/${queryArg.resource}/description` }), + providesTags: ['access_control'], + }), + getResourcePermissions: build.query({ + query: (queryArg) => ({ url: `/access-control/${queryArg.resource}/${queryArg.resourceId}` }), + providesTags: ['access_control'], + }), + setResourcePermissions: build.mutation({ + query: (queryArg) => ({ + url: `/access-control/${queryArg.resource}/${queryArg.resourceId}`, + method: 'POST', + body: queryArg.setPermissionsCommand, + }), + invalidatesTags: ['access_control'], + }), + setResourcePermissionsForBuiltInRole: build.mutation< + SetResourcePermissionsForBuiltInRoleApiResponse, + SetResourcePermissionsForBuiltInRoleApiArg + >({ + query: (queryArg) => ({ + url: `/access-control/${queryArg.resource}/${queryArg.resourceId}/builtInRoles/${queryArg.builtInRole}`, + method: 'POST', + body: queryArg.setPermissionCommand, + }), + invalidatesTags: ['access_control'], + }), + setResourcePermissionsForTeam: build.mutation< + SetResourcePermissionsForTeamApiResponse, + SetResourcePermissionsForTeamApiArg + >({ + query: (queryArg) => ({ + url: `/access-control/${queryArg.resource}/${queryArg.resourceId}/teams/${queryArg.teamId}`, + method: 'POST', + body: queryArg.setPermissionCommand, + }), + invalidatesTags: ['access_control'], + }), + setResourcePermissionsForUser: build.mutation< + SetResourcePermissionsForUserApiResponse, + SetResourcePermissionsForUserApiArg + >({ + query: (queryArg) => ({ + url: `/access-control/${queryArg.resource}/${queryArg.resourceId}/users/${queryArg.userId}`, + method: 'POST', + body: queryArg.setPermissionCommand, + }), + invalidatesTags: ['access_control'], + }), + getSyncStatus: build.query({ + query: () => ({ url: `/admin/ldap-sync-status` }), + providesTags: ['ldap_debug', 'enterprise'], + }), + reloadLdapCfg: build.mutation({ + query: () => ({ url: `/admin/ldap/reload`, method: 'POST' }), + invalidatesTags: ['admin_ldap'], + }), + getLdapStatus: build.query({ + query: () => ({ url: `/admin/ldap/status` }), + providesTags: ['admin_ldap'], + }), + postSyncUserWithLdap: build.mutation({ + query: (queryArg) => ({ url: `/admin/ldap/sync/${queryArg.userId}`, method: 'POST' }), + invalidatesTags: ['admin_ldap'], + }), + getUserFromLdap: build.query({ + query: (queryArg) => ({ url: `/admin/ldap/${queryArg.userName}` }), + providesTags: ['admin_ldap'], + }), + adminProvisioningReloadAccessControl: build.mutation< + AdminProvisioningReloadAccessControlApiResponse, + AdminProvisioningReloadAccessControlApiArg + >({ + query: () => ({ url: `/admin/provisioning/access-control/reload`, method: 'POST' }), + invalidatesTags: ['access_control_provisioning', 'enterprise'], + }), + adminProvisioningReloadDashboards: build.mutation< + AdminProvisioningReloadDashboardsApiResponse, + AdminProvisioningReloadDashboardsApiArg + >({ + query: () => ({ url: `/admin/provisioning/dashboards/reload`, method: 'POST' }), + invalidatesTags: ['admin_provisioning'], + }), + adminProvisioningReloadDatasources: build.mutation< + AdminProvisioningReloadDatasourcesApiResponse, + AdminProvisioningReloadDatasourcesApiArg + >({ + query: () => ({ url: `/admin/provisioning/datasources/reload`, method: 'POST' }), + invalidatesTags: ['admin_provisioning'], + }), + adminProvisioningReloadPlugins: build.mutation< + AdminProvisioningReloadPluginsApiResponse, + AdminProvisioningReloadPluginsApiArg + >({ + query: () => ({ url: `/admin/provisioning/plugins/reload`, method: 'POST' }), + invalidatesTags: ['admin_provisioning'], + }), + adminGetSettings: build.query({ + query: () => ({ url: `/admin/settings` }), + providesTags: ['admin'], + }), + adminGetStats: build.query({ + query: () => ({ url: `/admin/stats` }), + providesTags: ['admin'], + }), + adminCreateUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users`, method: 'POST', body: queryArg.adminCreateUserForm }), + invalidatesTags: ['admin_users'], + }), + adminDeleteUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}`, method: 'DELETE' }), + invalidatesTags: ['admin_users'], + }), + adminGetUserAuthTokens: build.query({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/auth-tokens` }), + providesTags: ['admin_users'], + }), + adminDisableUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/disable`, method: 'POST' }), + invalidatesTags: ['admin_users'], + }), + adminEnableUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/enable`, method: 'POST' }), + invalidatesTags: ['admin_users'], + }), + adminLogoutUser: build.mutation({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/logout`, method: 'POST' }), + invalidatesTags: ['admin_users'], + }), + adminUpdateUserPassword: build.mutation({ + query: (queryArg) => ({ + url: `/admin/users/${queryArg.userId}/password`, + method: 'PUT', + body: queryArg.adminUpdateUserPasswordForm, + }), + invalidatesTags: ['admin_users'], + }), + adminUpdateUserPermissions: build.mutation< + AdminUpdateUserPermissionsApiResponse, + AdminUpdateUserPermissionsApiArg + >({ + query: (queryArg) => ({ + url: `/admin/users/${queryArg.userId}/permissions`, + method: 'PUT', + body: queryArg.adminUpdateUserPermissionsForm, + }), + invalidatesTags: ['admin_users'], + }), + getUserQuota: build.query({ + query: (queryArg) => ({ url: `/admin/users/${queryArg.userId}/quotas` }), + providesTags: ['quota', 'admin_users'], + }), + updateUserQuota: build.mutation({ + query: (queryArg) => ({ + url: `/admin/users/${queryArg.userId}/quotas/${queryArg.quotaTarget}`, + method: 'PUT', + body: queryArg.updateQuotaCmd, + }), + invalidatesTags: ['quota', 'admin_users'], + }), + adminRevokeUserAuthToken: build.mutation({ + query: (queryArg) => ({ + url: `/admin/users/${queryArg.userId}/revoke-auth-token`, + method: 'POST', + body: queryArg.revokeAuthTokenCmd, + }), + invalidatesTags: ['admin_users'], + }), + getAnnotations: build.query({ + query: (queryArg) => ({ + url: `/annotations`, + params: { + from: queryArg['from'], + to: queryArg.to, + userId: queryArg.userId, + alertId: queryArg.alertId, + alertUID: queryArg.alertUid, + dashboardId: queryArg.dashboardId, + dashboardUID: queryArg.dashboardUid, + panelId: queryArg.panelId, + limit: queryArg.limit, + tags: queryArg.tags, + type: queryArg['type'], + matchAny: queryArg.matchAny, + }, + }), + providesTags: ['annotations'], + }), + postAnnotation: build.mutation({ + query: (queryArg) => ({ url: `/annotations`, method: 'POST', body: queryArg.postAnnotationsCmd }), + invalidatesTags: ['annotations'], + }), + postGraphiteAnnotation: build.mutation({ + query: (queryArg) => ({ + url: `/annotations/graphite`, + method: 'POST', + body: queryArg.postGraphiteAnnotationsCmd, + }), + invalidatesTags: ['annotations'], + }), + massDeleteAnnotations: build.mutation({ + query: (queryArg) => ({ + url: `/annotations/mass-delete`, + method: 'POST', + body: queryArg.massDeleteAnnotationsCmd, + }), + invalidatesTags: ['annotations'], + }), + getAnnotationTags: build.query({ + query: (queryArg) => ({ + url: `/annotations/tags`, + params: { + tag: queryArg.tag, + limit: queryArg.limit, + }, + }), + providesTags: ['annotations'], + }), + deleteAnnotationById: build.mutation({ + query: (queryArg) => ({ url: `/annotations/${queryArg.annotationId}`, method: 'DELETE' }), + invalidatesTags: ['annotations'], + }), + getAnnotationById: build.query({ + query: (queryArg) => ({ url: `/annotations/${queryArg.annotationId}` }), + providesTags: ['annotations'], + }), + patchAnnotation: build.mutation({ + query: (queryArg) => ({ + url: `/annotations/${queryArg.annotationId}`, + method: 'PATCH', + body: queryArg.patchAnnotationsCmd, + }), + invalidatesTags: ['annotations'], + }), + updateAnnotation: build.mutation({ + query: (queryArg) => ({ + url: `/annotations/${queryArg.annotationId}`, + method: 'PUT', + body: queryArg.updateAnnotationsCmd, + }), + invalidatesTags: ['annotations'], + }), + listDevices: build.query({ + query: () => ({ url: `/anonymous/devices` }), + providesTags: ['devices'], + }), + searchDevices: build.query({ + query: () => ({ url: `/anonymous/search` }), + providesTags: ['devices'], + }), + getSessionList: build.query({ + query: () => ({ url: `/cloudmigration/migration` }), + providesTags: ['migrations'], + }), + createSession: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration`, + method: 'POST', + body: queryArg.cloudMigrationSessionRequestDto, + }), + invalidatesTags: ['migrations'], + }), + deleteSession: build.mutation({ + query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['migrations'], + }), + getSession: build.query({ + query: (queryArg) => ({ url: `/cloudmigration/migration/${queryArg.uid}` }), + providesTags: ['migrations'], + }), + createSnapshot: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot`, + method: 'POST', + body: queryArg.createSnapshotRequestDto, + }), + invalidatesTags: ['migrations'], + }), + getSnapshot: build.query({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot/${queryArg.snapshotUid}`, + params: { + resultPage: queryArg.resultPage, + resultLimit: queryArg.resultLimit, + resultSortColumn: queryArg.resultSortColumn, + resultSortOrder: queryArg.resultSortOrder, + errorsOnly: queryArg.errorsOnly, + }, + }), + providesTags: ['migrations'], + }), + cancelSnapshot: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot/${queryArg.snapshotUid}/cancel`, + method: 'POST', + }), + invalidatesTags: ['migrations'], + }), + uploadSnapshot: build.mutation({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshot/${queryArg.snapshotUid}/upload`, + method: 'POST', + }), + invalidatesTags: ['migrations'], + }), + getShapshotList: build.query({ + query: (queryArg) => ({ + url: `/cloudmigration/migration/${queryArg.uid}/snapshots`, + params: { + page: queryArg.page, + limit: queryArg.limit, + sort: queryArg.sort, + }, + }), + providesTags: ['migrations'], + }), + getResourceDependencies: build.query({ + query: () => ({ url: `/cloudmigration/resources/dependencies` }), + providesTags: ['migrations'], + }), + getCloudMigrationToken: build.query({ + query: () => ({ url: `/cloudmigration/token` }), + providesTags: ['migrations'], + }), + createCloudMigrationToken: build.mutation({ + query: () => ({ url: `/cloudmigration/token`, method: 'POST' }), + invalidatesTags: ['migrations'], + }), + deleteCloudMigrationToken: build.mutation({ + query: (queryArg) => ({ url: `/cloudmigration/token/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['migrations'], + }), + routeConvertPrometheusCortexGetRules: build.query< + RouteConvertPrometheusCortexGetRulesApiResponse, + RouteConvertPrometheusCortexGetRulesApiArg + >({ + query: () => ({ url: `/convert/api/prom/rules` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexPostRuleGroups: build.mutation< + RouteConvertPrometheusCortexPostRuleGroupsApiResponse, + RouteConvertPrometheusCortexPostRuleGroupsApiArg + >({ + query: () => ({ url: `/convert/api/prom/rules`, method: 'POST' }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexDeleteNamespace: build.mutation< + RouteConvertPrometheusCortexDeleteNamespaceApiResponse, + RouteConvertPrometheusCortexDeleteNamespaceApiArg + >({ + query: (queryArg) => ({ url: `/convert/api/prom/rules/${queryArg.namespaceTitle}`, method: 'DELETE' }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexGetNamespace: build.query< + RouteConvertPrometheusCortexGetNamespaceApiResponse, + RouteConvertPrometheusCortexGetNamespaceApiArg + >({ + query: (queryArg) => ({ url: `/convert/api/prom/rules/${queryArg.namespaceTitle}` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexPostRuleGroup: build.mutation< + RouteConvertPrometheusCortexPostRuleGroupApiResponse, + RouteConvertPrometheusCortexPostRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/api/prom/rules/${queryArg.namespaceTitle}`, + method: 'POST', + body: queryArg.prometheusRuleGroup, + headers: { + 'x-grafana-alerting-datasource-uid': queryArg['x-grafana-alerting-datasource-uid'], + 'x-grafana-alerting-recording-rules-paused': queryArg['x-grafana-alerting-recording-rules-paused'], + 'x-grafana-alerting-alert-rules-paused': queryArg['x-grafana-alerting-alert-rules-paused'], + 'x-grafana-alerting-target-datasource-uid': queryArg['x-grafana-alerting-target-datasource-uid'], + 'x-grafana-alerting-folder-uid': queryArg['x-grafana-alerting-folder-uid'], + 'x-grafana-alerting-notification-receiver': queryArg['x-grafana-alerting-notification-receiver'], + }, + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexDeleteRuleGroup: build.mutation< + RouteConvertPrometheusCortexDeleteRuleGroupApiResponse, + RouteConvertPrometheusCortexDeleteRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/api/prom/rules/${queryArg.namespaceTitle}/${queryArg.group}`, + method: 'DELETE', + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusCortexGetRuleGroup: build.query< + RouteConvertPrometheusCortexGetRuleGroupApiResponse, + RouteConvertPrometheusCortexGetRuleGroupApiArg + >({ + query: (queryArg) => ({ url: `/convert/api/prom/rules/${queryArg.namespaceTitle}/${queryArg.group}` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusGetRules: build.query< + RouteConvertPrometheusGetRulesApiResponse, + RouteConvertPrometheusGetRulesApiArg + >({ + query: () => ({ url: `/convert/prometheus/config/v1/rules` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusPostRuleGroups: build.mutation< + RouteConvertPrometheusPostRuleGroupsApiResponse, + RouteConvertPrometheusPostRuleGroupsApiArg + >({ + query: () => ({ url: `/convert/prometheus/config/v1/rules`, method: 'POST' }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusDeleteNamespace: build.mutation< + RouteConvertPrometheusDeleteNamespaceApiResponse, + RouteConvertPrometheusDeleteNamespaceApiArg + >({ + query: (queryArg) => ({ + url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}`, + method: 'DELETE', + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusGetNamespace: build.query< + RouteConvertPrometheusGetNamespaceApiResponse, + RouteConvertPrometheusGetNamespaceApiArg + >({ + query: (queryArg) => ({ url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}` }), + providesTags: ['convert_prometheus'], + }), + routeConvertPrometheusPostRuleGroup: build.mutation< + RouteConvertPrometheusPostRuleGroupApiResponse, + RouteConvertPrometheusPostRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}`, + method: 'POST', + body: queryArg.prometheusRuleGroup, + headers: { + 'x-grafana-alerting-datasource-uid': queryArg['x-grafana-alerting-datasource-uid'], + 'x-grafana-alerting-recording-rules-paused': queryArg['x-grafana-alerting-recording-rules-paused'], + 'x-grafana-alerting-alert-rules-paused': queryArg['x-grafana-alerting-alert-rules-paused'], + 'x-grafana-alerting-target-datasource-uid': queryArg['x-grafana-alerting-target-datasource-uid'], + 'x-grafana-alerting-folder-uid': queryArg['x-grafana-alerting-folder-uid'], + 'x-grafana-alerting-notification-receiver': queryArg['x-grafana-alerting-notification-receiver'], + }, + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusDeleteRuleGroup: build.mutation< + RouteConvertPrometheusDeleteRuleGroupApiResponse, + RouteConvertPrometheusDeleteRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}/${queryArg.group}`, + method: 'DELETE', + }), + invalidatesTags: ['convert_prometheus'], + }), + routeConvertPrometheusGetRuleGroup: build.query< + RouteConvertPrometheusGetRuleGroupApiResponse, + RouteConvertPrometheusGetRuleGroupApiArg + >({ + query: (queryArg) => ({ + url: `/convert/prometheus/config/v1/rules/${queryArg.namespaceTitle}/${queryArg.group}`, + }), + providesTags: ['convert_prometheus'], + }), + searchDashboardSnapshots: build.query({ + query: (queryArg) => ({ + url: `/dashboard/snapshots`, + params: { + query: queryArg.query, + limit: queryArg.limit, + }, + }), + providesTags: ['dashboards', 'snapshots'], + }), + calculateDashboardDiff: build.mutation({ + query: (queryArg) => ({ url: `/dashboards/calculate-diff`, method: 'POST', body: queryArg.body }), + invalidatesTags: ['dashboards'], + }), + postDashboard: build.mutation({ + query: (queryArg) => ({ url: `/dashboards/db`, method: 'POST', body: queryArg.saveDashboardCommand }), + invalidatesTags: ['dashboards'], + }), + getHomeDashboard: build.query({ + query: () => ({ url: `/dashboards/home` }), + providesTags: ['dashboards'], + }), + importDashboard: build.mutation({ + query: (queryArg) => ({ url: `/dashboards/import`, method: 'POST', body: queryArg.importDashboardRequest }), + invalidatesTags: ['dashboards'], + }), + interpolateDashboard: build.mutation({ + query: () => ({ url: `/dashboards/interpolate`, method: 'POST' }), + invalidatesTags: ['dashboards'], + }), + listPublicDashboards: build.query({ + query: () => ({ url: `/dashboards/public-dashboards` }), + providesTags: ['dashboards', 'dashboard_public'], + }), + getDashboardTags: build.query({ + query: () => ({ url: `/dashboards/tags` }), + providesTags: ['dashboards'], + }), + getPublicDashboard: build.query({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.dashboardUid}/public-dashboards` }), + providesTags: ['dashboards', 'dashboard_public'], + }), + createPublicDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.dashboardUid}/public-dashboards`, + method: 'POST', + body: queryArg.publicDashboardDto, + }), + invalidatesTags: ['dashboards', 'dashboard_public'], + }), + deletePublicDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.dashboardUid}/public-dashboards/${queryArg.uid}`, + method: 'DELETE', + }), + invalidatesTags: ['dashboards', 'dashboard_public'], + }), + updatePublicDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.dashboardUid}/public-dashboards/${queryArg.uid}`, + method: 'PATCH', + body: queryArg.publicDashboardDto, + }), + invalidatesTags: ['dashboards', 'dashboard_public'], + }), + deleteDashboardByUid: build.mutation({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['dashboards'], + }), + getDashboardByUid: build.query({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.uid}` }), + providesTags: ['dashboards'], + }), + getDashboardPermissionsListByUid: build.query< + GetDashboardPermissionsListByUidApiResponse, + GetDashboardPermissionsListByUidApiArg + >({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.uid}/permissions` }), + providesTags: ['dashboards', 'permissions'], + }), + updateDashboardPermissionsByUid: build.mutation< + UpdateDashboardPermissionsByUidApiResponse, + UpdateDashboardPermissionsByUidApiArg + >({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.uid}/permissions`, + method: 'POST', + body: queryArg.updateDashboardAclCommand, + }), + invalidatesTags: ['dashboards', 'permissions'], + }), + restoreDashboardVersionByUid: build.mutation< + RestoreDashboardVersionByUidApiResponse, + RestoreDashboardVersionByUidApiArg + >({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.uid}/restore`, + method: 'POST', + body: queryArg.restoreDashboardVersionCommand, + }), + invalidatesTags: ['dashboards', 'versions'], + }), + getDashboardVersionsByUid: build.query({ + query: (queryArg) => ({ + url: `/dashboards/uid/${queryArg.uid}/versions`, + params: { + limit: queryArg.limit, + start: queryArg.start, + }, + }), + providesTags: ['dashboards', 'versions'], + }), + getDashboardVersionByUid: build.query({ + query: (queryArg) => ({ url: `/dashboards/uid/${queryArg.uid}/versions/${queryArg.dashboardVersionId}` }), + providesTags: ['dashboards', 'versions'], + }), + getDataSources: build.query({ + query: () => ({ url: `/datasources` }), + providesTags: ['datasources'], + }), + addDataSource: build.mutation({ + query: (queryArg) => ({ url: `/datasources`, method: 'POST', body: queryArg.addDataSourceCommand }), + invalidatesTags: ['datasources'], + }), + getCorrelations: build.query({ + query: (queryArg) => ({ + url: `/datasources/correlations`, + params: { + limit: queryArg.limit, + page: queryArg.page, + sourceUID: queryArg.sourceUid, + }, + }), + providesTags: ['datasources', 'correlations'], + }), + getDataSourceIdByName: build.query({ + query: (queryArg) => ({ url: `/datasources/id/${queryArg.name}` }), + providesTags: ['datasources'], + }), + deleteDataSourceByName: build.mutation({ + query: (queryArg) => ({ url: `/datasources/name/${queryArg.name}`, method: 'DELETE' }), + invalidatesTags: ['datasources'], + }), + getDataSourceByName: build.query({ + query: (queryArg) => ({ url: `/datasources/name/${queryArg.name}` }), + providesTags: ['datasources'], + }), + datasourceProxyDeleteByUiDcalls: build.mutation< + DatasourceProxyDeleteByUiDcallsApiResponse, + DatasourceProxyDeleteByUiDcallsApiArg + >({ + query: (queryArg) => ({ + url: `/datasources/proxy/uid/${queryArg.uid}/${queryArg.datasourceProxyRoute}`, + method: 'DELETE', + }), + invalidatesTags: ['datasources'], + }), + datasourceProxyGetByUiDcalls: build.query< + DatasourceProxyGetByUiDcallsApiResponse, + DatasourceProxyGetByUiDcallsApiArg + >({ + query: (queryArg) => ({ url: `/datasources/proxy/uid/${queryArg.uid}/${queryArg.datasourceProxyRoute}` }), + providesTags: ['datasources'], + }), + datasourceProxyPostByUiDcalls: build.mutation< + DatasourceProxyPostByUiDcallsApiResponse, + DatasourceProxyPostByUiDcallsApiArg + >({ + query: (queryArg) => ({ + url: `/datasources/proxy/uid/${queryArg.uid}/${queryArg.datasourceProxyRoute}`, + method: 'POST', + body: queryArg.body, + }), + invalidatesTags: ['datasources'], + }), + getCorrelationsBySourceUid: build.query({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.sourceUid}/correlations` }), + providesTags: ['datasources', 'correlations'], + }), + createCorrelation: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.sourceUid}/correlations`, + method: 'POST', + body: queryArg.createCorrelationCommand, + }), + invalidatesTags: ['datasources', 'correlations'], + }), + getCorrelation: build.query({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.sourceUid}/correlations/${queryArg.correlationUid}`, + }), + providesTags: ['datasources', 'correlations'], + }), + updateCorrelation: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.sourceUid}/correlations/${queryArg.correlationUid}`, + method: 'PATCH', + body: queryArg.updateCorrelationCommand, + }), + invalidatesTags: ['datasources', 'correlations'], + }), + deleteDataSourceByUid: build.mutation({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['datasources'], + }), + getDataSourceByUid: build.query({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}` }), + providesTags: ['datasources'], + }), + updateDataSourceByUid: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.uid}`, + method: 'PUT', + body: queryArg.updateDataSourceCommand, + }), + invalidatesTags: ['datasources'], + }), + deleteCorrelation: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.uid}/correlations/${queryArg.correlationUid}`, + method: 'DELETE', + }), + invalidatesTags: ['datasources', 'correlations'], + }), + checkDatasourceHealthWithUid: build.query< + CheckDatasourceHealthWithUidApiResponse, + CheckDatasourceHealthWithUidApiArg + >({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}/health` }), + providesTags: ['datasources', 'health'], + }), + getTeamLbacRulesApi: build.query({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}/lbac/teams` }), + providesTags: ['enterprise'], + }), + updateTeamLbacRulesApi: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/uid/${queryArg.uid}/lbac/teams`, + method: 'PUT', + body: queryArg.updateTeamLbacCommand, + }), + invalidatesTags: ['enterprise'], + }), + callDatasourceResourceWithUid: build.query< + CallDatasourceResourceWithUidApiResponse, + CallDatasourceResourceWithUidApiArg + >({ + query: (queryArg) => ({ url: `/datasources/uid/${queryArg.uid}/resources/${queryArg.datasourceProxyRoute}` }), + providesTags: ['datasources'], + }), + getDataSourceCacheConfig: build.query({ + query: (queryArg) => ({ url: `/datasources/${queryArg.dataSourceUid}/cache` }), + providesTags: ['enterprise'], + }), + setDataSourceCacheConfig: build.mutation({ + query: (queryArg) => ({ + url: `/datasources/${queryArg.dataSourceUid}/cache`, + method: 'POST', + body: queryArg.cacheConfigSetter, + }), + invalidatesTags: ['enterprise'], + }), + cleanDataSourceCache: build.mutation({ + query: (queryArg) => ({ url: `/datasources/${queryArg.dataSourceUid}/cache/clean`, method: 'POST' }), + invalidatesTags: ['enterprise'], + }), + disableDataSourceCache: build.mutation({ + query: (queryArg) => ({ url: `/datasources/${queryArg.dataSourceUid}/cache/disable`, method: 'POST' }), + invalidatesTags: ['enterprise'], + }), + enableDataSourceCache: build.mutation({ + query: (queryArg) => ({ url: `/datasources/${queryArg.dataSourceUid}/cache/enable`, method: 'POST' }), + invalidatesTags: ['enterprise'], + }), + queryMetricsWithExpressions: build.mutation< + QueryMetricsWithExpressionsApiResponse, + QueryMetricsWithExpressionsApiArg + >({ + query: (queryArg) => ({ url: `/ds/query`, method: 'POST', body: queryArg.metricRequest }), + invalidatesTags: ['datasources'], + }), + getFolders: build.query({ + query: (queryArg) => ({ + url: `/folders`, + params: { + limit: queryArg.limit, + page: queryArg.page, + parentUid: queryArg.parentUid, + permission: queryArg.permission, + }, + }), + providesTags: ['folders'], + }), + createFolder: build.mutation({ + query: (queryArg) => ({ url: `/folders`, method: 'POST', body: queryArg.createFolderCommand }), + invalidatesTags: ['folders'], + }), + deleteFolder: build.mutation({ + query: (queryArg) => ({ + url: `/folders/${queryArg.folderUid}`, + method: 'DELETE', + params: { + forceDeleteRules: queryArg.forceDeleteRules, + }, + }), + invalidatesTags: ['folders'], + }), + getFolderByUid: build.query({ + query: (queryArg) => ({ url: `/folders/${queryArg.folderUid}` }), + providesTags: ['folders'], + }), + updateFolder: build.mutation({ + query: (queryArg) => ({ + url: `/folders/${queryArg.folderUid}`, + method: 'PUT', + body: queryArg.updateFolderCommand, + }), + invalidatesTags: ['folders'], + }), + getFolderDescendantCounts: build.query({ + query: (queryArg) => ({ url: `/folders/${queryArg.folderUid}/counts` }), + providesTags: ['folders'], + }), + moveFolder: build.mutation({ + query: (queryArg) => ({ + url: `/folders/${queryArg.folderUid}/move`, + method: 'POST', + body: queryArg.moveFolderCommand, + }), + invalidatesTags: ['folders'], + }), + getFolderPermissionList: build.query({ + query: (queryArg) => ({ url: `/folders/${queryArg.folderUid}/permissions` }), + providesTags: ['folders', 'permissions'], + }), + updateFolderPermissions: build.mutation({ + query: (queryArg) => ({ + url: `/folders/${queryArg.folderUid}/permissions`, + method: 'POST', + body: queryArg.updateDashboardAclCommand, + }), + invalidatesTags: ['folders', 'permissions'], + }), + getMappedGroups: build.query({ + query: () => ({ url: `/groupsync/groups` }), + providesTags: ['group_attribute_sync', 'enterprise'], + }), + deleteGroupMappings: build.mutation({ + query: (queryArg) => ({ url: `/groupsync/groups/${queryArg.groupId}`, method: 'DELETE' }), + invalidatesTags: ['group_attribute_sync', 'enterprise'], + }), + createGroupMappings: build.mutation({ + query: (queryArg) => ({ + url: `/groupsync/groups/${queryArg.groupId}`, + method: 'POST', + body: queryArg.groupAttributes, + }), + invalidatesTags: ['group_attribute_sync', 'enterprise'], + }), + updateGroupMappings: build.mutation({ + query: (queryArg) => ({ + url: `/groupsync/groups/${queryArg.groupId}`, + method: 'PUT', + body: queryArg.groupAttributes, + }), + invalidatesTags: ['group_attribute_sync', 'enterprise'], + }), + getGroupRoles: build.query({ + query: (queryArg) => ({ url: `/groupsync/groups/${queryArg.groupId}/roles` }), + providesTags: ['group_attribute_sync', 'enterprise'], + }), + getHealth: build.query({ + query: () => ({ url: `/health` }), + providesTags: ['health'], + }), + getLibraryElements: build.query({ + query: (queryArg) => ({ + url: `/library-elements`, + params: { + searchString: queryArg.searchString, + kind: queryArg.kind, + sortDirection: queryArg.sortDirection, + typeFilter: queryArg.typeFilter, + excludeUid: queryArg.excludeUid, + folderFilter: queryArg.folderFilter, + perPage: queryArg.perPage, + page: queryArg.page, + }, + }), + providesTags: ['library_elements'], + }), + createLibraryElement: build.mutation({ + query: (queryArg) => ({ url: `/library-elements`, method: 'POST', body: queryArg.createLibraryElementCommand }), + invalidatesTags: ['library_elements'], + }), + getLibraryElementByName: build.query({ + query: (queryArg) => ({ url: `/library-elements/name/${queryArg.libraryElementName}` }), + providesTags: ['library_elements'], + }), + deleteLibraryElementByUid: build.mutation({ + query: (queryArg) => ({ url: `/library-elements/${queryArg.libraryElementUid}`, method: 'DELETE' }), + invalidatesTags: ['library_elements'], + }), + getLibraryElementByUid: build.query({ + query: (queryArg) => ({ url: `/library-elements/${queryArg.libraryElementUid}` }), + providesTags: ['library_elements'], + }), + updateLibraryElement: build.mutation({ + query: (queryArg) => ({ + url: `/library-elements/${queryArg.libraryElementUid}`, + method: 'PATCH', + body: queryArg.patchLibraryElementCommand, + }), + invalidatesTags: ['library_elements'], + }), + getLibraryElementConnections: build.query< + GetLibraryElementConnectionsApiResponse, + GetLibraryElementConnectionsApiArg + >({ + query: (queryArg) => ({ url: `/library-elements/${queryArg.libraryElementUid}/connections/` }), + providesTags: ['library_elements'], + }), + getStatus: build.query({ + query: () => ({ url: `/licensing/check` }), + providesTags: ['licensing', 'enterprise'], + }), + refreshLicenseStats: build.query({ + query: () => ({ url: `/licensing/refresh-stats` }), + providesTags: ['licensing', 'enterprise'], + }), + deleteLicenseToken: build.mutation({ + query: (queryArg) => ({ url: `/licensing/token`, method: 'DELETE', body: queryArg.deleteTokenCommand }), + invalidatesTags: ['licensing', 'enterprise'], + }), + getLicenseToken: build.query({ + query: () => ({ url: `/licensing/token` }), + providesTags: ['licensing', 'enterprise'], + }), + postLicenseToken: build.mutation({ + query: (queryArg) => ({ url: `/licensing/token`, method: 'POST', body: queryArg.deleteTokenCommand }), + invalidatesTags: ['licensing', 'enterprise'], + }), + postRenewLicenseToken: build.mutation({ + query: (queryArg) => ({ url: `/licensing/token/renew`, method: 'POST', body: queryArg.body }), + invalidatesTags: ['licensing', 'enterprise'], + }), + getSamlLogout: build.query({ + query: () => ({ url: `/logout/saml` }), + providesTags: ['saml', 'enterprise'], + }), + getCurrentOrg: build.query({ + query: () => ({ url: `/org` }), + providesTags: ['org'], + }), + updateCurrentOrg: build.mutation({ + query: (queryArg) => ({ url: `/org`, method: 'PUT', body: queryArg.updateOrgForm }), + invalidatesTags: ['org'], + }), + updateCurrentOrgAddress: build.mutation({ + query: (queryArg) => ({ url: `/org/address`, method: 'PUT', body: queryArg.updateOrgAddressForm }), + invalidatesTags: ['org'], + }), + getPendingOrgInvites: build.query({ + query: () => ({ url: `/org/invites` }), + providesTags: ['org', 'invites'], + }), + addOrgInvite: build.mutation({ + query: (queryArg) => ({ url: `/org/invites`, method: 'POST', body: queryArg.addInviteForm }), + invalidatesTags: ['org', 'invites'], + }), + revokeInvite: build.mutation({ + query: (queryArg) => ({ url: `/org/invites/${queryArg.invitationCode}/revoke`, method: 'DELETE' }), + invalidatesTags: ['org', 'invites'], + }), + getOrgPreferences: build.query({ + query: () => ({ url: `/org/preferences` }), + providesTags: ['org', 'preferences'], + }), + patchOrgPreferences: build.mutation({ + query: (queryArg) => ({ url: `/org/preferences`, method: 'PATCH', body: queryArg.patchPrefsCmd }), + invalidatesTags: ['org', 'preferences'], + }), + updateOrgPreferences: build.mutation({ + query: (queryArg) => ({ url: `/org/preferences`, method: 'PUT', body: queryArg.updatePrefsCmd }), + invalidatesTags: ['org', 'preferences'], + }), + getCurrentOrgQuota: build.query({ + query: () => ({ url: `/org/quotas` }), + providesTags: ['quota', 'org'], + }), + getOrgUsersForCurrentOrg: build.query({ + query: (queryArg) => ({ + url: `/org/users`, + params: { + query: queryArg.query, + limit: queryArg.limit, + }, + }), + providesTags: ['org'], + }), + addOrgUserToCurrentOrg: build.mutation({ + query: (queryArg) => ({ url: `/org/users`, method: 'POST', body: queryArg.addOrgUserCommand }), + invalidatesTags: ['org'], + }), + getOrgUsersForCurrentOrgLookup: build.query< + GetOrgUsersForCurrentOrgLookupApiResponse, + GetOrgUsersForCurrentOrgLookupApiArg + >({ + query: (queryArg) => ({ + url: `/org/users/lookup`, + params: { + query: queryArg.query, + limit: queryArg.limit, + }, + }), + providesTags: ['org'], + }), + removeOrgUserForCurrentOrg: build.mutation< + RemoveOrgUserForCurrentOrgApiResponse, + RemoveOrgUserForCurrentOrgApiArg + >({ + query: (queryArg) => ({ url: `/org/users/${queryArg.userId}`, method: 'DELETE' }), + invalidatesTags: ['org'], + }), + updateOrgUserForCurrentOrg: build.mutation< + UpdateOrgUserForCurrentOrgApiResponse, + UpdateOrgUserForCurrentOrgApiArg + >({ + query: (queryArg) => ({ + url: `/org/users/${queryArg.userId}`, + method: 'PATCH', + body: queryArg.updateOrgUserCommand, + }), + invalidatesTags: ['org'], + }), + searchOrgs: build.query({ + query: (queryArg) => ({ + url: `/orgs`, + params: { + page: queryArg.page, + perpage: queryArg.perpage, + name: queryArg.name, + query: queryArg.query, + }, + }), + providesTags: ['orgs'], + }), + createOrg: build.mutation({ + query: (queryArg) => ({ url: `/orgs`, method: 'POST', body: queryArg.createOrgCommand }), + invalidatesTags: ['orgs'], + }), + getOrgByName: build.query({ + query: (queryArg) => ({ url: `/orgs/name/${queryArg.orgName}` }), + providesTags: ['orgs'], + }), + deleteOrgById: build.mutation({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}`, method: 'DELETE' }), + invalidatesTags: ['orgs'], + }), + getOrgById: build.query({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}` }), + providesTags: ['orgs'], + }), + updateOrg: build.mutation({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}`, method: 'PUT', body: queryArg.updateOrgForm }), + invalidatesTags: ['orgs'], + }), + updateOrgAddress: build.mutation({ + query: (queryArg) => ({ + url: `/orgs/${queryArg.orgId}/address`, + method: 'PUT', + body: queryArg.updateOrgAddressForm, + }), + invalidatesTags: ['orgs'], + }), + getOrgQuota: build.query({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}/quotas` }), + providesTags: ['quota', 'orgs'], + }), + updateOrgQuota: build.mutation({ + query: (queryArg) => ({ + url: `/orgs/${queryArg.orgId}/quotas/${queryArg.quotaTarget}`, + method: 'PUT', + body: queryArg.updateQuotaCmd, + }), + invalidatesTags: ['quota', 'orgs'], + }), + getOrgUsers: build.query({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}/users` }), + providesTags: ['orgs'], + }), + addOrgUser: build.mutation({ + query: (queryArg) => ({ + url: `/orgs/${queryArg.orgId}/users`, + method: 'POST', + body: queryArg.addOrgUserCommand, + }), + invalidatesTags: ['orgs'], + }), + searchOrgUsers: build.query({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}/users/search` }), + providesTags: ['orgs'], + }), + removeOrgUser: build.mutation({ + query: (queryArg) => ({ url: `/orgs/${queryArg.orgId}/users/${queryArg.userId}`, method: 'DELETE' }), + invalidatesTags: ['orgs'], + }), + updateOrgUser: build.mutation({ + query: (queryArg) => ({ + url: `/orgs/${queryArg.orgId}/users/${queryArg.userId}`, + method: 'PATCH', + body: queryArg.updateOrgUserCommand, + }), + invalidatesTags: ['orgs'], + }), + searchPlaylists: build.query({ + query: (queryArg) => ({ + url: `/playlists`, + params: { + query: queryArg.query, + limit: queryArg.limit, + }, + }), + providesTags: ['playlists'], + }), + createPlaylist: build.mutation({ + query: (queryArg) => ({ url: `/playlists`, method: 'POST', body: queryArg.createPlaylistCommand }), + invalidatesTags: ['playlists'], + }), + deletePlaylist: build.mutation({ + query: (queryArg) => ({ url: `/playlists/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['playlists'], + }), + getPlaylist: build.query({ + query: (queryArg) => ({ url: `/playlists/${queryArg.uid}` }), + providesTags: ['playlists'], + }), + updatePlaylist: build.mutation({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.uid}`, + method: 'PUT', + body: queryArg.updatePlaylistCommand, + }), + invalidatesTags: ['playlists'], + }), + getPlaylistItems: build.query({ + query: (queryArg) => ({ url: `/playlists/${queryArg.uid}/items` }), + providesTags: ['playlists'], + }), + viewPublicDashboard: build.query({ + query: (queryArg) => ({ url: `/public/dashboards/${queryArg.accessToken}` }), + providesTags: ['dashboards', 'dashboard_public'], + }), + getPublicAnnotations: build.query({ + query: (queryArg) => ({ url: `/public/dashboards/${queryArg.accessToken}/annotations` }), + providesTags: ['dashboards', 'annotations', 'dashboard_public'], + }), + queryPublicDashboard: build.mutation({ + query: (queryArg) => ({ + url: `/public/dashboards/${queryArg.accessToken}/panels/${queryArg.panelId}/query`, + method: 'POST', + }), + invalidatesTags: ['dashboards', 'dashboard_public'], + }), + searchQueries: build.query({ + query: (queryArg) => ({ + url: `/query-history`, + params: { + datasourceUid: queryArg.datasourceUid, + searchString: queryArg.searchString, + onlyStarred: queryArg.onlyStarred, + sort: queryArg.sort, + page: queryArg.page, + limit: queryArg.limit, + from: queryArg['from'], + to: queryArg.to, + }, + }), + providesTags: ['query_history'], + }), + createQuery: build.mutation({ + query: (queryArg) => ({ + url: `/query-history`, + method: 'POST', + body: queryArg.createQueryInQueryHistoryCommand, + }), + invalidatesTags: ['query_history'], + }), + unstarQuery: build.mutation({ + query: (queryArg) => ({ url: `/query-history/star/${queryArg.queryHistoryUid}`, method: 'DELETE' }), + invalidatesTags: ['query_history'], + }), + starQuery: build.mutation({ + query: (queryArg) => ({ url: `/query-history/star/${queryArg.queryHistoryUid}`, method: 'POST' }), + invalidatesTags: ['query_history'], + }), + deleteQuery: build.mutation({ + query: (queryArg) => ({ url: `/query-history/${queryArg.queryHistoryUid}`, method: 'DELETE' }), + invalidatesTags: ['query_history'], + }), + patchQueryComment: build.mutation({ + query: (queryArg) => ({ + url: `/query-history/${queryArg.queryHistoryUid}`, + method: 'PATCH', + body: queryArg.patchQueryCommentInQueryHistoryCommand, + }), + invalidatesTags: ['query_history'], + }), + listRecordingRules: build.query({ + query: () => ({ url: `/recording-rules` }), + providesTags: ['recording_rules', 'enterprise'], + }), + createRecordingRule: build.mutation({ + query: (queryArg) => ({ url: `/recording-rules`, method: 'POST', body: queryArg.recordingRuleJson }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + updateRecordingRule: build.mutation({ + query: (queryArg) => ({ url: `/recording-rules`, method: 'PUT', body: queryArg.recordingRuleJson }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + testCreateRecordingRule: build.mutation({ + query: (queryArg) => ({ url: `/recording-rules/test`, method: 'POST', body: queryArg.recordingRuleJson }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + deleteRecordingRuleWriteTarget: build.mutation< + DeleteRecordingRuleWriteTargetApiResponse, + DeleteRecordingRuleWriteTargetApiArg + >({ + query: () => ({ url: `/recording-rules/writer`, method: 'DELETE' }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + getRecordingRuleWriteTarget: build.query< + GetRecordingRuleWriteTargetApiResponse, + GetRecordingRuleWriteTargetApiArg + >({ + query: () => ({ url: `/recording-rules/writer` }), + providesTags: ['recording_rules', 'enterprise'], + }), + createRecordingRuleWriteTarget: build.mutation< + CreateRecordingRuleWriteTargetApiResponse, + CreateRecordingRuleWriteTargetApiArg + >({ + query: (queryArg) => ({ + url: `/recording-rules/writer`, + method: 'POST', + body: queryArg.prometheusRemoteWriteTargetJson, + }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + deleteRecordingRule: build.mutation({ + query: (queryArg) => ({ url: `/recording-rules/${queryArg.recordingRuleId}`, method: 'DELETE' }), + invalidatesTags: ['recording_rules', 'enterprise'], + }), + getReports: build.query({ + query: () => ({ url: `/reports` }), + providesTags: ['reports', 'enterprise'], + }), + createReport: build.mutation({ + query: (queryArg) => ({ url: `/reports`, method: 'POST', body: queryArg.createOrUpdateReport }), + invalidatesTags: ['reports', 'enterprise'], + }), + getReportsByDashboardUid: build.query({ + query: (queryArg) => ({ url: `/reports/dashboards/${queryArg.uid}` }), + providesTags: ['reports', 'enterprise'], + }), + sendReport: build.mutation({ + query: (queryArg) => ({ url: `/reports/email`, method: 'POST', body: queryArg.reportEmail }), + invalidatesTags: ['reports', 'enterprise'], + }), + getSettingsImage: build.query({ + query: () => ({ url: `/reports/images/:image` }), + providesTags: ['reports', 'enterprise'], + }), + renderReportCsVs: build.query({ + query: (queryArg) => ({ + url: `/reports/render/csvs`, + params: { + dashboards: queryArg.dashboards, + title: queryArg.title, + }, + }), + providesTags: ['reports', 'enterprise'], + }), + renderReportPdFs: build.query({ + query: (queryArg) => ({ + url: `/reports/render/pdfs`, + params: { + dashboards: queryArg.dashboards, + orientation: queryArg.orientation, + layout: queryArg.layout, + title: queryArg.title, + scaleFactor: queryArg.scaleFactor, + includeTables: queryArg.includeTables, + }, + }), + providesTags: ['reports', 'enterprise'], + }), + getReportSettings: build.query({ + query: () => ({ url: `/reports/settings` }), + providesTags: ['reports', 'enterprise'], + }), + saveReportSettings: build.mutation({ + query: (queryArg) => ({ url: `/reports/settings`, method: 'POST', body: queryArg.reportSettings }), + invalidatesTags: ['reports', 'enterprise'], + }), + sendTestEmail: build.mutation({ + query: (queryArg) => ({ url: `/reports/test-email`, method: 'POST', body: queryArg.createOrUpdateReport }), + invalidatesTags: ['reports', 'enterprise'], + }), + postAcs: build.mutation({ + query: (queryArg) => ({ + url: `/saml/acs`, + method: 'POST', + params: { + RelayState: queryArg.relayState, + }, + }), + invalidatesTags: ['saml', 'enterprise'], + }), + getMetadata: build.query({ + query: () => ({ url: `/saml/metadata` }), + providesTags: ['saml', 'enterprise'], + }), + getSlo: build.query({ + query: () => ({ url: `/saml/slo` }), + providesTags: ['saml', 'enterprise'], + }), + postSlo: build.mutation({ + query: (queryArg) => ({ + url: `/saml/slo`, + method: 'POST', + params: { + SAMLRequest: queryArg.samlRequest, + SAMLResponse: queryArg.samlResponse, + }, + }), + invalidatesTags: ['saml', 'enterprise'], + }), + search: build.query({ + query: (queryArg) => ({ + url: `/search`, + params: { + query: queryArg.query, + tag: queryArg.tag, + type: queryArg['type'], + dashboardIds: queryArg.dashboardIds, + dashboardUIDs: queryArg.dashboardUiDs, + folderIds: queryArg.folderIds, + folderUIDs: queryArg.folderUiDs, + starred: queryArg.starred, + limit: queryArg.limit, + page: queryArg.page, + permission: queryArg.permission, + sort: queryArg.sort, + deleted: queryArg.deleted, + }, + }), + providesTags: ['search'], + }), + listSortOptions: build.query({ + query: () => ({ url: `/search/sorting` }), + providesTags: ['search'], + }), + createServiceAccount: build.mutation({ + query: (queryArg) => ({ url: `/serviceaccounts`, method: 'POST', body: queryArg.createServiceAccountForm }), + invalidatesTags: ['service_accounts'], + }), + searchOrgServiceAccountsWithPaging: build.query< + SearchOrgServiceAccountsWithPagingApiResponse, + SearchOrgServiceAccountsWithPagingApiArg + >({ + query: (queryArg) => ({ + url: `/serviceaccounts/search`, + params: { + Disabled: queryArg.disabled, + expiredTokens: queryArg.expiredTokens, + query: queryArg.query, + perpage: queryArg.perpage, + page: queryArg.page, + }, + }), + providesTags: ['service_accounts'], + }), + deleteServiceAccount: build.mutation({ + query: (queryArg) => ({ url: `/serviceaccounts/${queryArg.serviceAccountId}`, method: 'DELETE' }), + invalidatesTags: ['service_accounts'], + }), + retrieveServiceAccount: build.query({ + query: (queryArg) => ({ url: `/serviceaccounts/${queryArg.serviceAccountId}` }), + providesTags: ['service_accounts'], + }), + updateServiceAccount: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.serviceAccountId}`, + method: 'PATCH', + body: queryArg.updateServiceAccountForm, + }), + invalidatesTags: ['service_accounts'], + }), + listTokens: build.query({ + query: (queryArg) => ({ url: `/serviceaccounts/${queryArg.serviceAccountId}/tokens` }), + providesTags: ['service_accounts'], + }), + createToken: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.serviceAccountId}/tokens`, + method: 'POST', + body: queryArg.addServiceAccountTokenCommand, + }), + invalidatesTags: ['service_accounts'], + }), + deleteToken: build.mutation({ + query: (queryArg) => ({ + url: `/serviceaccounts/${queryArg.serviceAccountId}/tokens/${queryArg.tokenId}`, + method: 'DELETE', + }), + invalidatesTags: ['service_accounts'], + }), + retrieveJwks: build.query({ + query: () => ({ url: `/signing-keys/keys` }), + providesTags: ['signing_keys'], + }), + getSharingOptions: build.query({ + query: () => ({ url: `/snapshot/shared-options` }), + providesTags: ['snapshots'], + }), + createDashboardSnapshot: build.mutation({ + query: (queryArg) => ({ url: `/snapshots`, method: 'POST', body: queryArg.createDashboardSnapshotCommand }), + invalidatesTags: ['dashboards', 'snapshots'], + }), + deleteDashboardSnapshotByDeleteKey: build.query< + DeleteDashboardSnapshotByDeleteKeyApiResponse, + DeleteDashboardSnapshotByDeleteKeyApiArg + >({ + query: (queryArg) => ({ url: `/snapshots-delete/${queryArg.deleteKey}` }), + providesTags: ['dashboards', 'snapshots'], + }), + deleteDashboardSnapshot: build.mutation({ + query: (queryArg) => ({ url: `/snapshots/${queryArg.key}`, method: 'DELETE' }), + invalidatesTags: ['dashboards', 'snapshots'], + }), + getDashboardSnapshot: build.query({ + query: (queryArg) => ({ url: `/snapshots/${queryArg.key}` }), + providesTags: ['dashboards', 'snapshots'], + }), + createTeam: build.mutation({ + query: (queryArg) => ({ url: `/teams`, method: 'POST', body: queryArg.createTeamCommand }), + invalidatesTags: ['teams'], + }), + searchTeams: build.query({ + query: (queryArg) => ({ + url: `/teams/search`, + params: { + page: queryArg.page, + perpage: queryArg.perpage, + name: queryArg.name, + query: queryArg.query, + }, + }), + providesTags: ['teams'], + }), + removeTeamGroupApiQuery: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/groups`, + method: 'DELETE', + params: { + groupId: queryArg.groupId, + }, + }), + invalidatesTags: ['sync_team_groups', 'enterprise'], + }), + getTeamGroupsApi: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}/groups` }), + providesTags: ['sync_team_groups', 'enterprise'], + }), + addTeamGroupApi: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/groups`, + method: 'POST', + body: queryArg.teamGroupMapping, + }), + invalidatesTags: ['sync_team_groups', 'enterprise'], + }), + searchTeamGroups: build.query({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/groups/search`, + params: { + page: queryArg.page, + perpage: queryArg.perpage, + query: queryArg.query, + name: queryArg.name, + }, + }), + providesTags: ['sync_team_groups', 'enterprise'], + }), + deleteTeamById: build.mutation({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}`, method: 'DELETE' }), + invalidatesTags: ['teams'], + }), + getTeamById: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}` }), + providesTags: ['teams'], + }), + updateTeam: build.mutation({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}`, method: 'PUT', body: queryArg.updateTeamCommand }), + invalidatesTags: ['teams'], + }), + getTeamMembers: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}/members` }), + providesTags: ['teams'], + }), + addTeamMember: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/members`, + method: 'POST', + body: queryArg.addTeamMemberCommand, + }), + invalidatesTags: ['teams'], + }), + setTeamMemberships: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/members`, + method: 'PUT', + body: queryArg.setTeamMembershipsCommand, + }), + invalidatesTags: ['teams'], + }), + removeTeamMember: build.mutation({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}/members/${queryArg.userId}`, method: 'DELETE' }), + invalidatesTags: ['teams'], + }), + updateTeamMember: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/members/${queryArg.userId}`, + method: 'PUT', + body: queryArg.updateTeamMemberCommand, + }), + invalidatesTags: ['teams'], + }), + getTeamPreferences: build.query({ + query: (queryArg) => ({ url: `/teams/${queryArg.teamId}/preferences` }), + providesTags: ['teams', 'preferences'], + }), + updateTeamPreferences: build.mutation({ + query: (queryArg) => ({ + url: `/teams/${queryArg.teamId}/preferences`, + method: 'PUT', + body: queryArg.updatePrefsCmd, + }), + invalidatesTags: ['teams', 'preferences'], + }), + getSignedInUser: build.query({ + query: () => ({ url: `/user` }), + providesTags: ['signed_in_user'], + }), + updateSignedInUser: build.mutation({ + query: (queryArg) => ({ url: `/user`, method: 'PUT', body: queryArg.updateUserCommand }), + invalidatesTags: ['signed_in_user'], + }), + getUserAuthTokens: build.query({ + query: () => ({ url: `/user/auth-tokens` }), + providesTags: ['signed_in_user'], + }), + updateUserEmail: build.query({ + query: () => ({ url: `/user/email/update` }), + providesTags: ['user'], + }), + clearHelpFlags: build.query({ + query: () => ({ url: `/user/helpflags/clear` }), + providesTags: ['signed_in_user'], + }), + setHelpFlag: build.mutation({ + query: (queryArg) => ({ url: `/user/helpflags/${queryArg.flagId}`, method: 'PUT' }), + invalidatesTags: ['signed_in_user'], + }), + getSignedInUserOrgList: build.query({ + query: () => ({ url: `/user/orgs` }), + providesTags: ['signed_in_user'], + }), + changeUserPassword: build.mutation({ + query: (queryArg) => ({ url: `/user/password`, method: 'PUT', body: queryArg.changeUserPasswordCommand }), + invalidatesTags: ['signed_in_user'], + }), + getUserPreferences: build.query({ + query: () => ({ url: `/user/preferences` }), + providesTags: ['signed_in_user', 'preferences'], + }), + patchUserPreferences: build.mutation({ + query: (queryArg) => ({ url: `/user/preferences`, method: 'PATCH', body: queryArg.patchPrefsCmd }), + invalidatesTags: ['signed_in_user', 'preferences'], + }), + updateUserPreferences: build.mutation({ + query: (queryArg) => ({ url: `/user/preferences`, method: 'PUT', body: queryArg.updatePrefsCmd }), + invalidatesTags: ['signed_in_user', 'preferences'], + }), + getUserQuotas: build.query({ + query: () => ({ url: `/user/quotas` }), + providesTags: ['quota', 'signed_in_user'], + }), + revokeUserAuthToken: build.mutation({ + query: (queryArg) => ({ url: `/user/revoke-auth-token`, method: 'POST', body: queryArg.revokeAuthTokenCmd }), + invalidatesTags: ['signed_in_user'], + }), + unstarDashboardByUid: build.mutation({ + query: (queryArg) => ({ url: `/user/stars/dashboard/uid/${queryArg.dashboardUid}`, method: 'DELETE' }), + invalidatesTags: ['signed_in_user'], + }), + starDashboardByUid: build.mutation({ + query: (queryArg) => ({ url: `/user/stars/dashboard/uid/${queryArg.dashboardUid}`, method: 'POST' }), + invalidatesTags: ['signed_in_user'], + }), + getSignedInUserTeamList: build.query({ + query: () => ({ url: `/user/teams` }), + providesTags: ['signed_in_user'], + }), + userSetUsingOrg: build.mutation({ + query: (queryArg) => ({ url: `/user/using/${queryArg.orgId}`, method: 'POST' }), + invalidatesTags: ['signed_in_user'], + }), + searchUsers: build.query({ + query: (queryArg) => ({ + url: `/users`, + params: { + perpage: queryArg.perpage, + page: queryArg.page, + }, + }), + providesTags: ['users'], + }), + getUserByLoginOrEmail: build.query({ + query: (queryArg) => ({ + url: `/users/lookup`, + params: { + loginOrEmail: queryArg.loginOrEmail, + }, + }), + providesTags: ['users'], + }), + searchUsersWithPaging: build.query({ + query: () => ({ url: `/users/search` }), + providesTags: ['users'], + }), + getUserById: build.query({ + query: (queryArg) => ({ url: `/users/${queryArg.userId}` }), + providesTags: ['users'], + }), + updateUser: build.mutation({ + query: (queryArg) => ({ url: `/users/${queryArg.userId}`, method: 'PUT', body: queryArg.updateUserCommand }), + invalidatesTags: ['users'], + }), + getUserOrgList: build.query({ + query: (queryArg) => ({ url: `/users/${queryArg.userId}/orgs` }), + providesTags: ['users'], + }), + getUserTeams: build.query({ + query: (queryArg) => ({ url: `/users/${queryArg.userId}/teams` }), + providesTags: ['users'], + }), + routeGetAlertRules: build.query({ + query: () => ({ url: `/v1/provisioning/alert-rules` }), + providesTags: ['provisioning'], + }), + routePostAlertRule: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules`, + method: 'POST', + body: queryArg.provisionedAlertRule, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRulesExport: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules/export`, + params: { + download: queryArg.download, + format: queryArg.format, + folderUid: queryArg.folderUid, + group: queryArg.group, + ruleUid: queryArg.ruleUid, + }, + }), + providesTags: ['provisioning'], + }), + routeDeleteAlertRule: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules/${queryArg.uid}`, + method: 'DELETE', + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRule: build.query({ + query: (queryArg) => ({ url: `/v1/provisioning/alert-rules/${queryArg.uid}` }), + providesTags: ['provisioning'], + }), + routePutAlertRule: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules/${queryArg.uid}`, + method: 'PUT', + body: queryArg.provisionedAlertRule, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRuleExport: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/alert-rules/${queryArg.uid}/export`, + params: { + download: queryArg.download, + format: queryArg.format, + }, + }), + providesTags: ['provisioning'], + }), + routeGetContactpoints: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/contact-points`, + params: { + name: queryArg.name, + }, + }), + providesTags: ['provisioning'], + }), + routePostContactpoints: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/contact-points`, + method: 'POST', + body: queryArg.embeddedContactPoint, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetContactpointsExport: build.query< + RouteGetContactpointsExportApiResponse, + RouteGetContactpointsExportApiArg + >({ + query: (queryArg) => ({ + url: `/v1/provisioning/contact-points/export`, + params: { + download: queryArg.download, + format: queryArg.format, + decrypt: queryArg.decrypt, + name: queryArg.name, + }, + }), + providesTags: ['provisioning'], + }), + routeDeleteContactpoints: build.mutation({ + query: (queryArg) => ({ url: `/v1/provisioning/contact-points/${queryArg.uid}`, method: 'DELETE' }), + invalidatesTags: ['provisioning'], + }), + routePutContactpoint: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/contact-points/${queryArg.uid}`, + method: 'PUT', + body: queryArg.embeddedContactPoint, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeDeleteAlertRuleGroup: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/folder/${queryArg.folderUid}/rule-groups/${queryArg.group}`, + method: 'DELETE', + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRuleGroup: build.query({ + query: (queryArg) => ({ url: `/v1/provisioning/folder/${queryArg.folderUid}/rule-groups/${queryArg.group}` }), + providesTags: ['provisioning'], + }), + routePutAlertRuleGroup: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/folder/${queryArg.folderUid}/rule-groups/${queryArg.group}`, + method: 'PUT', + body: queryArg.alertRuleGroup, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetAlertRuleGroupExport: build.query< + RouteGetAlertRuleGroupExportApiResponse, + RouteGetAlertRuleGroupExportApiArg + >({ + query: (queryArg) => ({ + url: `/v1/provisioning/folder/${queryArg.folderUid}/rule-groups/${queryArg.group}/export`, + params: { + download: queryArg.download, + format: queryArg.format, + }, + }), + providesTags: ['provisioning'], + }), + routeGetMuteTimings: build.query({ + query: () => ({ url: `/v1/provisioning/mute-timings` }), + providesTags: ['provisioning'], + }), + routePostMuteTiming: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings`, + method: 'POST', + body: queryArg.muteTimeInterval, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeExportMuteTimings: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings/export`, + params: { + download: queryArg.download, + format: queryArg.format, + }, + }), + providesTags: ['provisioning'], + }), + routeDeleteMuteTiming: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings/${queryArg.name}`, + method: 'DELETE', + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + params: { + version: queryArg.version, + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetMuteTiming: build.query({ + query: (queryArg) => ({ url: `/v1/provisioning/mute-timings/${queryArg.name}` }), + providesTags: ['provisioning'], + }), + routePutMuteTiming: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings/${queryArg.name}`, + method: 'PUT', + body: queryArg.muteTimeInterval, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeExportMuteTiming: build.query({ + query: (queryArg) => ({ + url: `/v1/provisioning/mute-timings/${queryArg.name}/export`, + params: { + download: queryArg.download, + format: queryArg.format, + }, + }), + providesTags: ['provisioning'], + }), + routeResetPolicyTree: build.mutation({ + query: () => ({ url: `/v1/provisioning/policies`, method: 'DELETE' }), + invalidatesTags: ['provisioning'], + }), + routeGetPolicyTree: build.query({ + query: () => ({ url: `/v1/provisioning/policies` }), + providesTags: ['provisioning'], + }), + routePutPolicyTree: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/policies`, + method: 'PUT', + body: queryArg.route, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetPolicyTreeExport: build.query({ + query: () => ({ url: `/v1/provisioning/policies/export` }), + providesTags: ['provisioning'], + }), + routeGetTemplates: build.query({ + query: () => ({ url: `/v1/provisioning/templates` }), + providesTags: ['provisioning'], + }), + routeDeleteTemplate: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/templates/${queryArg.name}`, + method: 'DELETE', + params: { + version: queryArg.version, + }, + }), + invalidatesTags: ['provisioning'], + }), + routeGetTemplate: build.query({ + query: (queryArg) => ({ url: `/v1/provisioning/templates/${queryArg.name}` }), + providesTags: ['provisioning'], + }), + routePutTemplate: build.mutation({ + query: (queryArg) => ({ + url: `/v1/provisioning/templates/${queryArg.name}`, + method: 'PUT', + body: queryArg.notificationTemplateContent, + headers: { + 'X-Disable-Provenance': queryArg['X-Disable-Provenance'], + }, + }), + invalidatesTags: ['provisioning'], + }), + listAllProvidersSettings: build.query({ + query: () => ({ url: `/v1/sso-settings` }), + providesTags: ['sso_settings'], + }), + removeProviderSettings: build.mutation({ + query: (queryArg) => ({ url: `/v1/sso-settings/${queryArg.key}`, method: 'DELETE' }), + invalidatesTags: ['sso_settings'], + }), + getProviderSettings: build.query({ + query: (queryArg) => ({ url: `/v1/sso-settings/${queryArg.key}` }), + providesTags: ['sso_settings'], + }), + updateProviderSettings: build.mutation({ + query: (queryArg) => ({ url: `/v1/sso-settings/${queryArg.key}`, method: 'PUT', body: queryArg.body }), + invalidatesTags: ['sso_settings'], + }), + }), + overrideExisting: false, + }); +export { injectedRtkApi as generatedAPI }; +export type SearchResultApiResponse = /** status 200 (empty) */ SearchResult; +export type SearchResultApiArg = void; +export type ListRolesApiResponse = /** status 200 (empty) */ RoleDto[]; +export type ListRolesApiArg = { + delegatable?: boolean; + includeHidden?: boolean; +}; +export type CreateRoleApiResponse = /** status 201 (empty) */ RoleDto; +export type CreateRoleApiArg = { + createRoleForm: CreateRoleForm; +}; +export type DeleteRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteRoleApiArg = { + force?: boolean; + global?: boolean; + roleUid: string; +}; +export type GetRoleApiResponse = /** status 200 (empty) */ RoleDto; +export type GetRoleApiArg = { + roleUid: string; +}; +export type UpdateRoleApiResponse = /** status 200 (empty) */ RoleDto; +export type UpdateRoleApiArg = { + roleUid: string; + updateRoleCommand: UpdateRoleCommand; +}; +export type GetRoleAssignmentsApiResponse = /** status 200 (empty) */ RoleAssignmentsDto; +export type GetRoleAssignmentsApiArg = { + roleUid: string; +}; +export type SetRoleAssignmentsApiResponse = /** status 200 (empty) */ RoleAssignmentsDto; +export type SetRoleAssignmentsApiArg = { + roleUid: string; + setRoleAssignmentsCommand: SetRoleAssignmentsCommand; +}; +export type GetAccessControlStatusApiResponse = /** status 200 (empty) */ Status; +export type GetAccessControlStatusApiArg = void; +export type ListTeamsRolesApiResponse = /** status 200 (empty) */ { + [key: string]: RoleDto[]; +}; +export type ListTeamsRolesApiArg = { + rolesSearchQuery: RolesSearchQuery; +}; +export type ListTeamRolesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type ListTeamRolesApiArg = { + teamId: number; +}; +export type AddTeamRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddTeamRoleApiArg = { + teamId: number; + addTeamRoleCommand: AddTeamRoleCommand; +}; +export type SetTeamRolesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetTeamRolesApiArg = { + teamId: number; +}; +export type RemoveTeamRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveTeamRoleApiArg = { + roleUid: string; + teamId: number; +}; +export type ListUsersRolesApiResponse = /** status 200 (empty) */ { + [key: string]: RoleDto[]; +}; +export type ListUsersRolesApiArg = { + rolesSearchQuery: RolesSearchQuery; +}; +export type ListUserRolesApiResponse = /** status 200 (empty) */ RoleDto[]; +export type ListUserRolesApiArg = { + userId: number; +}; +export type AddUserRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddUserRoleApiArg = { + userId: number; + addUserRoleCommand: AddUserRoleCommand; +}; +export type SetUserRolesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetUserRolesApiArg = { + userId: number; + setUserRolesCommand: SetUserRolesCommand; +}; +export type RemoveUserRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveUserRoleApiArg = { + /** A flag indicating if the assignment is global or not. If set to false, the default org ID of the authenticated user will be used from the request to remove assignment. */ + global?: boolean; + roleUid: string; + userId: number; +}; +export type GetResourceDescriptionApiResponse = /** status 200 (empty) */ Description; +export type GetResourceDescriptionApiArg = { + resource: string; +}; +export type GetResourcePermissionsApiResponse = /** status 200 (empty) */ ResourcePermissionDto[]; +export type GetResourcePermissionsApiArg = { + resource: string; + resourceId: string; +}; +export type SetResourcePermissionsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetResourcePermissionsApiArg = { + resource: string; + resourceId: string; + setPermissionsCommand: SetPermissionsCommand; +}; +export type SetResourcePermissionsForBuiltInRoleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetResourcePermissionsForBuiltInRoleApiArg = { + resource: string; + resourceId: string; + builtInRole: string; + setPermissionCommand: SetPermissionCommand; +}; +export type SetResourcePermissionsForTeamApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetResourcePermissionsForTeamApiArg = { + resource: string; + resourceId: string; + teamId: number; + setPermissionCommand: SetPermissionCommand; +}; +export type SetResourcePermissionsForUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetResourcePermissionsForUserApiArg = { + resource: string; + resourceId: string; + userId: number; + setPermissionCommand: SetPermissionCommand; +}; +export type GetSyncStatusApiResponse = /** status 200 (empty) */ ActiveSyncStatusDto; +export type GetSyncStatusApiArg = void; +export type ReloadLdapCfgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type ReloadLdapCfgApiArg = void; +export type GetLdapStatusApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type GetLdapStatusApiArg = void; +export type PostSyncUserWithLdapApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type PostSyncUserWithLdapApiArg = { + userId: number; +}; +export type GetUserFromLdapApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type GetUserFromLdapApiArg = { + userName: string; +}; +export type AdminProvisioningReloadAccessControlApiResponse = /** status 202 AcceptedResponse */ ErrorResponseBody; +export type AdminProvisioningReloadAccessControlApiArg = void; +export type AdminProvisioningReloadDashboardsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminProvisioningReloadDashboardsApiArg = void; +export type AdminProvisioningReloadDatasourcesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminProvisioningReloadDatasourcesApiArg = void; +export type AdminProvisioningReloadPluginsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminProvisioningReloadPluginsApiArg = void; +export type AdminGetSettingsApiResponse = /** status 200 (empty) */ SettingsBag; +export type AdminGetSettingsApiArg = void; +export type AdminGetStatsApiResponse = /** status 200 (empty) */ AdminStats; +export type AdminGetStatsApiArg = void; +export type AdminCreateUserApiResponse = /** status 200 (empty) */ AdminCreateUserResponse; +export type AdminCreateUserApiArg = { + adminCreateUserForm: AdminCreateUserForm; +}; +export type AdminDeleteUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminDeleteUserApiArg = { + userId: number; +}; +export type AdminGetUserAuthTokensApiResponse = /** status 200 (empty) */ UserToken[]; +export type AdminGetUserAuthTokensApiArg = { + userId: number; +}; +export type AdminDisableUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminDisableUserApiArg = { + userId: number; +}; +export type AdminEnableUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminEnableUserApiArg = { + userId: number; +}; +export type AdminLogoutUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminLogoutUserApiArg = { + userId: number; +}; +export type AdminUpdateUserPasswordApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminUpdateUserPasswordApiArg = { + userId: number; + adminUpdateUserPasswordForm: AdminUpdateUserPasswordForm; +}; +export type AdminUpdateUserPermissionsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminUpdateUserPermissionsApiArg = { + userId: number; + adminUpdateUserPermissionsForm: AdminUpdateUserPermissionsForm; +}; +export type GetUserQuotaApiResponse = /** status 200 (empty) */ QuotaDto[]; +export type GetUserQuotaApiArg = { + userId: number; +}; +export type UpdateUserQuotaApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateUserQuotaApiArg = { + quotaTarget: string; + userId: number; + updateQuotaCmd: UpdateQuotaCmd; +}; +export type AdminRevokeUserAuthTokenApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AdminRevokeUserAuthTokenApiArg = { + userId: number; + revokeAuthTokenCmd: RevokeAuthTokenCmd; +}; +export type GetAnnotationsApiResponse = /** status 200 (empty) */ Annotation[]; +export type GetAnnotationsApiArg = { + /** Find annotations created after specific epoch datetime in milliseconds. */ + from?: number; + /** Find annotations created before specific epoch datetime in milliseconds. */ + to?: number; + /** Limit response to annotations created by specific user. */ + userId?: number; + /** Find annotations for a specified alert rule by its ID. + deprecated: AlertID is deprecated and will be removed in future versions. Please use AlertUID instead. */ + alertId?: number; + /** Find annotations for a specified alert rule by its UID. */ + alertUid?: string; + /** Find annotations that are scoped to a specific dashboard */ + dashboardId?: number; + /** Find annotations that are scoped to a specific dashboard */ + dashboardUid?: string; + /** Find annotations that are scoped to a specific panel */ + panelId?: number; + /** Max limit for results returned. */ + limit?: number; + /** Use this to filter organization annotations. Organization annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. You can filter by multiple tags. */ + tags?: string[]; + /** Return alerts or user created annotations */ + type?: 'alert' | 'annotation'; + /** Match any or all tags */ + matchAny?: boolean; +}; +export type PostAnnotationApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the created annotation. */ + id: number; + /** Message Message of the created annotation. */ + message: string; +}; +export type PostAnnotationApiArg = { + postAnnotationsCmd: PostAnnotationsCmd; +}; +export type PostGraphiteAnnotationApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the created annotation. */ + id: number; + /** Message Message of the created annotation. */ + message: string; +}; +export type PostGraphiteAnnotationApiArg = { + postGraphiteAnnotationsCmd: PostGraphiteAnnotationsCmd; +}; +export type MassDeleteAnnotationsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type MassDeleteAnnotationsApiArg = { + massDeleteAnnotationsCmd: MassDeleteAnnotationsCmd; +}; +export type GetAnnotationTagsApiResponse = + /** status 200 (empty) */ GetAnnotationTagsResponseIsAResponseStructForFindTagsResult; +export type GetAnnotationTagsApiArg = { + /** Tag is a string that you can use to filter tags. */ + tag?: string; + /** Max limit for results returned. */ + limit?: string; +}; +export type DeleteAnnotationByIdApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteAnnotationByIdApiArg = { + annotationId: string; +}; +export type GetAnnotationByIdApiResponse = /** status 200 (empty) */ Annotation; +export type GetAnnotationByIdApiArg = { + annotationId: string; +}; +export type PatchAnnotationApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type PatchAnnotationApiArg = { + annotationId: string; + patchAnnotationsCmd: PatchAnnotationsCmd; +}; +export type UpdateAnnotationApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateAnnotationApiArg = { + annotationId: string; + updateAnnotationsCmd: UpdateAnnotationsCmd; +}; +export type ListDevicesApiResponse = /** status 200 (empty) */ DeviceDto[]; +export type ListDevicesApiArg = void; +export type SearchDevicesApiResponse = /** status 200 (empty) */ SearchDeviceQueryResult; +export type SearchDevicesApiArg = void; +export type GetSessionListApiResponse = /** status 200 (empty) */ CloudMigrationSessionListResponseDto; +export type GetSessionListApiArg = void; +export type CreateSessionApiResponse = /** status 200 (empty) */ CloudMigrationSessionResponseDto; +export type CreateSessionApiArg = { + cloudMigrationSessionRequestDto: CloudMigrationSessionRequestDto; +}; +export type DeleteSessionApiResponse = unknown; +export type DeleteSessionApiArg = { + /** UID of a migration session */ + uid: string; +}; +export type GetSessionApiResponse = /** status 200 (empty) */ CloudMigrationSessionResponseDto; +export type GetSessionApiArg = { + /** UID of a migration session */ + uid: string; +}; +export type CreateSnapshotApiResponse = /** status 200 (empty) */ CreateSnapshotResponseDto; +export type CreateSnapshotApiArg = { + /** UID of a session */ + uid: string; + createSnapshotRequestDto: CreateSnapshotRequestDto; +}; +export type GetSnapshotApiResponse = /** status 200 (empty) */ GetSnapshotResponseDto; +export type GetSnapshotApiArg = { + /** ResultPage is used for pagination with ResultLimit */ + resultPage?: number; + /** Max limit for snapshot results returned. */ + resultLimit?: number; + /** ResultSortColumn can be used to override the default system sort. Valid values are "name", "resource_type", and "status". */ + resultSortColumn?: string; + /** ResultSortOrder is used with ResultSortColumn. Valid values are ASC and DESC. */ + resultSortOrder?: string; + /** ErrorsOnly is used to only return resources with error statuses */ + errorsOnly?: boolean; + /** Session UID of a session */ + uid: string; + /** UID of a snapshot */ + snapshotUid: string; +}; +export type CancelSnapshotApiResponse = unknown; +export type CancelSnapshotApiArg = { + /** Session UID of a session */ + uid: string; + /** UID of a snapshot */ + snapshotUid: string; +}; +export type UploadSnapshotApiResponse = unknown; +export type UploadSnapshotApiArg = { + /** Session UID of a session */ + uid: string; + /** UID of a snapshot */ + snapshotUid: string; +}; +export type GetShapshotListApiResponse = /** status 200 (empty) */ SnapshotListResponseDto; +export type GetShapshotListApiArg = { + /** Page is used for pagination with limit */ + page?: number; + /** Max limit for results returned. */ + limit?: number; + /** Session UID of a session */ + uid: string; + /** Sort with value latest to return results sorted in descending order. */ + sort?: string; +}; +export type GetResourceDependenciesApiResponse = /** status 200 (empty) */ ResourceDependenciesResponseDto; +export type GetResourceDependenciesApiArg = void; +export type GetCloudMigrationTokenApiResponse = /** status 200 (empty) */ GetAccessTokenResponseDto; +export type GetCloudMigrationTokenApiArg = void; +export type CreateCloudMigrationTokenApiResponse = /** status 200 (empty) */ CreateAccessTokenResponseDto; +export type CreateCloudMigrationTokenApiArg = void; +export type DeleteCloudMigrationTokenApiResponse = unknown; +export type DeleteCloudMigrationTokenApiArg = { + /** UID of a cloud migration token */ + uid: string; +}; +export type RouteConvertPrometheusCortexGetRulesApiResponse = unknown; +export type RouteConvertPrometheusCortexGetRulesApiArg = void; +export type RouteConvertPrometheusCortexPostRuleGroupsApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusCortexPostRuleGroupsApiArg = void; +export type RouteConvertPrometheusCortexDeleteNamespaceApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusCortexDeleteNamespaceApiArg = { + namespaceTitle: string; +}; +export type RouteConvertPrometheusCortexGetNamespaceApiResponse = unknown; +export type RouteConvertPrometheusCortexGetNamespaceApiArg = { + namespaceTitle: string; +}; +export type RouteConvertPrometheusCortexPostRuleGroupApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusCortexPostRuleGroupApiArg = { + namespaceTitle: string; + 'x-grafana-alerting-datasource-uid'?: string; + 'x-grafana-alerting-recording-rules-paused'?: boolean; + 'x-grafana-alerting-alert-rules-paused'?: boolean; + 'x-grafana-alerting-target-datasource-uid'?: string; + 'x-grafana-alerting-folder-uid'?: string; + 'x-grafana-alerting-notification-receiver'?: string; + prometheusRuleGroup: PrometheusRuleGroup; +}; +export type RouteConvertPrometheusCortexDeleteRuleGroupApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusCortexDeleteRuleGroupApiArg = { + namespaceTitle: string; + group: string; +}; +export type RouteConvertPrometheusCortexGetRuleGroupApiResponse = unknown; +export type RouteConvertPrometheusCortexGetRuleGroupApiArg = { + namespaceTitle: string; + group: string; +}; +export type RouteConvertPrometheusGetRulesApiResponse = unknown; +export type RouteConvertPrometheusGetRulesApiArg = void; +export type RouteConvertPrometheusPostRuleGroupsApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusPostRuleGroupsApiArg = void; +export type RouteConvertPrometheusDeleteNamespaceApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusDeleteNamespaceApiArg = { + namespaceTitle: string; +}; +export type RouteConvertPrometheusGetNamespaceApiResponse = unknown; +export type RouteConvertPrometheusGetNamespaceApiArg = { + namespaceTitle: string; +}; +export type RouteConvertPrometheusPostRuleGroupApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusPostRuleGroupApiArg = { + namespaceTitle: string; + 'x-grafana-alerting-datasource-uid'?: string; + 'x-grafana-alerting-recording-rules-paused'?: boolean; + 'x-grafana-alerting-alert-rules-paused'?: boolean; + 'x-grafana-alerting-target-datasource-uid'?: string; + 'x-grafana-alerting-folder-uid'?: string; + 'x-grafana-alerting-notification-receiver'?: string; + prometheusRuleGroup: PrometheusRuleGroup; +}; +export type RouteConvertPrometheusDeleteRuleGroupApiResponse = + /** status 202 ConvertPrometheusResponse */ ConvertPrometheusResponse; +export type RouteConvertPrometheusDeleteRuleGroupApiArg = { + namespaceTitle: string; + group: string; +}; +export type RouteConvertPrometheusGetRuleGroupApiResponse = unknown; +export type RouteConvertPrometheusGetRuleGroupApiArg = { + namespaceTitle: string; + group: string; +}; +export type SearchDashboardSnapshotsApiResponse = /** status 200 (empty) */ DashboardSnapshotDto[]; +export type SearchDashboardSnapshotsApiArg = { + /** Search Query */ + query?: string; + /** Limit the number of returned results */ + limit?: number; +}; +export type CalculateDashboardDiffApiResponse = /** status 200 (empty) */ number[]; +export type CalculateDashboardDiffApiArg = { + body: { + base?: CalculateDiffTarget; + /** The type of diff to return + Description: + `basic` + `json` */ + diffType?: 'basic' | 'json'; + new?: CalculateDiffTarget; + }; +}; +export type PostDashboardApiResponse = /** status 200 (empty) */ { + /** FolderUID The unique identifier (uid) of the folder the dashboard belongs to. */ + folderUid?: string; + /** ID The unique identifier (id) of the created/updated dashboard. */ + id: number; + /** Status status of the response. */ + status: string; + /** Slug The slug of the dashboard. */ + title: string; + /** UID The unique identifier (uid) of the created/updated dashboard. */ + uid: string; + /** URL The relative URL for accessing the created/updated dashboard. */ + url: string; + /** Version The version of the dashboard. */ + version: number; +}; +export type PostDashboardApiArg = { + saveDashboardCommand: SaveDashboardCommand; +}; +export type GetHomeDashboardApiResponse = /** status 200 (empty) */ GetHomeDashboardResponse; +export type GetHomeDashboardApiArg = void; +export type ImportDashboardApiResponse = + /** status 200 (empty) */ ImportDashboardResponseResponseObjectReturnedWhenImportingADashboard; +export type ImportDashboardApiArg = { + importDashboardRequest: ImportDashboardRequestRequestObjectForImportingADashboard; +}; +export type InterpolateDashboardApiResponse = /** status 200 (empty) */ any; +export type InterpolateDashboardApiArg = void; +export type ListPublicDashboardsApiResponse = /** status 200 (empty) */ PublicDashboardListResponseWithPagination; +export type ListPublicDashboardsApiArg = void; +export type GetDashboardTagsApiResponse = /** status 200 (empty) */ DashboardTagCloudItem[]; +export type GetDashboardTagsApiArg = void; +export type GetPublicDashboardApiResponse = /** status 200 (empty) */ PublicDashboard; +export type GetPublicDashboardApiArg = { + dashboardUid: string; +}; +export type CreatePublicDashboardApiResponse = /** status 200 (empty) */ PublicDashboard; +export type CreatePublicDashboardApiArg = { + dashboardUid: string; + publicDashboardDto: PublicDashboardDto; +}; +export type DeletePublicDashboardApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeletePublicDashboardApiArg = { + dashboardUid: string; + uid: string; +}; +export type UpdatePublicDashboardApiResponse = /** status 200 (empty) */ PublicDashboard; +export type UpdatePublicDashboardApiArg = { + dashboardUid: string; + uid: string; + publicDashboardDto: PublicDashboardDto; +}; +export type DeleteDashboardByUidApiResponse = /** status 200 (empty) */ { + /** Message Message of the deleted dashboard. */ + message: string; + /** Title Title of the deleted dashboard. */ + title: string; + /** UID Identifier of the deleted dashboard. */ + uid: string; +}; +export type DeleteDashboardByUidApiArg = { + uid: string; +}; +export type GetDashboardByUidApiResponse = /** status 200 (empty) */ DashboardFullWithMeta; +export type GetDashboardByUidApiArg = { + uid: string; +}; +export type GetDashboardPermissionsListByUidApiResponse = /** status 200 (empty) */ DashboardAclInfoDto[]; +export type GetDashboardPermissionsListByUidApiArg = { + uid: string; +}; +export type UpdateDashboardPermissionsByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateDashboardPermissionsByUidApiArg = { + uid: string; + updateDashboardAclCommand: UpdateDashboardAclCommand; +}; +export type RestoreDashboardVersionByUidApiResponse = /** status 200 (empty) */ { + /** FolderUID The unique identifier (uid) of the folder the dashboard belongs to. */ + folderUid?: string; + /** ID The unique identifier (id) of the created/updated dashboard. */ + id: number; + /** Status status of the response. */ + status: string; + /** Slug The slug of the dashboard. */ + title: string; + /** UID The unique identifier (uid) of the created/updated dashboard. */ + uid: string; + /** URL The relative URL for accessing the created/updated dashboard. */ + url: string; + /** Version The version of the dashboard. */ + version: number; +}; +export type RestoreDashboardVersionByUidApiArg = { + uid: string; + restoreDashboardVersionCommand: RestoreDashboardVersionCommand; +}; +export type GetDashboardVersionsByUidApiResponse = /** status 200 (empty) */ DashboardVersionResponseMeta; +export type GetDashboardVersionsByUidApiArg = { + uid: string; + /** Maximum number of results to return */ + limit?: number; + /** Version to start from when returning queries */ + start?: number; +}; +export type GetDashboardVersionByUidApiResponse = /** status 200 (empty) */ DashboardVersionMeta; +export type GetDashboardVersionByUidApiArg = { + dashboardVersionId: number; + uid: string; +}; +export type GetDataSourcesApiResponse = /** status 200 (empty) */ DataSourceList; +export type GetDataSourcesApiArg = void; +export type AddDataSourceApiResponse = /** status 200 (empty) */ { + datasource: DataSource; + /** ID Identifier of the new data source. */ + id: number; + /** Message Message of the deleted dashboard. */ + message: string; + /** Name of the new data source. */ + name: string; +}; +export type AddDataSourceApiArg = { + addDataSourceCommand: AddDataSourceCommand; +}; +export type GetCorrelationsApiResponse = /** status 200 (empty) */ Correlation[]; +export type GetCorrelationsApiArg = { + /** Limit the maximum number of correlations to return per page */ + limit?: number; + /** Page index for starting fetching correlations */ + page?: number; + /** Source datasource UID filter to be applied to correlations */ + sourceUid?: string[]; +}; +export type GetDataSourceIdByNameApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the data source. */ + id: number; +}; +export type GetDataSourceIdByNameApiArg = { + name: string; +}; +export type DeleteDataSourceByNameApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the deleted data source. */ + id: number; + /** Message Message of the deleted dashboard. */ + message: string; +}; +export type DeleteDataSourceByNameApiArg = { + name: string; +}; +export type GetDataSourceByNameApiResponse = /** status 200 (empty) */ DataSource; +export type GetDataSourceByNameApiArg = { + name: string; +}; +export type DatasourceProxyDeleteByUiDcallsApiResponse = unknown; +export type DatasourceProxyDeleteByUiDcallsApiArg = { + uid: string; + datasourceProxyRoute: string; +}; +export type DatasourceProxyGetByUiDcallsApiResponse = unknown; +export type DatasourceProxyGetByUiDcallsApiArg = { + datasourceProxyRoute: string; + uid: string; +}; +export type DatasourceProxyPostByUiDcallsApiResponse = unknown; +export type DatasourceProxyPostByUiDcallsApiArg = { + datasourceProxyRoute: string; + uid: string; + body: any; +}; +export type GetCorrelationsBySourceUidApiResponse = /** status 200 (empty) */ Correlation[]; +export type GetCorrelationsBySourceUidApiArg = { + sourceUid: string; +}; +export type CreateCorrelationApiResponse = /** status 200 (empty) */ CreateCorrelationResponseBody; +export type CreateCorrelationApiArg = { + sourceUid: string; + createCorrelationCommand: CreateCorrelationCommand; +}; +export type GetCorrelationApiResponse = /** status 200 (empty) */ Correlation; +export type GetCorrelationApiArg = { + sourceUid: string; + correlationUid: string; +}; +export type UpdateCorrelationApiResponse = /** status 200 (empty) */ UpdateCorrelationResponseBody; +export type UpdateCorrelationApiArg = { + sourceUid: string; + correlationUid: string; + updateCorrelationCommand: UpdateCorrelationCommand; +}; +export type DeleteDataSourceByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteDataSourceByUidApiArg = { + uid: string; +}; +export type GetDataSourceByUidApiResponse = /** status 200 (empty) */ DataSource; +export type GetDataSourceByUidApiArg = { + uid: string; +}; +export type UpdateDataSourceByUidApiResponse = /** status 200 (empty) */ { + datasource: DataSource; + /** ID Identifier of the new data source. */ + id: number; + /** Message Message of the deleted dashboard. */ + message: string; + /** Name of the new data source. */ + name: string; +}; +export type UpdateDataSourceByUidApiArg = { + uid: string; + updateDataSourceCommand: UpdateDataSourceCommand; +}; +export type DeleteCorrelationApiResponse = /** status 200 (empty) */ DeleteCorrelationResponseBody; +export type DeleteCorrelationApiArg = { + uid: string; + correlationUid: string; +}; +export type CheckDatasourceHealthWithUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type CheckDatasourceHealthWithUidApiArg = { + uid: string; +}; +export type GetTeamLbacRulesApiApiResponse = /** status 200 (empty) */ TeamLbacRules; +export type GetTeamLbacRulesApiApiArg = { + uid: string; +}; +export type UpdateTeamLbacRulesApiApiResponse = /** status 200 (empty) */ { + id?: number; + message?: string; + name?: string; + rules?: TeamLbacRule[]; + uid?: string; +}; +export type UpdateTeamLbacRulesApiApiArg = { + uid: string; + updateTeamLbacCommand: UpdateTeamLbacCommand; +}; +export type CallDatasourceResourceWithUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type CallDatasourceResourceWithUidApiArg = { + datasourceProxyRoute: string; + uid: string; +}; +export type GetDataSourceCacheConfigApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type GetDataSourceCacheConfigApiArg = { + dataSourceUid: string; +}; +export type SetDataSourceCacheConfigApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type SetDataSourceCacheConfigApiArg = { + dataSourceUid: string; + cacheConfigSetter: CacheConfigSetter; +}; +export type CleanDataSourceCacheApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type CleanDataSourceCacheApiArg = { + dataSourceUid: string; +}; +export type DisableDataSourceCacheApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type DisableDataSourceCacheApiArg = { + dataSourceUid: string; +}; +export type EnableDataSourceCacheApiResponse = /** status 200 CacheConfigResponse */ CacheConfigResponse; +export type EnableDataSourceCacheApiArg = { + dataSourceUid: string; +}; +export type QueryMetricsWithExpressionsApiResponse = /** status 200 (empty) */ + | QueryDataResponseContainsTheResultsFromAQueryDataRequest + | /** status 207 (empty) */ QueryDataResponseContainsTheResultsFromAQueryDataRequest; +export type QueryMetricsWithExpressionsApiArg = { + metricRequest: MetricRequest; +}; +export type GetFoldersApiResponse = /** status 200 (empty) */ FolderSearchHit[]; +export type GetFoldersApiArg = { + /** Limit the maximum number of folders to return */ + limit?: number; + /** Page index for starting fetching folders */ + page?: number; + /** The parent folder UID */ + parentUid?: string; + /** Set to `Edit` to return folders that the user can edit */ + permission?: 'Edit' | 'View'; +}; +export type CreateFolderApiResponse = /** status 200 (empty) */ Folder; +export type CreateFolderApiArg = { + createFolderCommand: CreateFolderCommand; +}; +export type DeleteFolderApiResponse = /** status 200 (empty) */ { + /** ID Identifier of the deleted folder. */ + id: number; + /** Message Message of the deleted folder. */ + message: string; + /** Title of the deleted folder. */ + title: string; +}; +export type DeleteFolderApiArg = { + folderUid: string; + /** If `true` any Grafana 8 Alerts under this folder will be deleted. + Set to `false` so that the request will fail if the folder contains any Grafana 8 Alerts. */ + forceDeleteRules?: boolean; +}; +export type GetFolderByUidApiResponse = /** status 200 (empty) */ Folder; +export type GetFolderByUidApiArg = { + folderUid: string; +}; +export type UpdateFolderApiResponse = /** status 200 (empty) */ Folder; +export type UpdateFolderApiArg = { + folderUid: string; + /** To change the unique identifier (uid), provide another one. + To overwrite an existing folder with newer version, set `overwrite` to `true`. + Provide the current version to safelly update the folder: if the provided version differs from the stored one the request will fail, unless `overwrite` is `true`. */ + updateFolderCommand: UpdateFolderCommand; +}; +export type GetFolderDescendantCountsApiResponse = /** status 200 (empty) */ DescendantCounts; +export type GetFolderDescendantCountsApiArg = { + folderUid: string; +}; +export type MoveFolderApiResponse = /** status 200 (empty) */ Folder; +export type MoveFolderApiArg = { + folderUid: string; + moveFolderCommand: MoveFolderCommand; +}; +export type GetFolderPermissionListApiResponse = /** status 200 (empty) */ DashboardAclInfoDto[]; +export type GetFolderPermissionListApiArg = { + folderUid: string; +}; +export type UpdateFolderPermissionsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateFolderPermissionsApiArg = { + folderUid: string; + updateDashboardAclCommand: UpdateDashboardAclCommand; +}; +export type GetMappedGroupsApiResponse = /** status 200 (empty) */ GetGroupsResponse; +export type GetMappedGroupsApiArg = void; +export type DeleteGroupMappingsApiResponse = + /** status 204 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteGroupMappingsApiArg = { + groupId: string; +}; +export type CreateGroupMappingsApiResponse = /** status 201 (empty) */ MessageResponse; +export type CreateGroupMappingsApiArg = { + groupId: string; + groupAttributes: GroupAttributes; +}; +export type UpdateGroupMappingsApiResponse = /** status 201 (empty) */ MessageResponse; +export type UpdateGroupMappingsApiArg = { + groupId: string; + groupAttributes: GroupAttributes; +}; +export type GetGroupRolesApiResponse = /** status 200 (empty) */ RoleDto[]; +export type GetGroupRolesApiArg = { + groupId: string; +}; +export type GetHealthApiResponse = /** status 200 healthResponse */ HealthResponse; +export type GetHealthApiArg = void; +export type GetLibraryElementsApiResponse = + /** status 200 (empty) */ LibraryElementSearchResponseIsAResponseStructForLibraryElementSearchResult; +export type GetLibraryElementsApiArg = { + /** Part of the name or description searched for. */ + searchString?: string; + /** Kind of element to search for. */ + kind?: 1; + /** Sort order of elements. */ + sortDirection?: 'alpha-asc' | 'alpha-desc'; + /** A comma separated list of types to filter the elements by */ + typeFilter?: string; + /** Element UID to exclude from search results. */ + excludeUid?: string; + /** A comma separated list of folder ID(s) to filter the elements by. */ + folderFilter?: string; + /** The number of results per page. */ + perPage?: number; + /** The page for a set of records, given that only perPage records are returned at a time. Numbering starts at 1. */ + page?: number; +}; +export type CreateLibraryElementApiResponse = + /** status 200 (empty) */ LibraryElementResponseIsAResponseStructForLibraryElementDto; +export type CreateLibraryElementApiArg = { + createLibraryElementCommand: CreateLibraryElementCommand; +}; +export type GetLibraryElementByNameApiResponse = + /** status 200 (empty) */ LibraryElementArrayResponseIsAResponseStructForAnArrayOfLibraryElementDto; +export type GetLibraryElementByNameApiArg = { + libraryElementName: string; +}; +export type DeleteLibraryElementByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteLibraryElementByUidApiArg = { + libraryElementUid: string; +}; +export type GetLibraryElementByUidApiResponse = + /** status 200 (empty) */ LibraryElementResponseIsAResponseStructForLibraryElementDto; +export type GetLibraryElementByUidApiArg = { + libraryElementUid: string; +}; +export type UpdateLibraryElementApiResponse = + /** status 200 (empty) */ LibraryElementResponseIsAResponseStructForLibraryElementDto; +export type UpdateLibraryElementApiArg = { + libraryElementUid: string; + patchLibraryElementCommand: PatchLibraryElementCommand; +}; +export type GetLibraryElementConnectionsApiResponse = + /** status 200 (empty) */ LibraryElementConnectionsResponseIsAResponseStructForAnArrayOfLibraryElementConnectionDto; +export type GetLibraryElementConnectionsApiArg = { + libraryElementUid: string; +}; +export type GetStatusApiResponse = unknown; +export type GetStatusApiArg = void; +export type RefreshLicenseStatsApiResponse = /** status 200 (empty) */ ActiveUserStats; +export type RefreshLicenseStatsApiArg = void; +export type DeleteLicenseTokenApiResponse = /** status 202 AcceptedResponse */ ErrorResponseBody; +export type DeleteLicenseTokenApiArg = { + deleteTokenCommand: DeleteTokenCommand; +}; +export type GetLicenseTokenApiResponse = /** status 200 (empty) */ Token; +export type GetLicenseTokenApiArg = void; +export type PostLicenseTokenApiResponse = /** status 200 (empty) */ Token; +export type PostLicenseTokenApiArg = { + deleteTokenCommand: DeleteTokenCommand; +}; +export type PostRenewLicenseTokenApiResponse = unknown; +export type PostRenewLicenseTokenApiArg = { + body: object; +}; +export type GetSamlLogoutApiResponse = unknown; +export type GetSamlLogoutApiArg = void; +export type GetCurrentOrgApiResponse = /** status 200 (empty) */ OrgDetailsDto; +export type GetCurrentOrgApiArg = void; +export type UpdateCurrentOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateCurrentOrgApiArg = { + updateOrgForm: UpdateOrgForm; +}; +export type UpdateCurrentOrgAddressApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateCurrentOrgAddressApiArg = { + updateOrgAddressForm: UpdateOrgAddressForm; +}; +export type GetPendingOrgInvitesApiResponse = /** status 200 (empty) */ TempUserDto[]; +export type GetPendingOrgInvitesApiArg = void; +export type AddOrgInviteApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddOrgInviteApiArg = { + addInviteForm: AddInviteForm; +}; +export type RevokeInviteApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RevokeInviteApiArg = { + invitationCode: string; +}; +export type GetOrgPreferencesApiResponse = /** status 200 (empty) */ PreferencesSpec; +export type GetOrgPreferencesApiArg = void; +export type PatchOrgPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type PatchOrgPreferencesApiArg = { + patchPrefsCmd: PatchPrefsCmd; +}; +export type UpdateOrgPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgPreferencesApiArg = { + updatePrefsCmd: UpdatePrefsCmd; +}; +export type GetCurrentOrgQuotaApiResponse = /** status 200 (empty) */ QuotaDto[]; +export type GetCurrentOrgQuotaApiArg = void; +export type GetOrgUsersForCurrentOrgApiResponse = /** status 200 (empty) */ OrgUserDto[]; +export type GetOrgUsersForCurrentOrgApiArg = { + query?: string; + limit?: number; +}; +export type AddOrgUserToCurrentOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddOrgUserToCurrentOrgApiArg = { + addOrgUserCommand: AddOrgUserCommand; +}; +export type GetOrgUsersForCurrentOrgLookupApiResponse = /** status 200 (empty) */ UserLookupDto[]; +export type GetOrgUsersForCurrentOrgLookupApiArg = { + query?: string; + limit?: number; +}; +export type RemoveOrgUserForCurrentOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveOrgUserForCurrentOrgApiArg = { + userId: number; +}; +export type UpdateOrgUserForCurrentOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgUserForCurrentOrgApiArg = { + userId: number; + updateOrgUserCommand: UpdateOrgUserCommand; +}; +export type SearchOrgsApiResponse = /** status 200 (empty) */ OrgDto[]; +export type SearchOrgsApiArg = { + page?: number; + /** Number of items per page + The totalCount field in the response can be used for pagination list E.g. if totalCount is equal to 100 teams and the perpage parameter is set to 10 then there are 10 pages of teams. */ + perpage?: number; + name?: string; + /** If set it will return results where the query value is contained in the name field. Query values with spaces need to be URL encoded. */ + query?: string; +}; +export type CreateOrgApiResponse = /** status 200 (empty) */ { + /** Message Message of the created org. */ + message: string; + /** ID Identifier of the created org. */ + orgId: number; +}; +export type CreateOrgApiArg = { + createOrgCommand: CreateOrgCommand; +}; +export type GetOrgByNameApiResponse = /** status 200 (empty) */ OrgDetailsDto; +export type GetOrgByNameApiArg = { + orgName: string; +}; +export type DeleteOrgByIdApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteOrgByIdApiArg = { + orgId: number; +}; +export type GetOrgByIdApiResponse = /** status 200 (empty) */ OrgDetailsDto; +export type GetOrgByIdApiArg = { + orgId: number; +}; +export type UpdateOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgApiArg = { + orgId: number; + updateOrgForm: UpdateOrgForm; +}; +export type UpdateOrgAddressApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgAddressApiArg = { + orgId: number; + updateOrgAddressForm: UpdateOrgAddressForm; +}; +export type GetOrgQuotaApiResponse = /** status 200 (empty) */ QuotaDto[]; +export type GetOrgQuotaApiArg = { + orgId: number; +}; +export type UpdateOrgQuotaApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgQuotaApiArg = { + quotaTarget: string; + orgId: number; + updateQuotaCmd: UpdateQuotaCmd; +}; +export type GetOrgUsersApiResponse = /** status 200 (empty) */ OrgUserDto[]; +export type GetOrgUsersApiArg = { + orgId: number; +}; +export type AddOrgUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddOrgUserApiArg = { + orgId: number; + addOrgUserCommand: AddOrgUserCommand; +}; +export type SearchOrgUsersApiResponse = /** status 200 (empty) */ SearchOrgUsersQueryResult; +export type SearchOrgUsersApiArg = { + orgId: number; +}; +export type RemoveOrgUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveOrgUserApiArg = { + orgId: number; + userId: number; +}; +export type UpdateOrgUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateOrgUserApiArg = { + orgId: number; + userId: number; + updateOrgUserCommand: UpdateOrgUserCommand; +}; +export type SearchPlaylistsApiResponse = /** status 200 (empty) */ Playlists; +export type SearchPlaylistsApiArg = { + query?: string; + /** in:limit */ + limit?: number; +}; +export type CreatePlaylistApiResponse = /** status 200 (empty) */ Playlist; +export type CreatePlaylistApiArg = { + createPlaylistCommand: CreatePlaylistCommand; +}; +export type DeletePlaylistApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeletePlaylistApiArg = { + uid: string; +}; +export type GetPlaylistApiResponse = /** status 200 (empty) */ PlaylistDto; +export type GetPlaylistApiArg = { + uid: string; +}; +export type UpdatePlaylistApiResponse = /** status 200 (empty) */ PlaylistDto; +export type UpdatePlaylistApiArg = { + uid: string; + updatePlaylistCommand: UpdatePlaylistCommand; +}; +export type GetPlaylistItemsApiResponse = /** status 200 (empty) */ PlaylistItemDto[]; +export type GetPlaylistItemsApiArg = { + uid: string; +}; +export type ViewPublicDashboardApiResponse = /** status 200 (empty) */ DashboardFullWithMeta; +export type ViewPublicDashboardApiArg = { + accessToken: string; +}; +export type GetPublicAnnotationsApiResponse = /** status 200 (empty) */ AnnotationEvent[]; +export type GetPublicAnnotationsApiArg = { + accessToken: string; +}; +export type QueryPublicDashboardApiResponse = + /** status 200 (empty) */ QueryDataResponseContainsTheResultsFromAQueryDataRequest; +export type QueryPublicDashboardApiArg = { + accessToken: string; + panelId: number; +}; +export type SearchQueriesApiResponse = /** status 200 (empty) */ QueryHistorySearchResponse; +export type SearchQueriesApiArg = { + /** List of data source UIDs to search for */ + datasourceUid?: string[]; + /** Text inside query or comments that is searched for */ + searchString?: string; + /** Flag indicating if only starred queries should be returned */ + onlyStarred?: boolean; + /** Sort method */ + sort?: 'time-desc' | 'time-asc'; + /** Use this parameter to access hits beyond limit. Numbering starts at 1. limit param acts as page size. */ + page?: number; + /** Limit the number of returned results */ + limit?: number; + /** From range for the query history search */ + from?: number; + /** To range for the query history search */ + to?: number; +}; +export type CreateQueryApiResponse = /** status 200 (empty) */ QueryHistoryResponse; +export type CreateQueryApiArg = { + createQueryInQueryHistoryCommand: CreateQueryInQueryHistoryCommand; +}; +export type UnstarQueryApiResponse = /** status 200 (empty) */ QueryHistoryResponse; +export type UnstarQueryApiArg = { + queryHistoryUid: string; +}; +export type StarQueryApiResponse = /** status 200 (empty) */ QueryHistoryResponse; +export type StarQueryApiArg = { + queryHistoryUid: string; +}; +export type DeleteQueryApiResponse = /** status 200 (empty) */ QueryHistoryDeleteQueryResponse; +export type DeleteQueryApiArg = { + queryHistoryUid: string; +}; +export type PatchQueryCommentApiResponse = /** status 200 (empty) */ QueryHistoryResponse; +export type PatchQueryCommentApiArg = { + queryHistoryUid: string; + patchQueryCommentInQueryHistoryCommand: PatchQueryCommentInQueryHistoryCommand; +}; +export type ListRecordingRulesApiResponse = /** status 200 (empty) */ RecordingRuleJson[]; +export type ListRecordingRulesApiArg = void; +export type CreateRecordingRuleApiResponse = /** status 200 (empty) */ RecordingRuleJson; +export type CreateRecordingRuleApiArg = { + recordingRuleJson: RecordingRuleJson; +}; +export type UpdateRecordingRuleApiResponse = /** status 200 (empty) */ RecordingRuleJson; +export type UpdateRecordingRuleApiArg = { + recordingRuleJson: RecordingRuleJson; +}; +export type TestCreateRecordingRuleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type TestCreateRecordingRuleApiArg = { + recordingRuleJson: RecordingRuleJson; +}; +export type DeleteRecordingRuleWriteTargetApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteRecordingRuleWriteTargetApiArg = void; +export type GetRecordingRuleWriteTargetApiResponse = /** status 200 (empty) */ PrometheusRemoteWriteTargetJson; +export type GetRecordingRuleWriteTargetApiArg = void; +export type CreateRecordingRuleWriteTargetApiResponse = /** status 200 (empty) */ PrometheusRemoteWriteTargetJson; +export type CreateRecordingRuleWriteTargetApiArg = { + prometheusRemoteWriteTargetJson: PrometheusRemoteWriteTargetJson; +}; +export type DeleteRecordingRuleApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteRecordingRuleApiArg = { + recordingRuleId: number; +}; +export type GetReportsApiResponse = /** status 200 (empty) */ Report[]; +export type GetReportsApiArg = void; +export type CreateReportApiResponse = /** status 200 (empty) */ { + id?: number; + message?: string; +}; +export type CreateReportApiArg = { + createOrUpdateReport: CreateOrUpdateReport; +}; +export type GetReportsByDashboardUidApiResponse = /** status 200 (empty) */ Report[]; +export type GetReportsByDashboardUidApiArg = { + uid: string; +}; +export type SendReportApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SendReportApiArg = { + reportEmail: ReportEmail; +}; +export type GetSettingsImageApiResponse = /** status 200 (empty) */ number[]; +export type GetSettingsImageApiArg = void; +export type RenderReportCsVsApiResponse = /** status 200 (empty) */ number[] | /** status 204 (empty) */ object; +export type RenderReportCsVsApiArg = { + dashboards?: string; + title?: string; +}; +export type RenderReportPdFsApiResponse = /** status 200 (empty) */ number[]; +export type RenderReportPdFsApiArg = { + dashboards?: string; + orientation?: string; + layout?: string; + title?: string; + scaleFactor?: string; + includeTables?: string; +}; +export type GetReportSettingsApiResponse = /** status 200 (empty) */ ReportSettings; +export type GetReportSettingsApiArg = void; +export type SaveReportSettingsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SaveReportSettingsApiArg = { + reportSettings: ReportSettings; +}; +export type SendTestEmailApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SendTestEmailApiArg = { + createOrUpdateReport: CreateOrUpdateReport; +}; +export type PostAcsApiResponse = unknown; +export type PostAcsApiArg = { + relayState?: string; +}; +export type GetMetadataApiResponse = /** status 200 (empty) */ number[]; +export type GetMetadataApiArg = void; +export type GetSloApiResponse = unknown; +export type GetSloApiArg = void; +export type PostSloApiResponse = unknown; +export type PostSloApiArg = { + samlRequest?: string; + samlResponse?: string; +}; +export type SearchApiResponse = /** status 200 (empty) */ HitList; +export type SearchApiArg = { + /** Search Query */ + query?: string; + /** List of tags to search for */ + tag?: string[]; + /** Type to search for, dash-folder or dash-db */ + type?: 'dash-folder' | 'dash-db'; + /** List of dashboard id’s to search for + This is deprecated: users should use the `dashboardUIDs` query parameter instead */ + dashboardIds?: number[]; + /** List of dashboard uid’s to search for */ + dashboardUiDs?: string[]; + /** List of folder id’s to search in for dashboards + If it's `0` then it will query for the top level folders + This is deprecated: users should use the `folderUIDs` query parameter instead */ + folderIds?: number[]; + /** List of folder UID’s to search in for dashboards + If it's an empty string then it will query for the top level folders */ + folderUiDs?: string[]; + /** Flag indicating if only starred Dashboards should be returned */ + starred?: boolean; + /** Limit the number of returned results (max 5000) */ + limit?: number; + /** Use this parameter to access hits beyond limit. Numbering starts at 1. limit param acts as page size. Only available in Grafana v6.2+. */ + page?: number; + /** Set to `Edit` to return dashboards/folders that the user can edit */ + permission?: 'Edit' | 'View'; + /** Sort method; for listing all the possible sort methods use the search sorting endpoint. */ + sort?: 'alpha-asc' | 'alpha-desc'; + /** Flag indicating if only soft deleted Dashboards should be returned */ + deleted?: boolean; +}; +export type ListSortOptionsApiResponse = /** status 200 (empty) */ { + description?: string; + displayName?: string; + meta?: string; + name?: string; +}; +export type ListSortOptionsApiArg = void; +export type CreateServiceAccountApiResponse = /** status 201 (empty) */ ServiceAccountDto; +export type CreateServiceAccountApiArg = { + createServiceAccountForm: CreateServiceAccountForm; +}; +export type SearchOrgServiceAccountsWithPagingApiResponse = /** status 200 (empty) */ SearchOrgServiceAccountsResult; +export type SearchOrgServiceAccountsWithPagingApiArg = { + disabled?: boolean; + expiredTokens?: boolean; + /** It will return results where the query value is contained in one of the name. + Query values with spaces need to be URL encoded. */ + query?: string; + /** The default value is 1000. */ + perpage?: number; + /** The default value is 1. */ + page?: number; +}; +export type DeleteServiceAccountApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteServiceAccountApiArg = { + serviceAccountId: number; +}; +export type RetrieveServiceAccountApiResponse = /** status 200 (empty) */ ServiceAccountDto; +export type RetrieveServiceAccountApiArg = { + serviceAccountId: number; +}; +export type UpdateServiceAccountApiResponse = /** status 200 (empty) */ { + id?: number; + message?: string; + name?: string; + serviceaccount?: ServiceAccountProfileDto; +}; +export type UpdateServiceAccountApiArg = { + serviceAccountId: number; + updateServiceAccountForm: UpdateServiceAccountForm; +}; +export type ListTokensApiResponse = /** status 200 (empty) */ TokenDto[]; +export type ListTokensApiArg = { + serviceAccountId: number; +}; +export type CreateTokenApiResponse = /** status 200 (empty) */ NewApiKeyResult; +export type CreateTokenApiArg = { + serviceAccountId: number; + addServiceAccountTokenCommand: AddServiceAccountTokenCommand; +}; +export type DeleteTokenApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteTokenApiArg = { + tokenId: number; + serviceAccountId: number; +}; +export type RetrieveJwksApiResponse = /** status 200 (empty) */ { + keys?: JsonWebKey[]; +}; +export type RetrieveJwksApiArg = void; +export type GetSharingOptionsApiResponse = /** status 200 (empty) */ { + externalEnabled?: boolean; + externalSnapshotName?: string; + externalSnapshotURL?: string; +}; +export type GetSharingOptionsApiArg = void; +export type CreateDashboardSnapshotApiResponse = /** status 200 (empty) */ { + /** Unique key used to delete the snapshot. It is different from the key so that only the creator can delete the snapshot. */ + deleteKey?: string; + deleteUrl?: string; + /** Snapshot id */ + id?: number; + /** Unique key */ + key?: string; + url?: string; +}; +export type CreateDashboardSnapshotApiArg = { + createDashboardSnapshotCommand: CreateDashboardSnapshotCommand; +}; +export type DeleteDashboardSnapshotByDeleteKeyApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteDashboardSnapshotByDeleteKeyApiArg = { + deleteKey: string; +}; +export type DeleteDashboardSnapshotApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteDashboardSnapshotApiArg = { + key: string; +}; +export type GetDashboardSnapshotApiResponse = unknown; +export type GetDashboardSnapshotApiArg = { + key: string; +}; +export type CreateTeamApiResponse = /** status 200 (empty) */ { + message?: string; + teamId?: number; + uid?: string; +}; +export type CreateTeamApiArg = { + createTeamCommand: CreateTeamCommand; +}; +export type SearchTeamsApiResponse = /** status 200 (empty) */ SearchTeamQueryResult; +export type SearchTeamsApiArg = { + page?: number; + /** Number of items per page + The totalCount field in the response can be used for pagination list E.g. if totalCount is equal to 100 teams and the perpage parameter is set to 10 then there are 10 pages of teams. */ + perpage?: number; + name?: string; + /** If set it will return results where the query value is contained in the name field. Query values with spaces need to be URL encoded. */ + query?: string; +}; +export type RemoveTeamGroupApiQueryApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveTeamGroupApiQueryApiArg = { + groupId?: string; + teamId: number; +}; +export type GetTeamGroupsApiApiResponse = /** status 200 (empty) */ TeamGroupDto[]; +export type GetTeamGroupsApiApiArg = { + teamId: number; +}; +export type AddTeamGroupApiApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddTeamGroupApiApiArg = { + teamId: number; + teamGroupMapping: TeamGroupMapping; +}; +export type SearchTeamGroupsApiResponse = /** status 200 (empty) */ SearchTeamGroupsQueryResult; +export type SearchTeamGroupsApiArg = { + teamId: number; + page?: number; + /** Number of items per page */ + perpage?: number; + /** If set it will return results where the query value is contained in the name field. Query values with spaces need to be URL encoded. */ + query?: string; + /** Filter by exact name match */ + name?: string; +}; +export type DeleteTeamByIdApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type DeleteTeamByIdApiArg = { + teamId: string; +}; +export type GetTeamByIdApiResponse = /** status 200 (empty) */ TeamDto; +export type GetTeamByIdApiArg = { + teamId: string; +}; +export type UpdateTeamApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateTeamApiArg = { + teamId: string; + updateTeamCommand: UpdateTeamCommand; +}; +export type GetTeamMembersApiResponse = /** status 200 (empty) */ TeamMemberDto[]; +export type GetTeamMembersApiArg = { + teamId: string; +}; +export type AddTeamMemberApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type AddTeamMemberApiArg = { + teamId: string; + addTeamMemberCommand: AddTeamMemberCommand; +}; +export type SetTeamMembershipsApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type SetTeamMembershipsApiArg = { + teamId: string; + setTeamMembershipsCommand: SetTeamMembershipsCommand; +}; +export type RemoveTeamMemberApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveTeamMemberApiArg = { + teamId: string; + userId: number; +}; +export type UpdateTeamMemberApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateTeamMemberApiArg = { + teamId: string; + userId: number; + updateTeamMemberCommand: UpdateTeamMemberCommand; +}; +export type GetTeamPreferencesApiResponse = /** status 200 (empty) */ PreferencesSpec; +export type GetTeamPreferencesApiArg = { + teamId: string; +}; +export type UpdateTeamPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateTeamPreferencesApiArg = { + teamId: string; + updatePrefsCmd: UpdatePrefsCmd; +}; +export type GetSignedInUserApiResponse = /** status 200 (empty) */ UserProfileDto; +export type GetSignedInUserApiArg = void; +export type UpdateSignedInUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateSignedInUserApiArg = { + /** To change the email, name, login, theme, provide another one. */ + updateUserCommand: UpdateUserCommand; +}; +export type GetUserAuthTokensApiResponse = /** status 200 (empty) */ UserToken[]; +export type GetUserAuthTokensApiArg = void; +export type UpdateUserEmailApiResponse = unknown; +export type UpdateUserEmailApiArg = void; +export type ClearHelpFlagsApiResponse = /** status 200 (empty) */ { + helpFlags1?: number; + message?: string; +}; +export type ClearHelpFlagsApiArg = void; +export type SetHelpFlagApiResponse = /** status 200 (empty) */ { + helpFlags1?: number; + message?: string; +}; +export type SetHelpFlagApiArg = { + flagId: string; +}; +export type GetSignedInUserOrgListApiResponse = /** status 200 (empty) */ UserOrgDto[]; +export type GetSignedInUserOrgListApiArg = void; +export type ChangeUserPasswordApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type ChangeUserPasswordApiArg = { + /** To change the email, name, login, theme, provide another one. */ + changeUserPasswordCommand: ChangeUserPasswordCommand; +}; +export type GetUserPreferencesApiResponse = /** status 200 (empty) */ PreferencesSpec; +export type GetUserPreferencesApiArg = void; +export type PatchUserPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type PatchUserPreferencesApiArg = { + patchPrefsCmd: PatchPrefsCmd; +}; +export type UpdateUserPreferencesApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateUserPreferencesApiArg = { + updatePrefsCmd: UpdatePrefsCmd; +}; +export type GetUserQuotasApiResponse = /** status 200 (empty) */ QuotaDto[]; +export type GetUserQuotasApiArg = void; +export type RevokeUserAuthTokenApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RevokeUserAuthTokenApiArg = { + revokeAuthTokenCmd: RevokeAuthTokenCmd; +}; +export type UnstarDashboardByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UnstarDashboardByUidApiArg = { + dashboardUid: string; +}; +export type StarDashboardByUidApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type StarDashboardByUidApiArg = { + dashboardUid: string; +}; +export type GetSignedInUserTeamListApiResponse = /** status 200 (empty) */ TeamDto[]; +export type GetSignedInUserTeamListApiArg = void; +export type UserSetUsingOrgApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UserSetUsingOrgApiArg = { + orgId: number; +}; +export type SearchUsersApiResponse = /** status 200 (empty) */ UserSearchHitDto[]; +export type SearchUsersApiArg = { + /** Limit the maximum number of users to return per page */ + perpage?: number; + /** Page index for starting fetching users */ + page?: number; +}; +export type GetUserByLoginOrEmailApiResponse = /** status 200 (empty) */ UserProfileDto; +export type GetUserByLoginOrEmailApiArg = { + /** loginOrEmail of the user */ + loginOrEmail: string; +}; +export type SearchUsersWithPagingApiResponse = /** status 200 (empty) */ SearchUserQueryResult; +export type SearchUsersWithPagingApiArg = void; +export type GetUserByIdApiResponse = /** status 200 (empty) */ UserProfileDto; +export type GetUserByIdApiArg = { + userId: number; +}; +export type UpdateUserApiResponse = + /** status 200 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateUserApiArg = { + userId: number; + /** To change the email, name, login, theme, provide another one. */ + updateUserCommand: UpdateUserCommand; +}; +export type GetUserOrgListApiResponse = /** status 200 (empty) */ UserOrgDto[]; +export type GetUserOrgListApiArg = { + userId: number; +}; +export type GetUserTeamsApiResponse = /** status 200 (empty) */ TeamDto[]; +export type GetUserTeamsApiArg = { + userId: number; +}; +export type RouteGetAlertRulesApiResponse = /** status 200 ProvisionedAlertRules */ ProvisionedAlertRulesRead; +export type RouteGetAlertRulesApiArg = void; +export type RoutePostAlertRuleApiResponse = /** status 201 ProvisionedAlertRule */ ProvisionedAlertRuleRead; +export type RoutePostAlertRuleApiArg = { + 'X-Disable-Provenance'?: string; + provisionedAlertRule: ProvisionedAlertRule; +}; +export type RouteGetAlertRulesExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetAlertRulesExportApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + /** UIDs of folders from which to export rules */ + folderUid?: string[]; + /** Name of group of rules to export. Must be specified only together with a single folder UID */ + group?: string; + /** UID of alert rule to export. If specified, parameters folderUid and group must be empty. */ + ruleUid?: string; +}; +export type RouteDeleteAlertRuleApiResponse = unknown; +export type RouteDeleteAlertRuleApiArg = { + /** Alert rule UID */ + uid: string; + 'X-Disable-Provenance'?: string; +}; +export type RouteGetAlertRuleApiResponse = /** status 200 ProvisionedAlertRule */ ProvisionedAlertRuleRead; +export type RouteGetAlertRuleApiArg = { + /** Alert rule UID */ + uid: string; +}; +export type RoutePutAlertRuleApiResponse = /** status 200 ProvisionedAlertRule */ ProvisionedAlertRuleRead; +export type RoutePutAlertRuleApiArg = { + /** Alert rule UID */ + uid: string; + 'X-Disable-Provenance'?: string; + provisionedAlertRule: ProvisionedAlertRule; +}; +export type RouteGetAlertRuleExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetAlertRuleExportApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + /** Alert rule UID */ + uid: string; +}; +export type RouteGetContactpointsApiResponse = /** status 200 ContactPoints */ ContactPointsRead; +export type RouteGetContactpointsApiArg = { + /** Filter by name */ + name?: string; +}; +export type RoutePostContactpointsApiResponse = /** status 202 EmbeddedContactPoint */ EmbeddedContactPointRead; +export type RoutePostContactpointsApiArg = { + 'X-Disable-Provenance'?: string; + embeddedContactPoint: EmbeddedContactPoint; +}; +export type RouteGetContactpointsExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetContactpointsExportApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + /** Whether any contained secure settings should be decrypted or left redacted. Redacted settings will contain RedactedValue instead. Currently, only org admin can view decrypted secure settings. */ + decrypt?: boolean; + /** Filter by name */ + name?: string; +}; +export type RouteDeleteContactpointsApiResponse = unknown; +export type RouteDeleteContactpointsApiArg = { + /** UID is the contact point unique identifier */ + uid: string; +}; +export type RoutePutContactpointApiResponse = /** status 202 Ack */ Ack; +export type RoutePutContactpointApiArg = { + /** UID is the contact point unique identifier */ + uid: string; + 'X-Disable-Provenance'?: string; + embeddedContactPoint: EmbeddedContactPoint; +}; +export type RouteDeleteAlertRuleGroupApiResponse = unknown; +export type RouteDeleteAlertRuleGroupApiArg = { + folderUid: string; + group: string; +}; +export type RouteGetAlertRuleGroupApiResponse = /** status 200 AlertRuleGroup */ AlertRuleGroupRead; +export type RouteGetAlertRuleGroupApiArg = { + folderUid: string; + group: string; +}; +export type RoutePutAlertRuleGroupApiResponse = /** status 200 AlertRuleGroup */ AlertRuleGroupRead; +export type RoutePutAlertRuleGroupApiArg = { + 'X-Disable-Provenance'?: string; + folderUid: string; + group: string; + alertRuleGroup: AlertRuleGroup; +}; +export type RouteGetAlertRuleGroupExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetAlertRuleGroupExportApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + folderUid: string; + group: string; +}; +export type RouteGetMuteTimingsApiResponse = /** status 200 MuteTimings */ MuteTimings; +export type RouteGetMuteTimingsApiArg = void; +export type RoutePostMuteTimingApiResponse = + /** status 201 MuteTimeInterval */ MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +export type RoutePostMuteTimingApiArg = { + 'X-Disable-Provenance'?: string; + muteTimeInterval: MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +}; +export type RouteExportMuteTimingsApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteExportMuteTimingsApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; +}; +export type RouteDeleteMuteTimingApiResponse = unknown; +export type RouteDeleteMuteTimingApiArg = { + /** Mute timing name */ + name: string; + /** Version of mute timing to use for optimistic concurrency. Leave empty to disable validation */ + version?: string; + 'X-Disable-Provenance'?: string; +}; +export type RouteGetMuteTimingApiResponse = + /** status 200 MuteTimeInterval */ MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +export type RouteGetMuteTimingApiArg = { + /** Mute timing name */ + name: string; +}; +export type RoutePutMuteTimingApiResponse = + /** status 202 MuteTimeInterval */ MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +export type RoutePutMuteTimingApiArg = { + /** Mute timing name */ + name: string; + 'X-Disable-Provenance'?: string; + muteTimeInterval: MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted; +}; +export type RouteExportMuteTimingApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteExportMuteTimingApiArg = { + /** Whether to initiate a download of the file or not. */ + download?: boolean; + /** Format of the downloaded file. Supported yaml, json or hcl. Accept header can also be used, but the query parameter will take precedence. */ + format?: 'yaml' | 'json' | 'hcl'; + /** Mute timing name */ + name: string; +}; +export type RouteResetPolicyTreeApiResponse = /** status 202 Ack */ Ack; +export type RouteResetPolicyTreeApiArg = void; +export type RouteGetPolicyTreeApiResponse = /** status 200 Route */ Route; +export type RouteGetPolicyTreeApiArg = void; +export type RoutePutPolicyTreeApiResponse = /** status 202 Ack */ Ack; +export type RoutePutPolicyTreeApiArg = { + 'X-Disable-Provenance'?: string; + /** The new notification routing tree to use */ + route: Route; +}; +export type RouteGetPolicyTreeExportApiResponse = + /** status 200 AlertingFileExport */ AlertingFileExportIsTheFullProvisionedFileExport; +export type RouteGetPolicyTreeExportApiArg = void; +export type RouteGetTemplatesApiResponse = /** status 200 NotificationTemplates */ NotificationTemplates; +export type RouteGetTemplatesApiArg = void; +export type RouteDeleteTemplateApiResponse = unknown; +export type RouteDeleteTemplateApiArg = { + /** Template group name */ + name: string; + /** Version of template to use for optimistic concurrency. Leave empty to disable validation */ + version?: string; +}; +export type RouteGetTemplateApiResponse = /** status 200 NotificationTemplate */ NotificationTemplate; +export type RouteGetTemplateApiArg = { + /** Template group name */ + name: string; +}; +export type RoutePutTemplateApiResponse = /** status 202 NotificationTemplate */ NotificationTemplate; +export type RoutePutTemplateApiArg = { + /** Template group name */ + name: string; + 'X-Disable-Provenance'?: string; + notificationTemplateContent: NotificationTemplateContent; +}; +export type ListAllProvidersSettingsApiResponse = /** status 200 (empty) */ { + id?: string; + provider?: string; + settings?: { + [key: string]: any; + }; + source?: string; +}[]; +export type ListAllProvidersSettingsApiArg = void; +export type RemoveProviderSettingsApiResponse = + /** status 204 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type RemoveProviderSettingsApiArg = { + key: string; +}; +export type GetProviderSettingsApiResponse = /** status 200 (empty) */ { + id?: string; + provider?: string; + settings?: { + [key: string]: any; + }; + source?: string; +}; +export type GetProviderSettingsApiArg = { + key: string; +}; +export type UpdateProviderSettingsApiResponse = + /** status 204 An OKResponse is returned if the request was successful. */ SuccessResponseBody; +export type UpdateProviderSettingsApiArg = { + key: string; + body: { + id?: string; + provider?: string; + settings?: { + [key: string]: any; + }; + }; +}; +export type SearchResultItem = { + action?: string; + basicRole?: string; + orgId?: number; + roleName?: string; + scope?: string; + teamId?: number; + userId?: number; + version?: number; +}; +export type SearchResult = { + result?: SearchResultItem[]; +}; +export type ErrorResponseBody = { + /** Error An optional detailed description of the actual error. Only included if running in developer mode. */ + error?: string; + /** a human readable version of the error */ + message: string; + /** Status An optional status to denote the cause of the error. + + For example, a 412 Precondition Failed error may include additional information of why that error happened. */ + status?: string; +}; +export type PermissionIsTheModelForAccessControlPermissions = { + action?: string; + created?: string; + scope?: string; + updated?: string; +}; +export type RoleDto = { + created?: string; + delegatable?: boolean; + description?: string; + displayName?: string; + global?: boolean; + group?: string; + hidden?: boolean; + mapped?: boolean; + name?: string; + permissions?: PermissionIsTheModelForAccessControlPermissions[]; + uid?: string; + updated?: string; + version?: number; +}; +export type CreateRoleForm = { + description?: string; + displayName?: string; + global?: boolean; + group?: string; + hidden?: boolean; + name?: string; + permissions?: PermissionIsTheModelForAccessControlPermissions[]; + uid?: string; + version?: number; +}; +export type SuccessResponseBody = { + message?: string; +}; +export type UpdateRoleCommand = { + description: string; + displayName: string; + global?: boolean; + group: string; + hidden?: boolean; + name?: string; + permissions?: PermissionIsTheModelForAccessControlPermissions[]; + version?: number; +}; +export type RoleAssignmentsDto = { + role_uid?: string; + service_accounts?: number[]; + teams?: number[]; + users?: number[]; +}; +export type SetRoleAssignmentsCommand = { + service_accounts?: number[]; + teams?: number[]; + users?: number[]; +}; +export type Status = number; +export type RolesSearchQuery = { + includeHidden?: boolean; + orgId?: number; + teamIds?: number[]; + userIds?: number[]; +}; +export type AddTeamRoleCommand = { + roleUid?: string; +}; +export type AddUserRoleCommand = { + global?: boolean; + roleUid?: string; +}; +export type SetUserRolesCommand = { + global?: boolean; + includeHidden?: boolean; + roleUids?: string[]; +}; +export type Assignments = { + builtInRoles?: boolean; + serviceAccounts?: boolean; + teams?: boolean; + users?: boolean; +}; +export type Description = { + assignments?: Assignments; + permissions?: string[]; +}; +export type ResourcePermissionDto = { + actions?: string[]; + builtInRole?: string; + id?: number; + isInherited?: boolean; + isManaged?: boolean; + isServiceAccount?: boolean; + permission?: string; + roleName?: string; + team?: string; + teamAvatarUrl?: string; + teamId?: number; + teamUid?: string; + userAvatarUrl?: string; + userId?: number; + userLogin?: string; + userUid?: string; +}; +export type SetResourcePermissionCommand = { + builtInRole?: string; + permission?: string; + teamId?: number; + userId?: number; +}; +export type SetPermissionsCommand = { + permissions?: SetResourcePermissionCommand[]; +}; +export type SetPermissionCommand = { + permission?: string; +}; +export type Duration = number; +export type FailedUser = { + Error?: string; + Login?: string; +}; +export type SyncResultHoldsTheResultOfASyncWithLdapThisGivesUsInformationOnWhichUsersWereUpdatedAndHow = { + Elapsed?: Duration; + FailedUsers?: FailedUser[]; + MissingUserIds?: number[]; + Started?: string; + UpdatedUserIds?: number[]; +}; +export type ActiveSyncStatusDto = { + enabled?: boolean; + nextSync?: string; + prevSync?: SyncResultHoldsTheResultOfASyncWithLdapThisGivesUsInformationOnWhichUsersWereUpdatedAndHow; + schedule?: string; +}; +export type SettingsBag = { + [key: string]: { + [key: string]: string; + }; +}; +export type AdminStats = { + activeAdmins?: number; + activeDevices?: number; + activeEditors?: number; + activeSessions?: number; + activeUsers?: number; + activeViewers?: number; + admins?: number; + alerts?: number; + dailyActiveAdmins?: number; + dailyActiveEditors?: number; + dailyActiveSessions?: number; + dailyActiveUsers?: number; + dailyActiveViewers?: number; + dashboards?: number; + datasources?: number; + editors?: number; + monthlyActiveUsers?: number; + orgs?: number; + playlists?: number; + snapshots?: number; + stars?: number; + tags?: number; + users?: number; + viewers?: number; +}; +export type AdminCreateUserResponse = { + id?: number; + message?: string; + uid?: string; +}; +export type Password = string; +export type AdminCreateUserForm = { + email?: string; + login?: string; + name?: string; + orgId?: number; + password?: Password; +}; +export type UserToken = { + AuthToken?: string; + AuthTokenSeen?: boolean; + ClientIp?: string; + CreatedAt?: number; + ExternalSessionId?: number; + Id?: number; + PrevAuthToken?: string; + RevokedAt?: number; + RotatedAt?: number; + SeenAt?: number; + UnhashedToken?: string; + UpdatedAt?: number; + UserAgent?: string; + UserId?: number; +}; +export type AdminUpdateUserPasswordForm = { + password?: Password; +}; +export type AdminUpdateUserPermissionsForm = { + isGrafanaAdmin?: boolean; +}; +export type QuotaDto = { + limit?: number; + org_id?: number; + target?: string; + used?: number; + user_id?: number; +}; +export type UpdateQuotaCmd = { + limit?: number; + target?: string; +}; +export type RevokeAuthTokenCmd = { + authTokenId?: number; +}; +export type Json = object; +export type Annotation = { + alertId?: number; + alertName?: string; + avatarUrl?: string; + created?: number; + /** Deprecated: Use DashboardUID and OrgID instead */ + dashboardId?: number; + dashboardUID?: string; + data?: Json; + email?: string; + id?: number; + login?: string; + newState?: string; + panelId?: number; + prevState?: string; + tags?: string[]; + text?: string; + time?: number; + timeEnd?: number; + updated?: number; + userId?: number; +}; +export type PostAnnotationsCmd = { + dashboardId?: number; + dashboardUID?: string; + data?: Json; + panelId?: number; + tags?: string[]; + text: string; + time?: number; + timeEnd?: number; +}; +export type PostGraphiteAnnotationsCmd = { + data?: string; + tags?: any; + what?: string; + when?: number; +}; +export type MassDeleteAnnotationsCmd = { + annotationId?: number; + dashboardId?: number; + dashboardUID?: string; + panelId?: number; +}; +export type TagsDtoIsTheFrontendDtoForTag = { + count?: number; + tag?: string; +}; +export type FindTagsResultIsTheResultOfATagsSearch = { + tags?: TagsDtoIsTheFrontendDtoForTag[]; +}; +export type GetAnnotationTagsResponseIsAResponseStructForFindTagsResult = { + result?: FindTagsResultIsTheResultOfATagsSearch; +}; +export type PatchAnnotationsCmd = { + data?: Json; + id?: number; + tags?: string[]; + text?: string; + time?: number; + timeEnd?: number; +}; +export type UpdateAnnotationsCmd = { + data?: Json; + id?: number; + tags?: string[]; + text?: string; + time?: number; + timeEnd?: number; +}; +export type DeviceDto = { + avatarUrl?: string; + clientIp?: string; + createdAt?: string; + deviceId?: string; + lastSeenAt?: string; + updatedAt?: string; + userAgent?: string; +}; +export type DeviceSearchHitDto = { + clientIp?: string; + createdAt?: string; + deviceId?: string; + lastSeenAt?: string; + updatedAt?: string; + userAgent?: string; +}; +export type SearchDeviceQueryResult = { + devices?: DeviceSearchHitDto[]; + page?: number; + perPage?: number; + totalCount?: number; +}; +export type CloudMigrationSessionResponseDto = { + created?: string; + slug?: string; + uid?: string; + updated?: string; +}; +export type CloudMigrationSessionListResponseDto = { + sessions?: CloudMigrationSessionResponseDto[]; +}; +export type CloudMigrationSessionRequestDto = { + authToken?: string; +}; +export type CreateSnapshotResponseDto = { + uid?: string; +}; +export type CreateSnapshotRequestDto = { + resourceTypes?: ( + | 'DASHBOARD' + | 'DATASOURCE' + | 'FOLDER' + | 'LIBRARY_ELEMENT' + | 'ALERT_RULE' + | 'ALERT_RULE_GROUP' + | 'CONTACT_POINT' + | 'NOTIFICATION_POLICY' + | 'NOTIFICATION_TEMPLATE' + | 'MUTE_TIMING' + | 'PLUGIN' + )[]; +}; +export type MigrateDataResponseItemDto = { + errorCode?: + | 'ALERT_RULES_QUOTA_REACHED' + | 'ALERT_RULES_GROUP_QUOTA_REACHED' + | 'DATASOURCE_NAME_CONFLICT' + | 'DATASOURCE_INVALID_URL' + | 'DATASOURCE_ALREADY_MANAGED' + | 'FOLDER_NAME_CONFLICT' + | 'DASHBOARD_ALREADY_MANAGED' + | 'LIBRARY_ELEMENT_NAME_CONFLICT' + | 'UNSUPPORTED_DATA_TYPE' + | 'RESOURCE_CONFLICT' + | 'UNEXPECTED_STATUS_CODE' + | 'INTERNAL_SERVICE_ERROR' + | 'GENERIC_ERROR'; + message?: string; + name?: string; + parentName?: string; + refId: string; + status: 'OK' | 'WARNING' | 'ERROR' | 'PENDING' | 'UNKNOWN'; + type: + | 'DASHBOARD' + | 'DATASOURCE' + | 'FOLDER' + | 'LIBRARY_ELEMENT' + | 'ALERT_RULE' + | 'ALERT_RULE_GROUP' + | 'CONTACT_POINT' + | 'NOTIFICATION_POLICY' + | 'NOTIFICATION_TEMPLATE' + | 'MUTE_TIMING' + | 'PLUGIN'; +}; +export type SnapshotResourceStats = { + statuses?: { + [key: string]: number; + }; + total?: number; + types?: { + [key: string]: number; + }; +}; +export type GetSnapshotResponseDto = { + created?: string; + finished?: string; + results?: MigrateDataResponseItemDto[]; + sessionUid?: string; + stats?: SnapshotResourceStats; + status?: + | 'INITIALIZING' + | 'CREATING' + | 'PENDING_UPLOAD' + | 'UPLOADING' + | 'PENDING_PROCESSING' + | 'PROCESSING' + | 'FINISHED' + | 'CANCELED' + | 'ERROR' + | 'UNKNOWN'; + uid?: string; +}; +export type SnapshotDto = { + created?: string; + finished?: string; + sessionUid?: string; + status?: + | 'INITIALIZING' + | 'CREATING' + | 'PENDING_UPLOAD' + | 'UPLOADING' + | 'PENDING_PROCESSING' + | 'PROCESSING' + | 'FINISHED' + | 'CANCELED' + | 'ERROR' + | 'UNKNOWN'; + uid?: string; +}; +export type SnapshotListResponseDto = { + snapshots?: SnapshotDto[]; +}; +export type ResourceDependencyDto = { + dependencies?: ( + | 'DASHBOARD' + | 'DATASOURCE' + | 'FOLDER' + | 'LIBRARY_ELEMENT' + | 'ALERT_RULE' + | 'ALERT_RULE_GROUP' + | 'CONTACT_POINT' + | 'NOTIFICATION_POLICY' + | 'NOTIFICATION_TEMPLATE' + | 'MUTE_TIMING' + | 'PLUGIN' + )[]; + resourceType?: + | 'DASHBOARD' + | 'DATASOURCE' + | 'FOLDER' + | 'LIBRARY_ELEMENT' + | 'ALERT_RULE' + | 'ALERT_RULE_GROUP' + | 'CONTACT_POINT' + | 'NOTIFICATION_POLICY' + | 'NOTIFICATION_TEMPLATE' + | 'MUTE_TIMING' + | 'PLUGIN'; +}; +export type ResourceDependenciesResponseDto = { + resourceDependencies?: ResourceDependencyDto[]; +}; +export type GetAccessTokenResponseDto = { + createdAt?: string; + displayName?: string; + expiresAt?: string; + firstUsedAt?: string; + id?: string; + lastUsedAt?: string; +}; +export type CreateAccessTokenResponseDto = { + token?: string; +}; +export type ConvertPrometheusResponse = { + error?: string; + errorType?: string; + status?: string; +}; +export type PublicError = { + extra?: { + [key: string]: any; + }; + message?: string; + messageId?: string; + statusCode?: number; +}; +export type ForbiddenError = { + body?: PublicError; +}; +export type PrometheusRule = { + alert?: string; + annotations?: { + [key: string]: string; + }; + expr?: string; + for?: string; + keep_firing_for?: string; + labels?: { + [key: string]: string; + }; + record?: string; +}; +export type PrometheusRuleGroup = { + interval?: Duration; + labels?: { + [key: string]: string; + }; + limit?: number; + name?: string; + query_offset?: string; + rules?: PrometheusRule[]; +}; +export type DashboardSnapshotDto = { + created?: string; + expires?: string; + external?: boolean; + externalUrl?: string; + key?: string; + name?: string; + updated?: string; +}; +export type CalculateDiffTarget = { + dashboardId?: number; + unsavedDashboard?: Json; + version?: number; +}; +export type SaveDashboardCommand = { + UpdatedAt?: string; + dashboard?: Json; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + isFolder?: boolean; + message?: string; + overwrite?: boolean; + userId?: number; +}; +export type AnnotationActions = { + canAdd?: boolean; + canDelete?: boolean; + canEdit?: boolean; +}; +export type AnnotationPermission = { + dashboard?: AnnotationActions; + organization?: AnnotationActions; +}; +export type DashboardMeta = { + annotationsPermissions?: AnnotationPermission; + apiVersion?: string; + canAdmin?: boolean; + canDelete?: boolean; + canEdit?: boolean; + canSave?: boolean; + canStar?: boolean; + created?: string; + createdBy?: string; + expires?: string; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderTitle?: string; + folderUid?: string; + folderUrl?: string; + hasAcl?: boolean; + isFolder?: boolean; + isSnapshot?: boolean; + isStarred?: boolean; + provisioned?: boolean; + provisionedExternalId?: string; + publicDashboardEnabled?: boolean; + slug?: string; + type?: string; + updated?: string; + updatedBy?: string; + url?: string; + version?: number; +}; +export type GetHomeDashboardResponse = { + dashboard?: Json; + meta?: DashboardMeta; +} & { + redirectUri?: string; +}; +export type ImportDashboardResponseResponseObjectReturnedWhenImportingADashboard = { + dashboardId?: number; + description?: string; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + imported?: boolean; + importedRevision?: number; + importedUri?: string; + importedUrl?: string; + path?: string; + pluginId?: string; + removed?: boolean; + revision?: number; + slug?: string; + title?: string; + uid?: string; +}; +export type ImportDashboardInputDefinitionOfInputParametersWhenImportingADashboard = { + name?: string; + pluginId?: string; + type?: string; + value?: string; +}; +export type ImportDashboardRequestRequestObjectForImportingADashboard = { + dashboard?: Json; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + inputs?: ImportDashboardInputDefinitionOfInputParametersWhenImportingADashboard[]; + overwrite?: boolean; + path?: string; + pluginId?: string; +}; +export type PublicDashboardListResponse = { + accessToken?: string; + dashboardUid?: string; + isEnabled?: boolean; + slug?: string; + title?: string; + uid?: string; +}; +export type PublicDashboardListResponseWithPagination = { + page?: number; + perPage?: number; + publicDashboards?: PublicDashboardListResponse[]; + totalCount?: number; +}; +export type PublicError2 = { + /** Extra Additional information about the error */ + extra?: { + [key: string]: any; + }; + /** Message A human readable message */ + message?: string; + /** MessageID A unique identifier for the error */ + messageId: string; + /** StatusCode The HTTP status code returned */ + statusCode: number; +}; +export type DashboardTagCloudItem = { + count?: number; + term?: string; +}; +export type EmailDto = { + recipient?: string; + uid?: string; +}; +export type ShareType = string; +export type PublicDashboard = { + accessToken?: string; + annotationsEnabled?: boolean; + createdAt?: string; + createdBy?: number; + dashboardUid?: string; + isEnabled?: boolean; + recipients?: EmailDto[]; + share?: ShareType; + timeSelectionEnabled?: boolean; + uid?: string; + updatedAt?: string; + updatedBy?: number; +}; +export type PublicDashboardDto = { + accessToken?: string; + annotationsEnabled?: boolean; + isEnabled?: boolean; + share?: ShareType; + timeSelectionEnabled?: boolean; + uid?: string; +}; +export type DashboardFullWithMeta = { + dashboard?: Json; + meta?: DashboardMeta; +}; +export type PermissionType = number; +export type DashboardAclInfoDto = { + created?: string; + dashboardId?: number; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + inherited?: boolean; + isFolder?: boolean; + permission?: PermissionType; + permissionName?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + slug?: string; + team?: string; + teamAvatarUrl?: string; + teamEmail?: string; + teamId?: number; + teamUid?: string; + title?: string; + uid?: string; + updated?: string; + url?: string; + userAvatarUrl?: string; + userEmail?: string; + userId?: number; + userLogin?: string; + userUid?: string; +}; +export type DashboardAclUpdateItem = { + permission?: PermissionType; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + teamId?: number; + userId?: number; +}; +export type UpdateDashboardAclCommand = { + items?: DashboardAclUpdateItem[]; +}; +export type RestoreDashboardVersionCommand = { + version?: number; +}; +export type DashboardVersionMeta = { + created?: string; + createdBy?: string; + dashboardId?: number; + data?: Json; + id?: number; + message?: string; + parentVersion?: number; + restoredFrom?: number; + uid?: string; + version?: number; +}; +export type DashboardVersionResponseMeta = { + continueToken?: string; + versions?: DashboardVersionMeta[]; +}; +export type DsAccess = string; +export type DataSourceListItemDto = { + access?: DsAccess; + basicAuth?: boolean; + database?: string; + id?: number; + isDefault?: boolean; + jsonData?: Json; + name?: string; + orgId?: number; + readOnly?: boolean; + type?: string; + typeLogoUrl?: string; + typeName?: string; + uid?: string; + url?: string; + user?: string; +}; +export type DataSourceList = DataSourceListItemDto[]; +export type Metadata = { + [key: string]: boolean; +}; +export type DataSource = { + access?: DsAccess; + accessControl?: Metadata; + basicAuth?: boolean; + basicAuthUser?: string; + database?: string; + id?: number; + isDefault?: boolean; + jsonData?: Json; + name?: string; + orgId?: number; + readOnly?: boolean; + secureJsonFields?: { + [key: string]: boolean; + }; + type?: string; + typeLogoUrl?: string; + uid?: string; + url?: string; + user?: string; + version?: number; + withCredentials?: boolean; +}; +export type AddDataSourceCommand = { + access?: DsAccess; + basicAuth?: boolean; + basicAuthUser?: string; + database?: string; + isDefault?: boolean; + jsonData?: Json; + name?: string; + secureJsonData?: { + [key: string]: string; + }; + type?: string; + uid?: string; + url?: string; + user?: string; + withCredentials?: boolean; +}; +export type Transformation = { + expression?: string; + field?: string; + mapValue?: string; + type?: 'regex' | 'logfmt'; +}; +export type Transformations = Transformation[]; +export type CorrelationType = string; +export type CorrelationConfig = { + /** Field used to attach the correlation link */ + field: string; + /** Target data query */ + target: { + [key: string]: any; + }; + transformations?: Transformations; + type?: CorrelationType; +}; +export type Correlation = { + config?: CorrelationConfig; + /** Description of the correlation */ + description?: string; + /** Label identifying the correlation */ + label?: string; + /** OrgID of the data source the correlation originates from */ + orgId?: number; + /** Provisioned True if the correlation was created during provisioning */ + provisioned?: boolean; + /** UID of the data source the correlation originates from */ + sourceUID?: string; + /** UID of the data source the correlation points to */ + targetUID?: string; + type?: CorrelationType; + /** Unique identifier of the correlation */ + uid?: string; +}; +export type CreateCorrelationResponseBody = { + message?: string; + result?: Correlation; +}; +export type CreateCorrelationCommand = { + config?: CorrelationConfig; + /** Optional description of the correlation */ + description?: string; + /** Optional label identifying the correlation */ + label?: string; + /** True if correlation was created with provisioning. This makes it read-only. */ + provisioned?: boolean; + /** Target data source UID to which the correlation is created. required if type = query */ + targetUID?: string; + type?: CorrelationType; +}; +export type UpdateCorrelationResponseBody = { + message?: string; + result?: Correlation; +}; +export type CorrelationConfigUpdateDto = { + /** Field used to attach the correlation link */ + field?: string; + /** Target data query */ + target?: { + [key: string]: any; + }; + /** Source data transformations */ + transformations?: Transformation[]; +}; +export type UpdateCorrelationCommand = { + config?: CorrelationConfigUpdateDto; + /** Optional description of the correlation */ + description?: string; + /** Optional label identifying the correlation */ + label?: string; + type?: CorrelationType; +}; +export type UpdateDataSourceCommand = { + access?: DsAccess; + basicAuth?: boolean; + basicAuthUser?: string; + database?: string; + isDefault?: boolean; + jsonData?: Json; + name?: string; + secureJsonData?: { + [key: string]: string; + }; + type?: string; + uid?: string; + url?: string; + user?: string; + /** The previous version -- used for optimistic locking */ + version?: number; + withCredentials?: boolean; +}; +export type DeleteCorrelationResponseBody = { + message?: string; +}; +export type TeamLbacRule = { + rules?: string[]; + teamId?: string; + teamUid?: string; +}; +export type TeamLbacRules = { + rules?: TeamLbacRule[]; +}; +export type UpdateTeamLbacCommand = { + rules?: TeamLbacRule[]; +}; +export type CacheConfigResponse = { + created?: string; + /** Fields that can be set by the API caller - read/write */ + dataSourceID?: number; + dataSourceUID?: string; + /** These are returned by the HTTP API, but are managed internally - read-only + Note: 'created' and 'updated' are special properties managed automatically by xorm, but we are setting them manually */ + defaultTTLMs?: number; + enabled?: boolean; + message?: string; + /** TTL MS, or "time to live", is how long a cached item will stay in the cache before it is removed (in milliseconds) */ + ttlQueriesMs?: number; + ttlResourcesMs?: number; + updated?: string; + /** If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini */ + useDefaultTTL?: boolean; +}; +export type CacheConfigSetter = { + dataSourceID?: number; + dataSourceUID?: string; + enabled?: boolean; + /** TTL MS, or "time to live", is how long a cached item will stay in the cache before it is removed (in milliseconds) */ + ttlQueriesMs?: number; + ttlResourcesMs?: number; + /** If UseDefaultTTL is enabled, then the TTLQueriesMS and TTLResourcesMS in this object is always sent as the default TTL located in grafana.ini */ + useDefaultTTL?: boolean; +}; +export type SourceTypeDefinesTheStatusSource = string; +export type ExplorePanelsState = any; +export type TimeRange = { + from?: string; + to?: string; +}; +export type SupportedTransformationTypes = string; +export type LinkTransformationConfig = { + expression?: string; + field?: string; + mapValue?: string; + type?: SupportedTransformationTypes; +}; +export type InternalDataLink = { + datasourceName?: string; + datasourceUid?: string; + panelsState?: ExplorePanelsState; + query?: any; + timeRange?: TimeRange; + transformations?: LinkTransformationConfig[]; +}; +export type DataLink = { + internal?: InternalDataLink; + targetBlank?: boolean; + title?: string; + url?: string; +}; +export type ValueMapping = object; +export type ValueMappings = ValueMapping[]; +export type ConfFloat64 = number; +export type ThresholdsMode = string; +export type Threshold = { + color?: string; + state?: string; + value?: ConfFloat64; +}; +export type ThresholdsConfig = { + mode?: ThresholdsMode; + /** Must be sorted by 'value', first value is always -Infinity */ + steps?: Threshold[]; +}; +export type EnumFieldConfig = { + /** Color is the color value for a given index (empty is undefined) */ + color?: string[]; + /** Description of the enum state */ + description?: string[]; + /** Icon supports setting an icon for a given index value */ + icon?: string[]; + /** Value is the string display value for a given index */ + text?: string[]; +}; +export type FieldTypeConfig = { + enum?: EnumFieldConfig; +}; +export type FieldConfigRepresentsTheDisplayPropertiesForAField = { + /** Map values to a display color + NOTE: this interface is under development in the frontend... so simple map for now */ + color?: { + [key: string]: any; + }; + /** Panel Specific Values */ + custom?: { + [key: string]: any; + }; + decimals?: number; + /** Description is human readable field metadata */ + description?: string; + /** DisplayName overrides Grafana default naming, should not be used from a data source */ + displayName?: string; + /** DisplayNameFromDS overrides Grafana default naming strategy. */ + displayNameFromDS?: string; + /** Filterable indicates if the Field's data can be filtered by additional calls. */ + filterable?: boolean; + /** Interval indicates the expected regular step between values in the series. + When an interval exists, consumers can identify "missing" values when the expected value is not present. + The grafana timeseries visualization will render disconnected values when missing values are found it the time field. + The interval uses the same units as the values. For time.Time, this is defined in milliseconds. */ + interval?: number; + /** The behavior when clicking on a result */ + links?: DataLink[]; + mappings?: ValueMappings; + max?: ConfFloat64; + min?: ConfFloat64; + /** Alternative to empty string */ + noValue?: string; + /** Path is an explicit path to the field in the datasource. When the frame meta includes a path, + this will default to `${frame.meta.path}/${field.name} + + When defined, this value can be used as an identifier within the datasource scope, and + may be used as an identifier to update values in a subsequent request */ + path?: string; + thresholds?: ThresholdsConfig; + type?: FieldTypeConfig; + /** Numeric Options */ + unit?: string; + /** Writeable indicates that the datasource knows how to update this value */ + writeable?: boolean; +}; +export type FrameLabels = { + [key: string]: string; +}; +export type FieldRepresentsATypedColumnOfDataWithinAFrame = { + config?: FieldConfigRepresentsTheDisplayPropertiesForAField; + labels?: FrameLabels; + /** Name is default identifier of the field. The name does not have to be unique, but the combination + of name and Labels should be unique for proper behavior in all situations. */ + name?: string; +}; +export type DataTopicIsUsedToIdentifyWhichTopicTheFrameShouldBeAssignedTo = string; +export type InspectTypeIsATypeForTheInspectPropertyOfANotice = number; +export type NoticeSeverityIsATypeForTheSeverityPropertyOfANotice = number; +export type NoticeProvidesAStructureForPresentingNotificationsInGrafanasUserInterface = { + inspect?: InspectTypeIsATypeForTheInspectPropertyOfANotice; + /** Link is an optional link for display in the user interface and can be an + absolute URL or a path relative to Grafana's root url. */ + link?: string; + severity?: NoticeSeverityIsATypeForTheSeverityPropertyOfANotice; + /** Text is freeform descriptive text for the notice. */ + text?: string; +}; +export type VisTypeIsUsedToIndicateHowTheDataShouldBeVisualizedInExplore = string; +export type QueryStatIsUsedForStoringArbitraryStatisticsMetadataRelatedToAQueryAndItsResultEGTotalRequestTimeDataProcessingTime = + { + /** Map values to a display color + NOTE: this interface is under development in the frontend... so simple map for now */ + color?: { + [key: string]: any; + }; + /** Panel Specific Values */ + custom?: { + [key: string]: any; + }; + decimals?: number; + /** Description is human readable field metadata */ + description?: string; + /** DisplayName overrides Grafana default naming, should not be used from a data source */ + displayName?: string; + /** DisplayNameFromDS overrides Grafana default naming strategy. */ + displayNameFromDS?: string; + /** Filterable indicates if the Field's data can be filtered by additional calls. */ + filterable?: boolean; + /** Interval indicates the expected regular step between values in the series. + When an interval exists, consumers can identify "missing" values when the expected value is not present. + The grafana timeseries visualization will render disconnected values when missing values are found it the time field. + The interval uses the same units as the values. For time.Time, this is defined in milliseconds. */ + interval?: number; + /** The behavior when clicking on a result */ + links?: DataLink[]; + mappings?: ValueMappings; + max?: ConfFloat64; + min?: ConfFloat64; + /** Alternative to empty string */ + noValue?: string; + /** Path is an explicit path to the field in the datasource. When the frame meta includes a path, + this will default to `${frame.meta.path}/${field.name} + + When defined, this value can be used as an identifier within the datasource scope, and + may be used as an identifier to update values in a subsequent request */ + path?: string; + thresholds?: ThresholdsConfig; + type?: FieldTypeConfig; + /** Numeric Options */ + unit?: string; + value?: number; + /** Writeable indicates that the datasource knows how to update this value */ + writeable?: boolean; + }; +export type FrameType = string; +export type FrameTypeIsA2NumberVersionMajorMinor = number[]; +export type FrameMetaMatches = { + /** Channel is the path to a stream in grafana live that has real-time updates for this data. */ + channel?: string; + /** Custom datasource specific values. */ + custom?: any; + dataTopic?: DataTopicIsUsedToIdentifyWhichTopicTheFrameShouldBeAssignedTo; + /** ExecutedQueryString is the raw query sent to the underlying system. All macros and templating + have been applied. When metadata contains this value, it will be shown in the query inspector. */ + executedQueryString?: string; + /** Notices provide additional information about the data in the Frame that + Grafana can display to the user in the user interface. */ + notices?: NoticeProvidesAStructureForPresentingNotificationsInGrafanasUserInterface[]; + /** Path is a browsable path on the datasource. */ + path?: string; + /** PathSeparator defines the separator pattern to decode a hierarchy. The default separator is '/'. */ + pathSeparator?: string; + /** PreferredVisualizationPluginId sets the panel plugin id to use to render the data when using Explore. If + the plugin cannot be found will fall back to PreferredVisualization. */ + preferredVisualisationPluginId?: string; + preferredVisualisationType?: VisTypeIsUsedToIndicateHowTheDataShouldBeVisualizedInExplore; + /** Stats is an array of query result statistics. */ + stats?: QueryStatIsUsedForStoringArbitraryStatisticsMetadataRelatedToAQueryAndItsResultEGTotalRequestTimeDataProcessingTime[]; + type?: FrameType; + typeVersion?: FrameTypeIsA2NumberVersionMajorMinor; + /** Array of field indices which values create a unique id for each row. Ideally this should be globally unique ID + but that isn't guarantied. Should help with keeping track and deduplicating rows in visualizations, especially + with streaming data with frequent updates. */ + uniqueRowIdFields?: number[]; +}; +export type FrameIsAColumnarDataStructureWhereEachColumnIsAField = { + /** Fields are the columns of a frame. + All Fields must be of the same the length when marshalling the Frame for transmission. + There should be no `nil` entries in the Fields slice (making them pointers was a mistake). */ + Fields?: FieldRepresentsATypedColumnOfDataWithinAFrame[]; + Meta?: FrameMetaMatches; + /** Name is used in some Grafana visualizations. */ + Name?: string; + /** RefID is a property that can be set to match a Frame to its originating query. */ + RefID?: string; +}; +export type FramesIsASliceOfFramePointers = FrameIsAColumnarDataStructureWhereEachColumnIsAField[]; +export type DataResponseContainsTheResultsFromADataQuery = { + /** Error is a property to be set if the corresponding DataQuery has an error. */ + Error?: string; + ErrorSource?: SourceTypeDefinesTheStatusSource; + Frames?: FramesIsASliceOfFramePointers; + Status?: Status; +}; +export type ResponsesIsAMapOfRefIDsUniqueQueryIdToDataResponses = { + [key: string]: DataResponseContainsTheResultsFromADataQuery; +}; +export type QueryDataResponseContainsTheResultsFromAQueryDataRequest = { + results?: ResponsesIsAMapOfRefIDsUniqueQueryIdToDataResponses; +}; +export type MetricRequest = { + debug?: boolean; + /** From Start time in epoch timestamps in milliseconds or relative using Grafana time units. */ + from: string; + /** queries.refId – Specifies an identifier of the query. Is optional and default to “A”. + queries.datasourceId – Specifies the data source to be queried. Each query in the request must have an unique datasourceId. + queries.maxDataPoints - Species maximum amount of data points that dashboard panel can render. Is optional and default to 100. + queries.intervalMs - Specifies the time interval in milliseconds of time series. Is optional and defaults to 1000. */ + queries: Json[]; + /** To End time in epoch timestamps in milliseconds or relative using Grafana time units. */ + to: string; +}; +export type ManagerKindIsTheTypeOfManagerWhichIsResponsibleForManagingTheResource = string; +export type FolderSearchHit = { + id?: number; + managedBy?: ManagerKindIsTheTypeOfManagerWhichIsResponsibleForManagingTheResource; + parentUid?: string; + title?: string; + uid?: string; +}; +export type Folder = { + accessControl?: Metadata; + canAdmin?: boolean; + canDelete?: boolean; + canEdit?: boolean; + canSave?: boolean; + created?: string; + createdBy?: string; + hasAcl?: boolean; + /** Deprecated: use UID instead */ + id?: number; + managedBy?: ManagerKindIsTheTypeOfManagerWhichIsResponsibleForManagingTheResource; + orgId?: number; + /** only used if nested folders are enabled */ + parentUid?: string; + /** the parent folders starting from the root going down */ + parents?: Folder[]; + title?: string; + uid?: string; + updated?: string; + updatedBy?: string; + url?: string; + version?: number; +}; +export type CreateFolderCommand = { + description?: string; + parentUid?: string; + title?: string; + uid?: string; +}; +export type UpdateFolderCommand = { + /** NewDescription it's an optional parameter used for overriding the existing folder description */ + description?: string; + /** Overwrite only used by the legacy folder implementation */ + overwrite?: boolean; + /** NewTitle it's an optional parameter used for overriding the existing folder title */ + title?: string; + /** Version only used by the legacy folder implementation */ + version?: number; +}; +export type DescendantCounts = { + [key: string]: number; +}; +export type MoveFolderCommand = { + parentUid?: string; +}; +export type Group = { + groupID?: string; + mappings?: any; +}; +export type GetGroupsResponse = { + groups?: Group[]; + total?: number; +}; +export type MessageResponse = { + message?: string; +}; +export type GroupAttributes = { + roles?: string[]; +}; +export type HealthResponse = { + commit?: string; + database?: string; + enterpriseCommit?: string; + version?: string; +}; +export type LibraryElementDtoMetaUser = { + avatarUrl?: string; + id?: number; + name?: string; +}; +export type LibraryElementDtoMetaIsTheMetaInformationForLibraryElementDto = { + connectedDashboards?: number; + created?: string; + createdBy?: LibraryElementDtoMetaUser; + folderName?: string; + folderUid?: string; + updated?: string; + updatedBy?: LibraryElementDtoMetaUser; +}; +export type LibraryElementDtoIsTheFrontendDtoForEntities = { + description?: string; + /** Deprecated: use FolderUID instead */ + folderId?: number; + folderUid?: string; + id?: number; + kind?: number; + meta?: LibraryElementDtoMetaIsTheMetaInformationForLibraryElementDto; + model?: object; + name?: string; + orgId?: number; + schemaVersion?: number; + type?: string; + uid?: string; + version?: number; +}; +export type LibraryElementSearchResultIsTheSearchResultForEntities = { + elements?: LibraryElementDtoIsTheFrontendDtoForEntities[]; + page?: number; + perPage?: number; + totalCount?: number; +}; +export type LibraryElementSearchResponseIsAResponseStructForLibraryElementSearchResult = { + result?: LibraryElementSearchResultIsTheSearchResultForEntities; +}; +export type LibraryElementResponseIsAResponseStructForLibraryElementDto = { + result?: LibraryElementDtoIsTheFrontendDtoForEntities; +}; +export type CreateLibraryElementCommand = { + /** ID of the folder where the library element is stored. + + Deprecated: use FolderUID instead */ + folderId?: number; + /** UID of the folder where the library element is stored. */ + folderUid?: string; + /** Kind of element to create, Use 1 for library panels or 2 for c. + Description: + 1 - library panels */ + kind?: 1; + /** The JSON model for the library element. */ + model?: object; + /** Name of the library element. */ + name?: string; + uid?: string; +}; +export type LibraryElementArrayResponseIsAResponseStructForAnArrayOfLibraryElementDto = { + result?: LibraryElementDtoIsTheFrontendDtoForEntities[]; +}; +export type PatchLibraryElementCommand = { + /** ID of the folder where the library element is stored. + + Deprecated: use FolderUID instead */ + folderId?: number; + /** UID of the folder where the library element is stored. */ + folderUid?: string; + /** Kind of element to create, Use 1 for library panels or 2 for c. + Description: + 1 - library panels */ + kind?: 1; + /** The JSON model for the library element. */ + model?: object; + /** Name of the library element. */ + name?: string; + uid?: string; + /** Version of the library element you are updating. */ + version?: number; +}; +export type LibraryElementConnectionDtoIsTheFrontendDtoForElementConnections = { + connectionId?: number; + connectionUid?: string; + created?: string; + createdBy?: LibraryElementDtoMetaUser; + elementId?: number; + /** Deprecated: this field will be removed in the future */ + id?: number; + kind?: number; +}; +export type LibraryElementConnectionsResponseIsAResponseStructForAnArrayOfLibraryElementConnectionDto = { + result?: LibraryElementConnectionDtoIsTheFrontendDtoForElementConnections[]; +}; +export type ActiveUserStats = { + active_admins_and_editors?: number; + active_anonymous_devices?: number; + active_users?: number; + active_viewers?: number; +}; +export type DeleteTokenCommand = { + instance?: string; +}; +export type TokenStatus = number; +export type Token = { + account?: string; + anonymousRatio?: number; + company?: string; + details_url?: string; + exp?: number; + iat?: number; + included_users?: number; + iss?: string; + jti?: string; + lexp?: number; + lic_exp_warn_days?: number; + lid?: string; + limit_by?: string; + max_concurrent_user_sessions?: number; + nbf?: number; + prod?: string[]; + slug?: string; + status?: TokenStatus; + sub?: string; + tok_exp_warn_days?: number; + trial?: boolean; + trial_exp?: number; + update_days?: number; + usage_billing?: boolean; +}; +export type Address = { + address1?: string; + address2?: string; + city?: string; + country?: string; + state?: string; + zipCode?: string; +}; +export type OrgDetailsDto = { + address?: Address; + id?: number; + name?: string; +}; +export type UpdateOrgForm = { + name?: string; +}; +export type UpdateOrgAddressForm = { + address1?: string; + address2?: string; + city?: string; + country?: string; + state?: string; + zipcode?: string; +}; +export type TempUserStatus = string; +export type TempUserDto = { + code?: string; + createdOn?: string; + email?: string; + emailSent?: boolean; + emailSentOn?: string; + id?: number; + invitedByEmail?: string; + invitedByLogin?: string; + invitedByName?: string; + name?: string; + orgId?: number; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + status?: TempUserStatus; + url?: string; +}; +export type AddInviteForm = { + loginOrEmail?: string; + name?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + sendEmail?: boolean; +}; +export type PreferencesCookiePreferences = { + analytics?: any; + functional?: any; + performance?: any; +}; +export type PreferencesNavbarPreference = { + bookmarkUrls?: string[]; +}; +export type PreferencesQueryHistoryPreference = { + /** one of: '' | 'query' | 'starred'; */ + homeTab?: string; +}; +export type PreferencesSpec = { + cookiePreferences?: PreferencesCookiePreferences; + /** UID for the home dashboard */ + homeDashboardUID?: string; + /** Selected language (beta) */ + language?: string; + navbar?: PreferencesNavbarPreference; + queryHistory?: PreferencesQueryHistoryPreference; + /** Selected locale (beta) */ + regionalFormat?: string; + /** light, dark, empty is default */ + theme?: string; + /** The timezone selection + TODO: this should use the timezone defined in common */ + timezone?: string; + /** day of the week (sunday, monday, etc) */ + weekStart?: string; +}; +export type CookieType = string; +export type NavbarPreference = { + bookmarkUrls?: string[]; +}; +export type QueryHistoryPreference = { + homeTab?: string; +}; +export type PatchPrefsCmd = { + cookies?: CookieType[]; + /** The numerical :id of a favorited dashboard */ + homeDashboardId?: number; + homeDashboardUID?: string; + language?: string; + navbar?: NavbarPreference; + queryHistory?: QueryHistoryPreference; + regionalFormat?: string; + theme?: 'light' | 'dark'; + timezone?: 'utc' | 'browser'; + weekStart?: string; +}; +export type UpdatePrefsCmd = { + cookies?: CookieType[]; + /** The numerical :id of a favorited dashboard */ + homeDashboardId?: number; + homeDashboardUID?: string; + language?: string; + navbar?: NavbarPreference; + queryHistory?: QueryHistoryPreference; + regionalFormat?: string; + theme?: 'light' | 'dark' | 'system'; + timezone?: 'utc' | 'browser'; + weekStart?: string; +}; +export type OrgUserDto = { + accessControl?: { + [key: string]: boolean; + }; + authLabels?: string[]; + avatarUrl?: string; + email?: string; + isDisabled?: boolean; + isExternallySynced?: boolean; + isProvisioned?: boolean; + lastSeenAt?: string; + lastSeenAtAge?: string; + login?: string; + name?: string; + orgId?: number; + role?: string; + uid?: string; + userId?: number; +}; +export type AddOrgUserCommand = { + loginOrEmail?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; +}; +export type UserLookupDto = { + avatarUrl?: string; + login?: string; + uid?: string; + userId?: number; +}; +export type UpdateOrgUserCommand = { + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; +}; +export type OrgDto = { + id?: number; + name?: string; +}; +export type CreateOrgCommand = { + name?: string; +}; +export type SearchOrgUsersQueryResult = { + orgUsers?: OrgUserDto[]; + page?: number; + perPage?: number; + totalCount?: number; +}; +export type Playlist = { + id?: number; + interval?: string; + name?: string; + uid?: string; +}; +export type Playlists = Playlist[]; +export type PlaylistItem = { + Id?: number; + PlaylistId?: number; + order?: number; + title?: string; + type?: string; + value?: string; +}; +export type CreatePlaylistCommand = { + interval?: string; + items?: PlaylistItem[]; + name?: string; +}; +export type PlaylistItemDto = { + /** Title is an unused property -- it will be removed in the future */ + title?: string; + /** Type of the item. */ + type?: string; + /** Value depends on type and describes the playlist item. + + dashboard_by_id: The value is an internal numerical identifier set by Grafana. This + is not portable as the numerical identifier is non-deterministic between different instances. + Will be replaced by dashboard_by_uid in the future. (deprecated) + dashboard_by_tag: The value is a tag which is set on any number of dashboards. All + dashboards behind the tag will be added to the playlist. + dashboard_by_uid: The value is the dashboard UID */ + value?: string; +}; +export type PlaylistDto = { + /** Interval sets the time between switching views in a playlist. */ + interval?: string; + /** The ordered list of items that the playlist will iterate over. */ + items?: PlaylistItemDto[]; + /** Name of the playlist. */ + name?: string; + /** Unique playlist identifier. Generated on creation, either by the + creator of the playlist of by the application. */ + uid?: string; +}; +export type UpdatePlaylistCommand = { + interval?: string; + items?: PlaylistItem[]; + name?: string; + uid?: string; +}; +export type DataSourceRef = { + /** The plugin type-id */ + type?: string; + /** Specific datasource instance */ + uid?: string; +}; +export type AnnotationPanelFilter = { + /** Should the specified panels be included or excluded */ + exclude?: boolean; + /** Panel IDs that should be included or excluded */ + ids?: number[]; +}; +export type AnnotationTarget = { + /** Only required/valid for the grafana datasource... + but code+tests is already depending on it so hard to change */ + limit?: number; + /** Only required/valid for the grafana datasource... + but code+tests is already depending on it so hard to change */ + matchAny?: boolean; + /** Only required/valid for the grafana datasource... + but code+tests is already depending on it so hard to change */ + tags?: string[]; + /** Only required/valid for the grafana datasource... + but code+tests is already depending on it so hard to change */ + type?: string; +}; +export type AnnotationQuery = { + /** Set to 1 for the standard annotation query all dashboards have by default. */ + builtIn?: number; + datasource?: DataSourceRef; + /** When enabled the annotation query is issued with every dashboard refresh */ + enable?: boolean; + filter?: AnnotationPanelFilter; + /** Annotation queries can be toggled on or off at the top of the dashboard. + When hide is true, the toggle is not shown in the dashboard. */ + hide?: boolean; + /** Color to use for the annotation event markers */ + iconColor?: string; + /** Name of annotation. */ + name?: string; + target?: AnnotationTarget; + /** TODO -- this should not exist here, it is based on the --grafana-- datasource */ + type?: string; +}; +export type AnnotationEvent = { + color?: string; + dashboardId?: number; + dashboardUID?: string; + id?: number; + isRegion?: boolean; + panelId?: number; + source?: AnnotationQuery; + tags?: string[]; + text?: string; + time?: number; + timeEnd?: number; +}; +export type QueryHistoryDto = { + comment?: string; + createdAt?: number; + createdBy?: number; + datasourceUid?: string; + queries?: Json; + starred?: boolean; + uid?: string; +}; +export type QueryHistorySearchResult = { + page?: number; + perPage?: number; + queryHistory?: QueryHistoryDto[]; + totalCount?: number; +}; +export type QueryHistorySearchResponse = { + result?: QueryHistorySearchResult; +}; +export type QueryHistoryResponse = { + result?: QueryHistoryDto; +}; +export type CreateQueryInQueryHistoryCommand = { + /** UID of the data source for which are queries stored. */ + datasourceUid?: string; + queries: Json; +}; +export type QueryHistoryDeleteQueryResponse = { + id?: number; + message?: string; +}; +export type PatchQueryCommentInQueryHistoryCommand = { + /** Updated comment */ + comment?: string; +}; +export type RecordingRuleJson = { + active?: boolean; + count?: boolean; + description?: string; + dest_data_source_uid?: string; + id?: string; + interval?: number; + name?: string; + prom_name?: string; + queries?: { + [key: string]: any; + }[]; + range?: number; + target_ref_id?: string; +}; +export type PrometheusRemoteWriteTargetJson = { + data_source_uid?: string; + id?: string; + remote_write_path?: string; +}; +export type ReportDashboardId = { + id?: number; + name?: string; + uid?: string; +}; +export type ReportTimeRange = { + from?: string; + to?: string; +}; +export type ReportDashboard = { + dashboard?: ReportDashboardId; + reportVariables?: object; + timeRange?: ReportTimeRange; +}; +export type Type = string; +export type ReportOptions = { + layout?: string; + orientation?: string; + pdfCombineOneFile?: boolean; + pdfShowTemplateVariables?: boolean; + timeRange?: ReportTimeRange; +}; +export type ReportSchedule = { + dayOfMonth?: string; + endDate?: string; + frequency?: string; + intervalAmount?: number; + intervalFrequency?: string; + startDate?: string; + timeZone?: string; + workdaysOnly?: boolean; +}; +export type State = string; +export type Report = { + created?: string; + dashboards?: ReportDashboard[]; + enableCsv?: boolean; + enableDashboardUrl?: boolean; + formats?: Type[]; + id?: number; + message?: string; + name?: string; + options?: ReportOptions; + orgId?: number; + recipients?: string; + replyTo?: string; + scaleFactor?: number; + schedule?: ReportSchedule; + state?: State; + subject?: string; + uid?: string; + updated?: string; + userId?: number; +}; +export type CreateOrUpdateReport = { + dashboards?: ReportDashboard[]; + enableCsv?: boolean; + enableDashboardUrl?: boolean; + formats?: Type[]; + message?: string; + name?: string; + options?: ReportOptions; + recipients?: string; + replyTo?: string; + scaleFactor?: number; + schedule?: ReportSchedule; + state?: State; + subject?: string; +}; +export type ReportEmail = { + /** Comma-separated list of emails to which to send the report to. */ + emails?: string; + /** Send the report to the emails specified in the report. Required if emails is not present. */ + id?: string; + /** Send the report to the emails specified in the report. Required if emails is not present. */ + useEmailsFromReport?: boolean; +}; +export type ReportBrandingOptions = { + emailFooterLink?: string; + emailFooterMode?: string; + emailFooterText?: string; + emailLogoUrl?: string; + reportLogoUrl?: string; +}; +export type ReportSettings = { + branding?: ReportBrandingOptions; + embeddedImageTheme?: string; + id?: number; + orgId?: number; + pdfTheme?: string; + userId?: number; +}; +export type HitType = string; +export type Hit = { + description?: string; + folderId?: number; + folderTitle?: string; + folderUid?: string; + folderUrl?: string; + id?: number; + isDeleted?: boolean; + isStarred?: boolean; + orgId?: number; + permanentlyDeleteDate?: string; + slug?: string; + sortMeta?: number; + sortMetaName?: string; + tags?: string[]; + title?: string; + type?: HitType; + uid?: string; + uri?: string; + url?: string; +}; +export type HitList = Hit[]; +export type ServiceAccountDto = { + accessControl?: { + [key: string]: boolean; + }; + avatarUrl?: string; + id?: number; + isDisabled?: boolean; + isExternal?: boolean; + login?: string; + name?: string; + orgId?: number; + role?: string; + tokens?: number; + uid?: string; +}; +export type CreateServiceAccountForm = { + isDisabled?: boolean; + name?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; +}; +export type SearchOrgServiceAccountsResult = { + page?: number; + perPage?: number; + serviceAccounts?: ServiceAccountDto[]; + /** It can be used for pagination of the user list + E.g. if totalCount is equal to 100 users and + the perpage parameter is set to 10 then there are 10 pages of users. */ + totalCount?: number; +}; +export type ServiceAccountProfileDto = { + accessControl?: { + [key: string]: boolean; + }; + avatarUrl?: string; + createdAt?: string; + id?: number; + isDisabled?: boolean; + isExternal?: boolean; + login?: string; + name?: string; + orgId?: number; + requiredBy?: string; + role?: string; + teams?: string[]; + tokens?: number; + uid?: string; + updatedAt?: string; +}; +export type UpdateServiceAccountForm = { + isDisabled?: boolean; + name?: string; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; + serviceAccountId?: number; +}; +export type TokenDto = { + created?: string; + expiration?: string; + hasExpired?: boolean; + id?: number; + isRevoked?: boolean; + lastUsedAt?: string; + name?: string; + secondsUntilExpiration?: number; +}; +export type NewApiKeyResult = { + id?: number; + key?: string; + name?: string; +}; +export type AddServiceAccountTokenCommand = { + name?: string; + secondsToLive?: number; +}; +export type AnIpMaskIsABitmaskThatCanBeUsedToManipulateIpAddressesForIpAddressingAndRouting = number[]; +export type AnIpNetRepresentsAnIpNetwork = { + IP?: string; + Mask?: AnIpMaskIsABitmaskThatCanBeUsedToManipulateIpAddressesForIpAddressingAndRouting; +}; +export type ExtKeyUsageRepresentsAnExtendedSetOfActionsThatAreValidForAGivenKey = number; +export type AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier = number[]; +export type Extension = { + Critical?: boolean; + Id?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier; + Value?: number[]; +}; +export type AttributeTypeAndValue = { + Type?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier; + Value?: any; +}; +export type Name = { + Country?: string[]; + /** ExtraNames contains attributes to be copied, raw, into any marshaled + distinguished names. Values override any attributes with the same OID. + The ExtraNames field is not populated when parsing, see Names. */ + ExtraNames?: AttributeTypeAndValue[]; + Locality?: string[]; + /** Names contains all parsed attributes. When parsing distinguished names, + this can be used to extract non-standard attributes that are not parsed + by this package. When marshaling to RDNSequences, the Names field is + ignored, see ExtraNames. */ + Names?: AttributeTypeAndValue[]; + SerialNumber?: string; + StreetAddress?: string[]; +}; +export type KeyUsage = number; +export type PolicyMappingRepresentsAPolicyMappingEntryInThePolicyMappingsExtension = { + /** IssuerDomainPolicy contains a policy OID the issuing certificate considers + equivalent to SubjectDomainPolicy in the subject certificate. */ + IssuerDomainPolicy?: string; + /** SubjectDomainPolicy contains a OID the issuing certificate considers + equivalent to IssuerDomainPolicy in the subject certificate. */ + SubjectDomainPolicy?: string; +}; +export type PublicKeyAlgorithm = number; +export type SignatureAlgorithm = number; +export type Userinfo = object; +export type AUrlRepresentsAParsedUrlTechnicallyAUriReference = { + ForceQuery?: boolean; + Fragment?: string; + Host?: string; + OmitHost?: boolean; + Opaque?: string; + Path?: string; + RawFragment?: string; + RawPath?: string; + RawQuery?: string; + Scheme?: string; + User?: Userinfo; +}; +export type ACertificateRepresentsAnX509Certificate = { + AuthorityKeyId?: number[]; + /** BasicConstraintsValid indicates whether IsCA, MaxPathLen, + and MaxPathLenZero are valid. */ + BasicConstraintsValid?: boolean; + /** CRL Distribution Points */ + CRLDistributionPoints?: string[]; + /** Subject Alternate Name values. (Note that these values may not be valid + if invalid values were contained within a parsed certificate. For + example, an element of DNSNames may not be a valid DNS domain name.) */ + DNSNames?: string[]; + EmailAddresses?: string[]; + ExcludedDNSDomains?: string[]; + ExcludedEmailAddresses?: string[]; + ExcludedIPRanges?: AnIpNetRepresentsAnIpNetwork[]; + ExcludedURIDomains?: string[]; + ExtKeyUsage?: ExtKeyUsageRepresentsAnExtendedSetOfActionsThatAreValidForAGivenKey[]; + /** Extensions contains raw X.509 extensions. When parsing certificates, + this can be used to extract non-critical extensions that are not + parsed by this package. When marshaling certificates, the Extensions + field is ignored, see ExtraExtensions. */ + Extensions?: Extension[]; + /** ExtraExtensions contains extensions to be copied, raw, into any + marshaled certificates. Values override any extensions that would + otherwise be produced based on the other fields. The ExtraExtensions + field is not populated when parsing certificates, see Extensions. */ + ExtraExtensions?: Extension[]; + IPAddresses?: string[]; + /** InhibitAnyPolicy and InhibitAnyPolicyZero indicate the presence and value + of the inhibitAnyPolicy extension. + + The value of InhibitAnyPolicy indicates the number of additional + certificates in the path after this certificate that may use the + anyPolicy policy OID to indicate a match with any other policy. + + When parsing a certificate, a positive non-zero InhibitAnyPolicy means + that the field was specified, -1 means it was unset, and + InhibitAnyPolicyZero being true mean that the field was explicitly set to + zero. The case of InhibitAnyPolicy==0 with InhibitAnyPolicyZero==false + should be treated equivalent to -1 (unset). */ + InhibitAnyPolicy?: number; + /** InhibitAnyPolicyZero indicates that InhibitAnyPolicy==0 should be + interpreted as an actual maximum path length of zero. Otherwise, that + combination is interpreted as InhibitAnyPolicy not being set. */ + InhibitAnyPolicyZero?: boolean; + /** InhibitPolicyMapping and InhibitPolicyMappingZero indicate the presence + and value of the inhibitPolicyMapping field of the policyConstraints + extension. + + The value of InhibitPolicyMapping indicates the number of additional + certificates in the path after this certificate that may use policy + mapping. + + When parsing a certificate, a positive non-zero InhibitPolicyMapping + means that the field was specified, -1 means it was unset, and + InhibitPolicyMappingZero being true mean that the field was explicitly + set to zero. The case of InhibitPolicyMapping==0 with + InhibitPolicyMappingZero==false should be treated equivalent to -1 + (unset). */ + InhibitPolicyMapping?: number; + /** InhibitPolicyMappingZero indicates that InhibitPolicyMapping==0 should be + interpreted as an actual maximum path length of zero. Otherwise, that + combination is interpreted as InhibitAnyPolicy not being set. */ + InhibitPolicyMappingZero?: boolean; + IsCA?: boolean; + Issuer?: Name; + IssuingCertificateURL?: string[]; + KeyUsage?: KeyUsage; + /** MaxPathLen and MaxPathLenZero indicate the presence and + value of the BasicConstraints' "pathLenConstraint". + + When parsing a certificate, a positive non-zero MaxPathLen + means that the field was specified, -1 means it was unset, + and MaxPathLenZero being true mean that the field was + explicitly set to zero. The case of MaxPathLen==0 with MaxPathLenZero==false + should be treated equivalent to -1 (unset). + + When generating a certificate, an unset pathLenConstraint + can be requested with either MaxPathLen == -1 or using the + zero value for both MaxPathLen and MaxPathLenZero. */ + MaxPathLen?: number; + /** MaxPathLenZero indicates that BasicConstraintsValid==true + and MaxPathLen==0 should be interpreted as an actual + maximum path length of zero. Otherwise, that combination is + interpreted as MaxPathLen not being set. */ + MaxPathLenZero?: boolean; + NotBefore?: string; + /** RFC 5280, 4.2.2.1 (Authority Information Access) */ + OCSPServer?: string[]; + PermittedDNSDomains?: string[]; + /** Name constraints */ + PermittedDNSDomainsCritical?: boolean; + PermittedEmailAddresses?: string[]; + PermittedIPRanges?: AnIpNetRepresentsAnIpNetwork[]; + PermittedURIDomains?: string[]; + /** Policies contains all policy identifiers included in the certificate. + See CreateCertificate for context about how this field and the PolicyIdentifiers field + interact. + In Go 1.22, encoding/gob cannot handle and ignores this field. */ + Policies?: string[]; + /** PolicyIdentifiers contains asn1.ObjectIdentifiers, the components + of which are limited to int32. If a certificate contains a policy which + cannot be represented by asn1.ObjectIdentifier, it will not be included in + PolicyIdentifiers, but will be present in Policies, which contains all parsed + policy OIDs. + See CreateCertificate for context about how this field and the Policies field + interact. */ + PolicyIdentifiers?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier[]; + /** PolicyMappings contains a list of policy mappings included in the certificate. */ + PolicyMappings?: PolicyMappingRepresentsAPolicyMappingEntryInThePolicyMappingsExtension[]; + PublicKey?: any; + PublicKeyAlgorithm?: PublicKeyAlgorithm; + Raw?: number[]; + RawIssuer?: number[]; + RawSubject?: number[]; + RawSubjectPublicKeyInfo?: number[]; + RawTBSCertificate?: number[]; + /** RequireExplicitPolicy and RequireExplicitPolicyZero indicate the presence + and value of the requireExplicitPolicy field of the policyConstraints + extension. + + The value of RequireExplicitPolicy indicates the number of additional + certificates in the path after this certificate before an explicit policy + is required for the rest of the path. When an explicit policy is required, + each subsequent certificate in the path must contain a required policy OID, + or a policy OID which has been declared as equivalent through the policy + mapping extension. + + When parsing a certificate, a positive non-zero RequireExplicitPolicy + means that the field was specified, -1 means it was unset, and + RequireExplicitPolicyZero being true mean that the field was explicitly + set to zero. The case of RequireExplicitPolicy==0 with + RequireExplicitPolicyZero==false should be treated equivalent to -1 + (unset). */ + RequireExplicitPolicy?: number; + /** RequireExplicitPolicyZero indicates that RequireExplicitPolicy==0 should be + interpreted as an actual maximum path length of zero. Otherwise, that + combination is interpreted as InhibitAnyPolicy not being set. */ + RequireExplicitPolicyZero?: boolean; + SerialNumber?: string; + Signature?: number[]; + SignatureAlgorithm?: SignatureAlgorithm; + Subject?: Name; + SubjectKeyId?: number[]; + URIs?: AUrlRepresentsAParsedUrlTechnicallyAUriReference[]; + /** UnhandledCriticalExtensions contains a list of extension IDs that + were not (fully) processed when parsing. Verify will fail if this + slice is non-empty, unless verification is delegated to an OS + library which understands all the critical extensions. + + Users can access these extensions using Extensions and can remove + elements from this slice if they believe that they have been + handled. */ + UnhandledCriticalExtensions?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier[]; + UnknownExtKeyUsage?: AnObjectIdentifierRepresentsAnAsn1ObjectIdentifier[]; + Version?: number; +}; +export type JsonWebKey = { + /** Key algorithm, parsed from `alg` header. */ + Algorithm?: string; + /** X.509 certificate thumbprint (SHA-1), parsed from `x5t` header. */ + CertificateThumbprintSHA1?: number[]; + /** X.509 certificate thumbprint (SHA-256), parsed from `x5t#S256` header. */ + CertificateThumbprintSHA256?: number[]; + /** X.509 certificate chain, parsed from `x5c` header. */ + Certificates?: ACertificateRepresentsAnX509Certificate[]; + CertificatesURL?: AUrlRepresentsAParsedUrlTechnicallyAUriReference; + /** Key is the Go in-memory representation of this key. It must have one + of these types: + ed25519.PublicKey + ed25519.PrivateKey + ecdsa.PublicKey + ecdsa.PrivateKey + rsa.PublicKey + rsa.PrivateKey + []byte (a symmetric key) + + When marshaling this JSONWebKey into JSON, the "kty" header parameter + will be automatically set based on the type of this field. */ + Key?: any; + /** Key identifier, parsed from `kid` header. */ + KeyID?: string; + /** Key use, parsed from `use` header. */ + Use?: string; +}; +export type Unstructured = { + /** Object is a JSON compatible map with string, float, int, bool, []any, + or map[string]any children. */ + Object?: { + [key: string]: any; + }; +}; +export type CreateDashboardSnapshotCommand = { + /** 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 + +optional */ + apiVersion?: string; + dashboard: Unstructured; + /** Unique key used to delete the snapshot. It is different from the `key` so that only the creator can delete the snapshot. Required if `external` is `true`. */ + deleteKey?: string; + /** When the snapshot should expire in seconds in seconds. Default is never to expire. */ + expires?: number; + /** these are passed when storing an external snapshot ref + Save the snapshot on an external server rather than locally. */ + external?: boolean; + /** Define the unique key. Required if `external` is `true`. */ + key?: 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 + +optional */ + kind?: string; + /** Snapshot name */ + name?: string; +}; +export type CreateTeamCommand = { + email?: string; + name?: string; +}; +export type TeamDto = { + accessControl?: { + [key: string]: boolean; + }; + avatarUrl?: string; + email?: string; + externalUID?: string; + id?: number; + isProvisioned?: boolean; + memberCount?: number; + name?: string; + orgId?: number; + permission?: PermissionType; + uid?: string; +}; +export type SearchTeamQueryResult = { + page?: number; + perPage?: number; + teams?: TeamDto[]; + totalCount?: number; +}; +export type TeamGroupDto = { + groupId?: string; + orgId?: number; + teamId?: number; +}; +export type TeamGroupMapping = { + groupId?: string; +}; +export type SearchTeamGroupsQueryResult = { + page?: number; + perPage?: number; + teamGroups?: TeamGroupDto[]; + totalCount?: number; +}; +export type UpdateTeamCommand = { + Email?: string; + ExternalUID?: string; + ID?: number; + Name?: string; +}; +export type TeamMemberDto = { + auth_module?: string; + avatarUrl?: string; + email?: string; + labels?: string[]; + login?: string; + name?: string; + orgId?: number; + permission?: PermissionType; + teamId?: number; + teamUID?: string; + uid?: string; + userId?: number; + userUID?: string; +}; +export type AddTeamMemberCommand = { + userId?: number; +}; +export type SetTeamMembershipsCommand = { + admins?: string[]; + members?: string[]; +}; +export type UpdateTeamMemberCommand = { + permission?: PermissionType; +}; +export type UserProfileDto = { + accessControl?: { + [key: string]: boolean; + }; + authLabels?: string[]; + avatarUrl?: string; + createdAt?: string; + email?: string; + id?: number; + isDisabled?: boolean; + isExternal?: boolean; + isExternallySynced?: boolean; + isGrafanaAdmin?: boolean; + isGrafanaAdminExternallySynced?: boolean; + isProvisioned?: boolean; + login?: string; + name?: string; + orgId?: number; + theme?: string; + uid?: string; + updatedAt?: string; +}; +export type UpdateUserCommand = { + email?: string; + login?: string; + name?: string; + theme?: string; +}; +export type UserOrgDto = { + name?: string; + orgId?: number; + role?: 'None' | 'Viewer' | 'Editor' | 'Admin'; +}; +export type ChangeUserPasswordCommand = { + newPassword?: Password; + oldPassword?: Password; +}; +export type UserSearchHitDto = { + authLabels?: string[]; + avatarUrl?: string; + email?: string; + id?: number; + isAdmin?: boolean; + isDisabled?: boolean; + isProvisioned?: boolean; + lastSeenAt?: string; + lastSeenAtAge?: string; + login?: string; + name?: string; + uid?: string; +}; +export type SearchUserQueryResult = { + page?: number; + perPage?: number; + totalCount?: number; + users?: UserSearchHitDto[]; +}; +export type RelativeTimeRange = { + from?: Duration; + to?: Duration; +}; +export type AlertQueryRepresentsASingleQueryAssociatedWithAnAlertDefinition = { + /** Grafana data source unique identifier; it should be '__expr__' for a Server Side Expression operation. */ + datasourceUid?: string; + /** JSON is the raw JSON query and includes the above properties as well as custom properties. */ + model?: object; + /** QueryType is an optional identifier for the type of query. + It can be used to distinguish different types of queries. */ + queryType?: string; + /** RefID is the unique identifier of the query, set by the frontend call. */ + refId?: string; + relativeTimeRange?: RelativeTimeRange; +}; +export type AlertRuleNotificationSettings = { + /** Override the times when notifications should not be muted. These must match the name of a mute time interval defined + in the alertmanager configuration time_intervals section. All notifications will be suppressed unless they are sent + at the time that matches any interval. */ + active_time_intervals?: string[]; + /** Override the labels by which incoming alerts are grouped together. For example, multiple alerts coming in for + cluster=A and alertname=LatencyHigh would be batched into a single group. To aggregate by all possible labels + use the special value '...' as the sole label name. + This effectively disables aggregation entirely, passing through all alerts as-is. This is unlikely to be what + you want, unless you have a very low alert volume or your upstream notification system performs its own grouping. + Must include 'alertname' and 'grafana_folder' if not using '...'. */ + group_by?: string[]; + /** Override how long to wait before sending a notification about new alerts that are added to a group of alerts for + which an initial notification has already been sent. (Usually ~5m or more.) */ + group_interval?: string; + /** Override how long to initially wait to send a notification for a group of alerts. Allows to wait for an + inhibiting alert to arrive or collect more initial alerts for the same group. (Usually ~0s to few minutes.) */ + group_wait?: string; + /** Override the times when notifications should be muted. These must match the name of a mute time interval defined + in the alertmanager configuration time_intervals section. When muted it will not send any notifications, but + otherwise acts normally. */ + mute_time_intervals?: string[]; + /** Name of the receiver to send notifications to. */ + receiver: string; + /** Override how long to wait before sending a notification again if it has already been sent successfully for an + alert. (Usually ~3h or more). + Note that this parameter is implicitly bound by Alertmanager's `--data.retention` configuration flag. + Notifications will be resent after either repeat_interval or the data retention period have passed, whichever + occurs first. `repeat_interval` should not be less than `group_interval`. */ + repeat_interval?: string; +}; +export type Provenance = string; +export type Record = { + /** Which expression node should be used as the input for the recorded metric. */ + from: string; + /** Name of the recorded metric. */ + metric: string; + /** Which data source should be used to write the output of the recording rule, specified by UID. */ + target_datasource_uid?: string; +}; +export type ProvisionedAlertRule = { + annotations?: { + [key: string]: string; + }; + condition: string; + data: AlertQueryRepresentsASingleQueryAssociatedWithAnAlertDefinition[]; + execErrState: 'OK' | 'Alerting' | 'Error'; + folderUID: string; + for: string; + id?: number; + isPaused?: boolean; + keep_firing_for?: string; + labels?: { + [key: string]: string; + }; + missingSeriesEvalsToResolve?: number; + noDataState: 'Alerting' | 'NoData' | 'OK'; + notification_settings?: AlertRuleNotificationSettings; + orgID: number; + provenance?: Provenance; + record?: Record; + ruleGroup: string; + title: string; + uid?: string; +}; +export type ProvisionedAlertRuleRead = { + annotations?: { + [key: string]: string; + }; + condition: string; + data: AlertQueryRepresentsASingleQueryAssociatedWithAnAlertDefinition[]; + execErrState: 'OK' | 'Alerting' | 'Error'; + folderUID: string; + for: string; + id?: number; + isPaused?: boolean; + keep_firing_for?: string; + labels?: { + [key: string]: string; + }; + missingSeriesEvalsToResolve?: number; + noDataState: 'Alerting' | 'NoData' | 'OK'; + notification_settings?: AlertRuleNotificationSettings; + orgID: number; + provenance?: Provenance; + record?: Record; + ruleGroup: string; + title: string; + uid?: string; + updated?: string; +}; +export type ProvisionedAlertRules = ProvisionedAlertRule[]; +export type ProvisionedAlertRulesRead = ProvisionedAlertRuleRead[]; +export type ValidationError = { + message?: string; +}; +export type RawMessage = object; +export type ReceiverExportIsTheProvisionedFileExportOfAlertingReceiverV1 = { + disableResolveMessage?: boolean; + settings?: RawMessage; + type?: string; + uid?: string; +}; +export type ContactPointExportIsTheProvisionedFileExportOfAlertingContactPointV1 = { + name?: string; + orgId?: number; + receivers?: ReceiverExportIsTheProvisionedFileExportOfAlertingReceiverV1[]; +}; +export type RelativeTimeRangeExport = { + from?: number; + to?: number; +}; +export type AlertQueryExportIsTheProvisionedExportOfModelsAlertQuery = { + datasourceUid?: string; + model?: { + [key: string]: any; + }; + queryType?: string; + refId?: string; + relativeTimeRange?: RelativeTimeRangeExport; +}; +export type AlertRuleNotificationSettingsExportIsTheProvisionedExportOfModelsNotificationSettings = { + active_time_intervals?: string[]; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + mute_time_intervals?: string[]; + receiver?: string; + repeat_interval?: string; +}; +export type RecordIsTheProvisionedExportOfModelsRecord = { + from?: string; + metric?: string; + targetDatasourceUid?: string; +}; +export type AlertRuleExportIsTheProvisionedFileExportOfModelsAlertRule = { + annotations?: { + [key: string]: string; + }; + condition?: string; + dashboardUid?: string; + data?: AlertQueryExportIsTheProvisionedExportOfModelsAlertQuery[]; + execErrState?: 'OK' | 'Alerting' | 'Error'; + for?: Duration; + isPaused?: boolean; + keepFiringFor?: Duration; + labels?: { + [key: string]: string; + }; + missing_series_evals_to_resolve?: number; + noDataState?: 'Alerting' | 'NoData' | 'OK'; + notification_settings?: AlertRuleNotificationSettingsExportIsTheProvisionedExportOfModelsNotificationSettings; + panelId?: number; + record?: RecordIsTheProvisionedExportOfModelsRecord; + title?: string; + uid?: string; +}; +export type AlertRuleGroupExportIsTheProvisionedFileExportOfAlertRuleGroupV1 = { + folder?: string; + interval?: Duration; + name?: string; + orgId?: number; + rules?: AlertRuleExportIsTheProvisionedFileExportOfModelsAlertRule[]; +}; +export type TimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted = { + name?: string; + time_intervals?: TimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted[]; +}; +export type MuteTimeIntervalExport = { + name?: string; + orgId?: number; + time_intervals?: TimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted[]; +}; +export type MatchRegexpsRepresentsAMapOfRegexp = { + [key: string]: string; +}; +export type MatchTypeIsAnEnumForLabelMatchingTypes = number; +export type MatcherModelsTheMatchingOfALabel = { + Name?: string; + Type?: MatchTypeIsAnEnumForLabelMatchingTypes; + Value?: string; +}; +export type Matchers = MatcherModelsTheMatchingOfALabel[]; +export type ObjectMatcherIsAMatcherThatCanBeUsedToFilterAlerts = string[]; +export type ObjectMatchersIsAListOfMatchersThatCanBeUsedToFilterAlerts = + ObjectMatcherIsAMatcherThatCanBeUsedToFilterAlerts[]; +export type RouteExport = { + active_time_intervals?: string[]; + continue?: boolean; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + /** Deprecated. Remove before v1.0 release. */ + match?: { + [key: string]: string; + }; + match_re?: MatchRegexpsRepresentsAMapOfRegexp; + matchers?: Matchers; + mute_time_intervals?: string[]; + object_matchers?: ObjectMatchersIsAListOfMatchersThatCanBeUsedToFilterAlerts; + receiver?: string; + repeat_interval?: string; + routes?: RouteExport[]; +}; +export type NotificationPolicyExportIsTheProvisionedFileExportOfAlertingNotificiationPolicyV1 = { + active_time_intervals?: string[]; + continue?: boolean; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + /** Deprecated. Remove before v1.0 release. */ + match?: { + [key: string]: string; + }; + match_re?: MatchRegexpsRepresentsAMapOfRegexp; + matchers?: Matchers; + mute_time_intervals?: string[]; + object_matchers?: ObjectMatchersIsAListOfMatchersThatCanBeUsedToFilterAlerts; + orgId?: number; + receiver?: string; + repeat_interval?: string; + routes?: RouteExport[]; +}; +export type AlertingFileExportIsTheFullProvisionedFileExport = { + apiVersion?: number; + contactPoints?: ContactPointExportIsTheProvisionedFileExportOfAlertingContactPointV1[]; + groups?: AlertRuleGroupExportIsTheProvisionedFileExportOfAlertRuleGroupV1[]; + muteTimes?: MuteTimeIntervalExport[]; + policies?: NotificationPolicyExportIsTheProvisionedFileExportOfAlertingNotificiationPolicyV1[]; +}; +export type EmbeddedContactPoint = { + disableResolveMessage?: boolean; + /** Name is used as grouping key in the UI. Contact points with the + same name will be grouped in the UI. */ + name?: string; + settings: Json; + type: + | 'alertmanager' + | 'dingding' + | 'discord' + | 'email' + | 'googlechat' + | 'kafka' + | 'line' + | 'opsgenie' + | 'pagerduty' + | 'pushover' + | 'sensugo' + | 'slack' + | 'teams' + | 'telegram' + | 'threema' + | 'victorops' + | 'webhook' + | 'wecom'; + /** UID is the unique identifier of the contact point. The UID can be + set by the user. */ + uid?: string; +}; +export type EmbeddedContactPointRead = { + disableResolveMessage?: boolean; + /** Name is used as grouping key in the UI. Contact points with the + same name will be grouped in the UI. */ + name?: string; + provenance?: string; + settings: Json; + type: + | 'alertmanager' + | 'dingding' + | 'discord' + | 'email' + | 'googlechat' + | 'kafka' + | 'line' + | 'opsgenie' + | 'pagerduty' + | 'pushover' + | 'sensugo' + | 'slack' + | 'teams' + | 'telegram' + | 'threema' + | 'victorops' + | 'webhook' + | 'wecom'; + /** UID is the unique identifier of the contact point. The UID can be + set by the user. */ + uid?: string; +}; +export type ContactPoints = EmbeddedContactPoint[]; +export type ContactPointsRead = EmbeddedContactPointRead[]; +export type PermissionDenied = object; +export type Ack = object; +export type NotFound = object; +export type AlertRuleGroup = { + folderUid?: string; + interval?: number; + rules?: ProvisionedAlertRule[]; + title?: string; +}; +export type AlertRuleGroupRead = { + folderUid?: string; + interval?: number; + rules?: ProvisionedAlertRuleRead[]; + title?: string; +}; +export type MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted = { + name?: string; + time_intervals?: TimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted[]; +}; +export type MuteTimings = MuteTimeIntervalRepresentsANamedSetOfTimeIntervalsForWhichARouteShouldBeMuted[]; +export type Route = { + active_time_intervals?: string[]; + continue?: boolean; + group_by?: string[]; + group_interval?: string; + group_wait?: string; + /** Deprecated. Remove before v1.0 release. */ + match?: { + [key: string]: string; + }; + match_re?: MatchRegexpsRepresentsAMapOfRegexp; + matchers?: Matchers; + mute_time_intervals?: string[]; + object_matchers?: ObjectMatchersIsAListOfMatchersThatCanBeUsedToFilterAlerts; + provenance?: Provenance; + receiver?: string; + repeat_interval?: string; + routes?: Route[]; +}; +export type NotificationTemplate = { + name?: string; + provenance?: Provenance; + template?: string; + version?: string; +}; +export type NotificationTemplates = NotificationTemplate[]; +export type NotificationTemplateContent = { + template?: string; + version?: string; +}; +export const { + useSearchResultMutation, + useListRolesQuery, + useLazyListRolesQuery, + useCreateRoleMutation, + useDeleteRoleMutation, + useGetRoleQuery, + useLazyGetRoleQuery, + useUpdateRoleMutation, + useGetRoleAssignmentsQuery, + useLazyGetRoleAssignmentsQuery, + useSetRoleAssignmentsMutation, + useGetAccessControlStatusQuery, + useLazyGetAccessControlStatusQuery, + useListTeamsRolesMutation, + useListTeamRolesQuery, + useLazyListTeamRolesQuery, + useAddTeamRoleMutation, + useSetTeamRolesMutation, + useRemoveTeamRoleMutation, + useListUsersRolesMutation, + useListUserRolesQuery, + useLazyListUserRolesQuery, + useAddUserRoleMutation, + useSetUserRolesMutation, + useRemoveUserRoleMutation, + useGetResourceDescriptionQuery, + useLazyGetResourceDescriptionQuery, + useGetResourcePermissionsQuery, + useLazyGetResourcePermissionsQuery, + useSetResourcePermissionsMutation, + useSetResourcePermissionsForBuiltInRoleMutation, + useSetResourcePermissionsForTeamMutation, + useSetResourcePermissionsForUserMutation, + useGetSyncStatusQuery, + useLazyGetSyncStatusQuery, + useReloadLdapCfgMutation, + useGetLdapStatusQuery, + useLazyGetLdapStatusQuery, + usePostSyncUserWithLdapMutation, + useGetUserFromLdapQuery, + useLazyGetUserFromLdapQuery, + useAdminProvisioningReloadAccessControlMutation, + useAdminProvisioningReloadDashboardsMutation, + useAdminProvisioningReloadDatasourcesMutation, + useAdminProvisioningReloadPluginsMutation, + useAdminGetSettingsQuery, + useLazyAdminGetSettingsQuery, + useAdminGetStatsQuery, + useLazyAdminGetStatsQuery, + useAdminCreateUserMutation, + useAdminDeleteUserMutation, + useAdminGetUserAuthTokensQuery, + useLazyAdminGetUserAuthTokensQuery, + useAdminDisableUserMutation, + useAdminEnableUserMutation, + useAdminLogoutUserMutation, + useAdminUpdateUserPasswordMutation, + useAdminUpdateUserPermissionsMutation, + useGetUserQuotaQuery, + useLazyGetUserQuotaQuery, + useUpdateUserQuotaMutation, + useAdminRevokeUserAuthTokenMutation, + useGetAnnotationsQuery, + useLazyGetAnnotationsQuery, + usePostAnnotationMutation, + usePostGraphiteAnnotationMutation, + useMassDeleteAnnotationsMutation, + useGetAnnotationTagsQuery, + useLazyGetAnnotationTagsQuery, + useDeleteAnnotationByIdMutation, + useGetAnnotationByIdQuery, + useLazyGetAnnotationByIdQuery, + usePatchAnnotationMutation, + useUpdateAnnotationMutation, + useListDevicesQuery, + useLazyListDevicesQuery, + useSearchDevicesQuery, + useLazySearchDevicesQuery, + useGetSessionListQuery, + useLazyGetSessionListQuery, + useCreateSessionMutation, + useDeleteSessionMutation, + useGetSessionQuery, + useLazyGetSessionQuery, + useCreateSnapshotMutation, + useGetSnapshotQuery, + useLazyGetSnapshotQuery, + useCancelSnapshotMutation, + useUploadSnapshotMutation, + useGetShapshotListQuery, + useLazyGetShapshotListQuery, + useGetResourceDependenciesQuery, + useLazyGetResourceDependenciesQuery, + useGetCloudMigrationTokenQuery, + useLazyGetCloudMigrationTokenQuery, + useCreateCloudMigrationTokenMutation, + useDeleteCloudMigrationTokenMutation, + useRouteConvertPrometheusCortexGetRulesQuery, + useLazyRouteConvertPrometheusCortexGetRulesQuery, + useRouteConvertPrometheusCortexPostRuleGroupsMutation, + useRouteConvertPrometheusCortexDeleteNamespaceMutation, + useRouteConvertPrometheusCortexGetNamespaceQuery, + useLazyRouteConvertPrometheusCortexGetNamespaceQuery, + useRouteConvertPrometheusCortexPostRuleGroupMutation, + useRouteConvertPrometheusCortexDeleteRuleGroupMutation, + useRouteConvertPrometheusCortexGetRuleGroupQuery, + useLazyRouteConvertPrometheusCortexGetRuleGroupQuery, + useRouteConvertPrometheusGetRulesQuery, + useLazyRouteConvertPrometheusGetRulesQuery, + useRouteConvertPrometheusPostRuleGroupsMutation, + useRouteConvertPrometheusDeleteNamespaceMutation, + useRouteConvertPrometheusGetNamespaceQuery, + useLazyRouteConvertPrometheusGetNamespaceQuery, + useRouteConvertPrometheusPostRuleGroupMutation, + useRouteConvertPrometheusDeleteRuleGroupMutation, + useRouteConvertPrometheusGetRuleGroupQuery, + useLazyRouteConvertPrometheusGetRuleGroupQuery, + useSearchDashboardSnapshotsQuery, + useLazySearchDashboardSnapshotsQuery, + useCalculateDashboardDiffMutation, + usePostDashboardMutation, + useGetHomeDashboardQuery, + useLazyGetHomeDashboardQuery, + useImportDashboardMutation, + useInterpolateDashboardMutation, + useListPublicDashboardsQuery, + useLazyListPublicDashboardsQuery, + useGetDashboardTagsQuery, + useLazyGetDashboardTagsQuery, + useGetPublicDashboardQuery, + useLazyGetPublicDashboardQuery, + useCreatePublicDashboardMutation, + useDeletePublicDashboardMutation, + useUpdatePublicDashboardMutation, + useDeleteDashboardByUidMutation, + useGetDashboardByUidQuery, + useLazyGetDashboardByUidQuery, + useGetDashboardPermissionsListByUidQuery, + useLazyGetDashboardPermissionsListByUidQuery, + useUpdateDashboardPermissionsByUidMutation, + useRestoreDashboardVersionByUidMutation, + useGetDashboardVersionsByUidQuery, + useLazyGetDashboardVersionsByUidQuery, + useGetDashboardVersionByUidQuery, + useLazyGetDashboardVersionByUidQuery, + useGetDataSourcesQuery, + useLazyGetDataSourcesQuery, + useAddDataSourceMutation, + useGetCorrelationsQuery, + useLazyGetCorrelationsQuery, + useGetDataSourceIdByNameQuery, + useLazyGetDataSourceIdByNameQuery, + useDeleteDataSourceByNameMutation, + useGetDataSourceByNameQuery, + useLazyGetDataSourceByNameQuery, + useDatasourceProxyDeleteByUiDcallsMutation, + useDatasourceProxyGetByUiDcallsQuery, + useLazyDatasourceProxyGetByUiDcallsQuery, + useDatasourceProxyPostByUiDcallsMutation, + useGetCorrelationsBySourceUidQuery, + useLazyGetCorrelationsBySourceUidQuery, + useCreateCorrelationMutation, + useGetCorrelationQuery, + useLazyGetCorrelationQuery, + useUpdateCorrelationMutation, + useDeleteDataSourceByUidMutation, + useGetDataSourceByUidQuery, + useLazyGetDataSourceByUidQuery, + useUpdateDataSourceByUidMutation, + useDeleteCorrelationMutation, + useCheckDatasourceHealthWithUidQuery, + useLazyCheckDatasourceHealthWithUidQuery, + useGetTeamLbacRulesApiQuery, + useLazyGetTeamLbacRulesApiQuery, + useUpdateTeamLbacRulesApiMutation, + useCallDatasourceResourceWithUidQuery, + useLazyCallDatasourceResourceWithUidQuery, + useGetDataSourceCacheConfigQuery, + useLazyGetDataSourceCacheConfigQuery, + useSetDataSourceCacheConfigMutation, + useCleanDataSourceCacheMutation, + useDisableDataSourceCacheMutation, + useEnableDataSourceCacheMutation, + useQueryMetricsWithExpressionsMutation, + useGetFoldersQuery, + useLazyGetFoldersQuery, + useCreateFolderMutation, + useDeleteFolderMutation, + useGetFolderByUidQuery, + useLazyGetFolderByUidQuery, + useUpdateFolderMutation, + useGetFolderDescendantCountsQuery, + useLazyGetFolderDescendantCountsQuery, + useMoveFolderMutation, + useGetFolderPermissionListQuery, + useLazyGetFolderPermissionListQuery, + useUpdateFolderPermissionsMutation, + useGetMappedGroupsQuery, + useLazyGetMappedGroupsQuery, + useDeleteGroupMappingsMutation, + useCreateGroupMappingsMutation, + useUpdateGroupMappingsMutation, + useGetGroupRolesQuery, + useLazyGetGroupRolesQuery, + useGetHealthQuery, + useLazyGetHealthQuery, + useGetLibraryElementsQuery, + useLazyGetLibraryElementsQuery, + useCreateLibraryElementMutation, + useGetLibraryElementByNameQuery, + useLazyGetLibraryElementByNameQuery, + useDeleteLibraryElementByUidMutation, + useGetLibraryElementByUidQuery, + useLazyGetLibraryElementByUidQuery, + useUpdateLibraryElementMutation, + useGetLibraryElementConnectionsQuery, + useLazyGetLibraryElementConnectionsQuery, + useGetStatusQuery, + useLazyGetStatusQuery, + useRefreshLicenseStatsQuery, + useLazyRefreshLicenseStatsQuery, + useDeleteLicenseTokenMutation, + useGetLicenseTokenQuery, + useLazyGetLicenseTokenQuery, + usePostLicenseTokenMutation, + usePostRenewLicenseTokenMutation, + useGetSamlLogoutQuery, + useLazyGetSamlLogoutQuery, + useGetCurrentOrgQuery, + useLazyGetCurrentOrgQuery, + useUpdateCurrentOrgMutation, + useUpdateCurrentOrgAddressMutation, + useGetPendingOrgInvitesQuery, + useLazyGetPendingOrgInvitesQuery, + useAddOrgInviteMutation, + useRevokeInviteMutation, + useGetOrgPreferencesQuery, + useLazyGetOrgPreferencesQuery, + usePatchOrgPreferencesMutation, + useUpdateOrgPreferencesMutation, + useGetCurrentOrgQuotaQuery, + useLazyGetCurrentOrgQuotaQuery, + useGetOrgUsersForCurrentOrgQuery, + useLazyGetOrgUsersForCurrentOrgQuery, + useAddOrgUserToCurrentOrgMutation, + useGetOrgUsersForCurrentOrgLookupQuery, + useLazyGetOrgUsersForCurrentOrgLookupQuery, + useRemoveOrgUserForCurrentOrgMutation, + useUpdateOrgUserForCurrentOrgMutation, + useSearchOrgsQuery, + useLazySearchOrgsQuery, + useCreateOrgMutation, + useGetOrgByNameQuery, + useLazyGetOrgByNameQuery, + useDeleteOrgByIdMutation, + useGetOrgByIdQuery, + useLazyGetOrgByIdQuery, + useUpdateOrgMutation, + useUpdateOrgAddressMutation, + useGetOrgQuotaQuery, + useLazyGetOrgQuotaQuery, + useUpdateOrgQuotaMutation, + useGetOrgUsersQuery, + useLazyGetOrgUsersQuery, + useAddOrgUserMutation, + useSearchOrgUsersQuery, + useLazySearchOrgUsersQuery, + useRemoveOrgUserMutation, + useUpdateOrgUserMutation, + useSearchPlaylistsQuery, + useLazySearchPlaylistsQuery, + useCreatePlaylistMutation, + useDeletePlaylistMutation, + useGetPlaylistQuery, + useLazyGetPlaylistQuery, + useUpdatePlaylistMutation, + useGetPlaylistItemsQuery, + useLazyGetPlaylistItemsQuery, + useViewPublicDashboardQuery, + useLazyViewPublicDashboardQuery, + useGetPublicAnnotationsQuery, + useLazyGetPublicAnnotationsQuery, + useQueryPublicDashboardMutation, + useSearchQueriesQuery, + useLazySearchQueriesQuery, + useCreateQueryMutation, + useUnstarQueryMutation, + useStarQueryMutation, + useDeleteQueryMutation, + usePatchQueryCommentMutation, + useListRecordingRulesQuery, + useLazyListRecordingRulesQuery, + useCreateRecordingRuleMutation, + useUpdateRecordingRuleMutation, + useTestCreateRecordingRuleMutation, + useDeleteRecordingRuleWriteTargetMutation, + useGetRecordingRuleWriteTargetQuery, + useLazyGetRecordingRuleWriteTargetQuery, + useCreateRecordingRuleWriteTargetMutation, + useDeleteRecordingRuleMutation, + useGetReportsQuery, + useLazyGetReportsQuery, + useCreateReportMutation, + useGetReportsByDashboardUidQuery, + useLazyGetReportsByDashboardUidQuery, + useSendReportMutation, + useGetSettingsImageQuery, + useLazyGetSettingsImageQuery, + useRenderReportCsVsQuery, + useLazyRenderReportCsVsQuery, + useRenderReportPdFsQuery, + useLazyRenderReportPdFsQuery, + useGetReportSettingsQuery, + useLazyGetReportSettingsQuery, + useSaveReportSettingsMutation, + useSendTestEmailMutation, + usePostAcsMutation, + useGetMetadataQuery, + useLazyGetMetadataQuery, + useGetSloQuery, + useLazyGetSloQuery, + usePostSloMutation, + useSearchQuery, + useLazySearchQuery, + useListSortOptionsQuery, + useLazyListSortOptionsQuery, + useCreateServiceAccountMutation, + useSearchOrgServiceAccountsWithPagingQuery, + useLazySearchOrgServiceAccountsWithPagingQuery, + useDeleteServiceAccountMutation, + useRetrieveServiceAccountQuery, + useLazyRetrieveServiceAccountQuery, + useUpdateServiceAccountMutation, + useListTokensQuery, + useLazyListTokensQuery, + useCreateTokenMutation, + useDeleteTokenMutation, + useRetrieveJwksQuery, + useLazyRetrieveJwksQuery, + useGetSharingOptionsQuery, + useLazyGetSharingOptionsQuery, + useCreateDashboardSnapshotMutation, + useDeleteDashboardSnapshotByDeleteKeyQuery, + useLazyDeleteDashboardSnapshotByDeleteKeyQuery, + useDeleteDashboardSnapshotMutation, + useGetDashboardSnapshotQuery, + useLazyGetDashboardSnapshotQuery, + useCreateTeamMutation, + useSearchTeamsQuery, + useLazySearchTeamsQuery, + useRemoveTeamGroupApiQueryMutation, + useGetTeamGroupsApiQuery, + useLazyGetTeamGroupsApiQuery, + useAddTeamGroupApiMutation, + useSearchTeamGroupsQuery, + useLazySearchTeamGroupsQuery, + useDeleteTeamByIdMutation, + useGetTeamByIdQuery, + useLazyGetTeamByIdQuery, + useUpdateTeamMutation, + useGetTeamMembersQuery, + useLazyGetTeamMembersQuery, + useAddTeamMemberMutation, + useSetTeamMembershipsMutation, + useRemoveTeamMemberMutation, + useUpdateTeamMemberMutation, + useGetTeamPreferencesQuery, + useLazyGetTeamPreferencesQuery, + useUpdateTeamPreferencesMutation, + useGetSignedInUserQuery, + useLazyGetSignedInUserQuery, + useUpdateSignedInUserMutation, + useGetUserAuthTokensQuery, + useLazyGetUserAuthTokensQuery, + useUpdateUserEmailQuery, + useLazyUpdateUserEmailQuery, + useClearHelpFlagsQuery, + useLazyClearHelpFlagsQuery, + useSetHelpFlagMutation, + useGetSignedInUserOrgListQuery, + useLazyGetSignedInUserOrgListQuery, + useChangeUserPasswordMutation, + useGetUserPreferencesQuery, + useLazyGetUserPreferencesQuery, + usePatchUserPreferencesMutation, + useUpdateUserPreferencesMutation, + useGetUserQuotasQuery, + useLazyGetUserQuotasQuery, + useRevokeUserAuthTokenMutation, + useUnstarDashboardByUidMutation, + useStarDashboardByUidMutation, + useGetSignedInUserTeamListQuery, + useLazyGetSignedInUserTeamListQuery, + useUserSetUsingOrgMutation, + useSearchUsersQuery, + useLazySearchUsersQuery, + useGetUserByLoginOrEmailQuery, + useLazyGetUserByLoginOrEmailQuery, + useSearchUsersWithPagingQuery, + useLazySearchUsersWithPagingQuery, + useGetUserByIdQuery, + useLazyGetUserByIdQuery, + useUpdateUserMutation, + useGetUserOrgListQuery, + useLazyGetUserOrgListQuery, + useGetUserTeamsQuery, + useLazyGetUserTeamsQuery, + useRouteGetAlertRulesQuery, + useLazyRouteGetAlertRulesQuery, + useRoutePostAlertRuleMutation, + useRouteGetAlertRulesExportQuery, + useLazyRouteGetAlertRulesExportQuery, + useRouteDeleteAlertRuleMutation, + useRouteGetAlertRuleQuery, + useLazyRouteGetAlertRuleQuery, + useRoutePutAlertRuleMutation, + useRouteGetAlertRuleExportQuery, + useLazyRouteGetAlertRuleExportQuery, + useRouteGetContactpointsQuery, + useLazyRouteGetContactpointsQuery, + useRoutePostContactpointsMutation, + useRouteGetContactpointsExportQuery, + useLazyRouteGetContactpointsExportQuery, + useRouteDeleteContactpointsMutation, + useRoutePutContactpointMutation, + useRouteDeleteAlertRuleGroupMutation, + useRouteGetAlertRuleGroupQuery, + useLazyRouteGetAlertRuleGroupQuery, + useRoutePutAlertRuleGroupMutation, + useRouteGetAlertRuleGroupExportQuery, + useLazyRouteGetAlertRuleGroupExportQuery, + useRouteGetMuteTimingsQuery, + useLazyRouteGetMuteTimingsQuery, + useRoutePostMuteTimingMutation, + useRouteExportMuteTimingsQuery, + useLazyRouteExportMuteTimingsQuery, + useRouteDeleteMuteTimingMutation, + useRouteGetMuteTimingQuery, + useLazyRouteGetMuteTimingQuery, + useRoutePutMuteTimingMutation, + useRouteExportMuteTimingQuery, + useLazyRouteExportMuteTimingQuery, + useRouteResetPolicyTreeMutation, + useRouteGetPolicyTreeQuery, + useLazyRouteGetPolicyTreeQuery, + useRoutePutPolicyTreeMutation, + useRouteGetPolicyTreeExportQuery, + useLazyRouteGetPolicyTreeExportQuery, + useRouteGetTemplatesQuery, + useLazyRouteGetTemplatesQuery, + useRouteDeleteTemplateMutation, + useRouteGetTemplateQuery, + useLazyRouteGetTemplateQuery, + useRoutePutTemplateMutation, + useListAllProvidersSettingsQuery, + useLazyListAllProvidersSettingsQuery, + useRemoveProviderSettingsMutation, + useGetProviderSettingsQuery, + useLazyGetProviderSettingsQuery, + useUpdateProviderSettingsMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/legacy/index.ts b/packages/grafana-api-clients/src/clients/rtkq/legacy/index.ts new file mode 100644 index 00000000000..722532483b6 --- /dev/null +++ b/packages/grafana-api-clients/src/clients/rtkq/legacy/index.ts @@ -0,0 +1,5 @@ +import { generatedAPI as rawAPI } from './endpoints.gen'; + +export const legacyAPI = rawAPI.enhanceEndpoints({}); + +export * from './endpoints.gen'; diff --git a/packages/grafana-api-clients/src/clients/rtkq/migrate-to-cloud/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/migrate-to-cloud/endpoints.gen.ts index 87bcb349ecd..d1aff7226ee 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/migrate-to-cloud/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/migrate-to-cloud/endpoints.gen.ts @@ -405,18 +405,26 @@ export type LibraryElementResponseIsAResponseStructForLibraryElementDto = { }; export const { useGetSessionListQuery, + useLazyGetSessionListQuery, useCreateSessionMutation, useDeleteSessionMutation, useGetSessionQuery, + useLazyGetSessionQuery, useCreateSnapshotMutation, useGetSnapshotQuery, + useLazyGetSnapshotQuery, useCancelSnapshotMutation, useUploadSnapshotMutation, useGetShapshotListQuery, + useLazyGetShapshotListQuery, useGetResourceDependenciesQuery, + useLazyGetResourceDependenciesQuery, useGetCloudMigrationTokenQuery, + useLazyGetCloudMigrationTokenQuery, useCreateCloudMigrationTokenMutation, useDeleteCloudMigrationTokenMutation, useGetDashboardByUidQuery, + useLazyGetDashboardByUidQuery, useGetLibraryElementByUidQuery, + useLazyGetLibraryElementByUidQuery, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts index 7a42a89de07..8e1bdfc1c4c 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/playlist/v0alpha1/endpoints.gen.ts @@ -1,11 +1,15 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Playlist'] as const; +export const addTagTypes = ['API Discovery', 'Playlist'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/playlist.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), listPlaylist: build.query({ query: (queryArg) => ({ url: `/playlists`, @@ -39,6 +43,29 @@ const injectedRtkApi = api }), invalidatesTags: ['Playlist'], }), + deletecollectionPlaylist: build.mutation({ + query: (queryArg) => ({ + url: `/playlists`, + 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: ['Playlist'], + }), getPlaylist: build.query({ query: (queryArg) => ({ url: `/playlists/${queryArg.name}`, @@ -77,10 +104,65 @@ const injectedRtkApi = api }), invalidatesTags: ['Playlist'], }), + updatePlaylist: build.mutation({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Playlist'], + }), + getPlaylistStatus: build.query({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.name}/status`, + params: { + pretty: queryArg.pretty, + }, + }), + providesTags: ['Playlist'], + }), + replacePlaylistStatus: build.mutation({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.name}/status`, + method: 'PUT', + body: queryArg.playlist, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + }, + }), + invalidatesTags: ['Playlist'], + }), + updatePlaylistStatus: build.mutation({ + query: (queryArg) => ({ + url: `/playlists/${queryArg.name}/status`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Playlist'], + }), }), overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; export type ListPlaylistApiResponse = /** status 200 OK */ PlaylistList; export type ListPlaylistApiArg = { /** 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). */ @@ -141,6 +223,57 @@ export type CreatePlaylistApiArg = { fieldValidation?: string; playlist: Playlist; }; +export type DeletecollectionPlaylistApiResponse = /** status 200 OK */ Status; +export type DeletecollectionPlaylistApiArg = { + /** 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 GetPlaylistApiResponse = /** status 200 OK */ Playlist; export type GetPlaylistApiArg = { /** name of the Playlist */ @@ -179,6 +312,91 @@ export type DeletePlaylistApiArg = { /** 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 UpdatePlaylistApiResponse = /** status 200 OK */ Playlist | /** status 201 Created */ Playlist; +export type UpdatePlaylistApiArg = { + /** name of the Playlist */ + name: 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; + patch: Patch; +}; +export type GetPlaylistStatusApiResponse = /** status 200 OK */ Playlist; +export type GetPlaylistStatusApiArg = { + /** name of the Playlist */ + name: 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 ReplacePlaylistStatusApiResponse = /** status 200 OK */ Playlist | /** status 201 Created */ Playlist; +export type ReplacePlaylistStatusApiArg = { + /** name of the Playlist */ + name: 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; + playlist: Playlist; +}; +export type UpdatePlaylistStatusApiResponse = /** status 200 OK */ Playlist | /** status 201 Created */ Playlist; +export type UpdatePlaylistStatusApiArg = { + /** name of the Playlist */ + name: 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; + patch: Patch; +}; +export type ApiResource = { + /** 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 ApiResourceList = { + /** 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: ApiResource[]; +}; export type Time = string; export type FieldsV1 = object; export type ManagedFieldsEntry = { @@ -377,3 +595,21 @@ export type Status = { /** 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 Patch = object; +export const { + useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, + useListPlaylistQuery, + useLazyListPlaylistQuery, + useCreatePlaylistMutation, + useDeletecollectionPlaylistMutation, + useGetPlaylistQuery, + useLazyGetPlaylistQuery, + useReplacePlaylistMutation, + useDeletePlaylistMutation, + useUpdatePlaylistMutation, + useGetPlaylistStatusQuery, + useLazyGetPlaylistStatusQuery, + useReplacePlaylistStatusMutation, + useUpdatePlaylistStatusMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/preferences/user/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/preferences/user/endpoints.gen.ts index 60162b33858..71107ce1072 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/preferences/user/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/preferences/user/endpoints.gen.ts @@ -102,5 +102,9 @@ export type UpdatePrefsCmd = { timezone?: 'utc' | 'browser'; weekStart?: string; }; -export const { useGetUserPreferencesQuery, usePatchUserPreferencesMutation, useUpdateUserPreferencesMutation } = - injectedRtkApi; +export const { + useGetUserPreferencesQuery, + useLazyGetUserPreferencesQuery, + usePatchUserPreferencesMutation, + useUpdateUserPreferencesMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts index 2ea0fd00730..0edda5148ea 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/preferences/v1alpha1/endpoints.gen.ts @@ -778,17 +778,23 @@ export type StarsList = { }; export const { useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListPreferencesQuery, + useLazyListPreferencesQuery, useCreatePreferencesMutation, useMergedPreferencesQuery, + useLazyMergedPreferencesQuery, useGetPreferencesQuery, + useLazyGetPreferencesQuery, useReplacePreferencesMutation, useDeletePreferencesMutation, useUpdatePreferencesMutation, useListStarsQuery, + useLazyListStarsQuery, useCreateStarsMutation, useDeletecollectionStarsMutation, useGetStarsQuery, + useLazyGetStarsQuery, useReplaceStarsMutation, useDeleteStarsMutation, useUpdateStarsMutation, diff --git a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts index 41e4cac3c01..a3c5d30952a 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/provisioning/v0alpha1/endpoints.gen.ts @@ -1,11 +1,15 @@ import { api } from './baseAPI'; -export const addTagTypes = ['Job', 'Repository', 'Provisioning'] as const; +export const addTagTypes = ['API Discovery', 'Job', 'Repository', 'Provisioning'] as const; const injectedRtkApi = api .enhanceEndpoints({ addTagTypes, }) .injectEndpoints({ endpoints: (build) => ({ + getApiResources: build.query({ + query: () => ({ url: `/apis/provisioning.grafana.app/v0alpha1/` }), + providesTags: ['API Discovery'], + }), listJob: build.query({ query: (queryArg) => ({ url: `/jobs`, @@ -100,6 +104,21 @@ const injectedRtkApi = api }), invalidatesTags: ['Job'], }), + updateJob: build.mutation({ + query: (queryArg) => ({ + url: `/jobs/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Job'], + }), listRepository: build.query({ query: (queryArg) => ({ url: `/repositories`, @@ -197,6 +216,21 @@ const injectedRtkApi = api }), invalidatesTags: ['Repository'], }), + updateRepository: build.mutation({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Repository'], + }), getRepositoryFiles: build.query({ query: (queryArg) => ({ url: `/repositories/${queryArg.name}/files/`, @@ -336,6 +370,21 @@ const injectedRtkApi = api }), invalidatesTags: ['Repository'], }), + updateRepositoryStatus: build.mutation({ + query: (queryArg) => ({ + url: `/repositories/${queryArg.name}/status`, + method: 'PATCH', + body: queryArg.patch, + params: { + pretty: queryArg.pretty, + dryRun: queryArg.dryRun, + fieldManager: queryArg.fieldManager, + fieldValidation: queryArg.fieldValidation, + force: queryArg.force, + }, + }), + invalidatesTags: ['Repository'], + }), createRepositoryTest: build.mutation({ query: (queryArg) => ({ url: `/repositories/${queryArg.name}/test`, method: 'POST', body: queryArg.body }), invalidatesTags: ['Repository'], @@ -360,6 +409,8 @@ const injectedRtkApi = api overrideExisting: false, }); export { injectedRtkApi as generatedAPI }; +export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList; +export type GetApiResourcesApiArg = void; export type ListJobApiResponse = /** status 200 OK */ JobList; export type ListJobApiArg = { /** 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). */ @@ -509,6 +560,22 @@ export type DeleteJobApiArg = { /** 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 UpdateJobApiResponse = /** status 200 OK */ Job | /** status 201 Created */ Job; +export type UpdateJobApiArg = { + /** name of the Job */ + name: 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; + patch: Patch; +}; export type ListRepositoryApiResponse = /** status 200 OK */ RepositoryList; export type ListRepositoryApiArg = { /** 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). */ @@ -658,6 +725,22 @@ export type DeleteRepositoryApiArg = { /** 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 UpdateRepositoryApiResponse = /** status 200 OK */ Repository | /** status 201 Created */ Repository; +export type UpdateRepositoryApiArg = { + /** name of the Repository */ + name: 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; + patch: Patch; +}; export type GetRepositoryFilesApiResponse = /** status 200 OK */ { /** 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; @@ -811,6 +894,22 @@ export type ReplaceRepositoryStatusApiArg = { fieldValidation?: string; repository: Repository; }; +export type UpdateRepositoryStatusApiResponse = /** status 200 OK */ Repository | /** status 201 Created */ Repository; +export type UpdateRepositoryStatusApiArg = { + /** name of the Repository */ + name: 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; + patch: Patch; +}; export type CreateRepositoryTestApiResponse = /** status 200 OK */ TestResults; export type CreateRepositoryTestApiArg = { /** name of the TestResults */ @@ -840,6 +939,38 @@ export type GetFrontendSettingsApiResponse = /** status 200 undefined */ Reposit export type GetFrontendSettingsApiArg = void; export type GetResourceStatsApiResponse = /** status 200 undefined */ ResourceStats; export type GetResourceStatsApiArg = void; +export type ApiResource = { + /** 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 ApiResourceList = { + /** 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: ApiResource[]; +}; export type Time = string; export type FieldsV1 = object; export type ManagedFieldsEntry = { @@ -1117,6 +1248,7 @@ export type Status = { /** 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 Patch = object; export type InlineSecureValue = | { /** Create a secure value -- this is only used for POST/PUT */ @@ -1506,36 +1638,58 @@ export type ResourceStats = { unmanaged?: ResourceCount[]; }; export const { + useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, useListJobQuery, + useLazyListJobQuery, useCreateJobMutation, useDeletecollectionJobMutation, useGetJobQuery, + useLazyGetJobQuery, useReplaceJobMutation, useDeleteJobMutation, + useUpdateJobMutation, useListRepositoryQuery, + useLazyListRepositoryQuery, useCreateRepositoryMutation, useDeletecollectionRepositoryMutation, useGetRepositoryQuery, + useLazyGetRepositoryQuery, useReplaceRepositoryMutation, useDeleteRepositoryMutation, + useUpdateRepositoryMutation, useGetRepositoryFilesQuery, + useLazyGetRepositoryFilesQuery, useGetRepositoryFilesWithPathQuery, + useLazyGetRepositoryFilesWithPathQuery, useReplaceRepositoryFilesWithPathMutation, useCreateRepositoryFilesWithPathMutation, useDeleteRepositoryFilesWithPathMutation, useGetRepositoryHistoryQuery, + useLazyGetRepositoryHistoryQuery, useGetRepositoryHistoryWithPathQuery, + useLazyGetRepositoryHistoryWithPathQuery, useGetRepositoryJobsQuery, + useLazyGetRepositoryJobsQuery, useCreateRepositoryJobsMutation, useGetRepositoryJobsWithPathQuery, + useLazyGetRepositoryJobsWithPathQuery, useGetRepositoryRefsQuery, + useLazyGetRepositoryRefsQuery, useGetRepositoryRenderWithPathQuery, + useLazyGetRepositoryRenderWithPathQuery, useGetRepositoryResourcesQuery, + useLazyGetRepositoryResourcesQuery, useGetRepositoryStatusQuery, + useLazyGetRepositoryStatusQuery, useReplaceRepositoryStatusMutation, + useUpdateRepositoryStatusMutation, useCreateRepositoryTestMutation, useGetRepositoryWebhookQuery, + useLazyGetRepositoryWebhookQuery, useCreateRepositoryWebhookMutation, useGetFrontendSettingsQuery, + useLazyGetFrontendSettingsQuery, useGetResourceStatsQuery, + useLazyGetResourceStatsQuery, } = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts index 9cabcb8ab7b..a4bfc1df1ba 100644 --- a/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts +++ b/packages/grafana-api-clients/src/clients/rtkq/shorturl/v1alpha1/endpoints.gen.ts @@ -597,3 +597,22 @@ export type Patch = object; export type GetGoto = { url: string; }; +export const { + useGetApiResourcesQuery, + useLazyGetApiResourcesQuery, + useListShortUrlQuery, + useLazyListShortUrlQuery, + useCreateShortUrlMutation, + useDeletecollectionShortUrlMutation, + useGetShortUrlQuery, + useLazyGetShortUrlQuery, + useReplaceShortUrlMutation, + useDeleteShortUrlMutation, + useUpdateShortUrlMutation, + useGetShortUrlGotoQuery, + useLazyGetShortUrlGotoQuery, + useGetShortUrlStatusQuery, + useLazyGetShortUrlStatusQuery, + useReplaceShortUrlStatusMutation, + useUpdateShortUrlStatusMutation, +} = injectedRtkApi; diff --git a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts index 82e1339f9bd..a9422a02815 100644 --- a/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts +++ b/packages/grafana-api-clients/src/scripts/generate-rtk-apis.ts @@ -17,6 +17,12 @@ type OperationDefinition = { }; type EndpointMatcher = string[] | ((operationName: string, operationDefinition: OperationDefinition) => boolean); +const defaultHooksOptions = { + queries: true, + lazyQueries: true, + mutations: true, +}; + /** * Helper to return consistent base API generation config */ @@ -28,6 +34,7 @@ const createAPIConfig = (app: string, version: string, filterEndpoints?: Endpoin apiFile: `../clients/rtkq/${app}/${version}/baseAPI.ts`, filterEndpoints, tag: true, + hooks: defaultHooksOptions, ...additional, }, }; @@ -39,10 +46,18 @@ const config: ConfigFile = { exportName: 'generatedAPI', outputFiles: { + // OpenAPI3 client with all endpoints + '../clients/rtkq/legacy/endpoints.gen.ts': { + schemaFile: path.join(basePath, 'public/openapi3.json'), + hooks: defaultHooksOptions, + tag: true, + apiFile: '../clients/rtkq/legacy/baseAPI.ts', + filterEndpoints: (_name, operation) => !operation.operation.deprecated, + }, '../clients/rtkq/migrate-to-cloud/endpoints.gen.ts': { schemaFile: path.join(basePath, 'public/openapi3.json'), apiFile: '../clients/rtkq/migrate-to-cloud/baseAPI.ts', - hooks: true, + hooks: defaultHooksOptions, filterEndpoints: [ 'getSessionList', 'getSession', @@ -67,45 +82,27 @@ const config: ConfigFile = { }, '../clients/rtkq/preferences/user/endpoints.gen.ts': { schemaFile: path.join(basePath, 'public/openapi3.json'), - hooks: true, + hooks: defaultHooksOptions, apiFile: '../clients/rtkq/preferences/user/baseAPI.ts', filterEndpoints: ['getUserPreferences', 'updateUserPreferences', 'patchUserPreferences'], }, '../clients/rtkq/user/endpoints.gen.ts': { schemaFile: path.join(basePath, 'public/openapi3.json'), - hooks: true, + hooks: defaultHooksOptions, apiFile: '../clients/rtkq/user/baseAPI.ts', filterEndpoints: ['starDashboardByUid', 'unstarDashboardByUid'], }, - ...createAPIConfig('iam', 'v0alpha1', ['getDisplayMapping']), - ...createAPIConfig('provisioning', 'v0alpha1', filterEndpoints, { hooks: true }), - ...createAPIConfig('folder', 'v1beta1', undefined), - ...createAPIConfig('advisor', 'v0alpha1', [ - 'createCheck', - 'getCheck', - 'listCheck', - 'deleteCheck', - 'updateCheck', - 'listCheckType', - 'updateCheckType', - ]), - ...createAPIConfig('playlist', 'v0alpha1', [ - 'listPlaylist', - 'getPlaylist', - 'createPlaylist', - 'deletePlaylist', - 'replacePlaylist', - ]), - ...createAPIConfig('shorturl', 'v1alpha1'), - ...createAPIConfig('preferences', 'v1alpha1', undefined, { hooks: true }), - ...createAPIConfig('dashboard', 'v0alpha1', ['getSearch']), + ...createAPIConfig('advisor', 'v0alpha1'), ...createAPIConfig('correlations', 'v0alpha1'), + ...createAPIConfig('dashboard', 'v0alpha1'), + ...createAPIConfig('folder', 'v1beta1'), + ...createAPIConfig('iam', 'v0alpha1'), + ...createAPIConfig('playlist', 'v0alpha1'), + ...createAPIConfig('preferences', 'v1alpha1'), + ...createAPIConfig('provisioning', 'v0alpha1'), + ...createAPIConfig('shorturl', 'v1alpha1'), // PLOP_INJECT_API_CLIENT - Used by the API client generator }, }; -function filterEndpoints(name: string) { - return !name.toLowerCase().includes('getapiresources') && !name.toLowerCase().includes('update'); -} - export default config; diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 950d5122932..c990c304caf 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -89,7 +89,7 @@ "@testing-library/react": "16.3.0", "@types/history": "4.7.11", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/papaparse": "5.3.16", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 990e344c5f2..6cc7cce2086 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -1081,6 +1081,7 @@ export interface FeatureToggles { alertingImportAlertmanagerUI?: boolean; /** * Enables image sharing functionality for dashboards + * @default true */ sharingDashboardImage?: boolean; /** diff --git a/packages/grafana-data/src/types/pluginExtensions.ts b/packages/grafana-data/src/types/pluginExtensions.ts index 47e130afcce..4b988c49a0a 100644 --- a/packages/grafana-data/src/types/pluginExtensions.ts +++ b/packages/grafana-data/src/types/pluginExtensions.ts @@ -230,6 +230,7 @@ export enum PluginExtensionPointPatterns { // Extension Points available in plugins export enum PluginExtensionExposedComponents { CentralAlertHistorySceneV1 = 'grafana/central-alert-history-scene/v1', + AddToDashboardFormV1 = 'grafana/add-to-dashboard-form/v1', } export type PluginExtensionPanelContext = { diff --git a/packages/grafana-data/src/vector/FunctionalVector.ts b/packages/grafana-data/src/vector/FunctionalVector.ts index f5ccfc7be8a..a84b6122232 100644 --- a/packages/grafana-data/src/vector/FunctionalVector.ts +++ b/packages/grafana-data/src/vector/FunctionalVector.ts @@ -153,13 +153,13 @@ export abstract class FunctionalVector { findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number { return this.toArray().findIndex(predicate, thisArg); } - entries(): IterableIterator<[number, T]> { + entries(): ArrayIterator<[number, T]> { return this.toArray().entries(); } - keys(): IterableIterator { + keys(): ArrayIterator { return this.toArray().keys(); } - values(): IterableIterator { + values(): ArrayIterator { return this.toArray().values(); } includes(searchElement: T, fromIndex?: number | undefined): boolean { diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index 008f555cc3b..9a50bc2ee5c 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@rollup/plugin-node-resolve": "16.0.1", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/semver": "7.7.1", "esbuild": "0.25.8", "rimraf": "6.0.1", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 3185d65d5c1..98cb22e9fb2 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -67,7 +67,7 @@ "@types/d3": "^7", "@types/jest": "^29.5.4", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-virtualized-auto-sizer": "1.0.8", "@types/tinycolor2": "1.4.6", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 5694659ab70..b79a584715f 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -35,7 +35,7 @@ "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "^29.5.4", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/systemjs": "6.15.3", "jest": "^29.6.4", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index c7413cdd517..4c8f16ac688 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -87,7 +87,7 @@ "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/jest": "29.5.14", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/pluralize": "^0.0.33", "@types/prismjs": "1.26.5", "esbuild": "0.25.8", diff --git a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx index a4f17087b21..2364bc80a86 100644 --- a/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx +++ b/packages/grafana-prometheus/src/querybuilder/QueryPatternsModal.tsx @@ -111,7 +111,6 @@ export const QueryPatternsModal = (props: Props) => { } )} isOpen={isOpen} - collapsible={true} onToggle={() => { const action = isOpen ? 'close' : 'open'; reportInteraction(`grafana_prom_kickstart_toggle_pattern_card`, { diff --git a/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx b/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx index 554201a820d..8253d195ea0 100644 --- a/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx +++ b/packages/grafana-prometheus/src/querybuilder/shared/QueryOptionGroup.tsx @@ -20,7 +20,6 @@ export function QueryOptionGroup({ title, children, collapsedInfo }: Props) {
; + /** + * Whether a query is a Logs Insights or Logs Anomalies query + */ + logsMode?: LogsMode; /** * Language used for querying logs, can be CWLI, SQL, or PPL. If empty, the default language is CWLI. */ @@ -265,6 +274,33 @@ export const defaultCloudWatchLogsQuery: Partial = { statsGroups: [], }; +/** + * Shape of a Cloudwatch Logs Anomalies query + */ +export interface CloudWatchLogsAnomaliesQuery extends common.DataQuery { + /** + * Used to filter only the anomalies found by a certain anomaly detector + */ + anomalyDetectionARN?: string; + id: string; + /** + * Whether a query is a Logs Insights or Logs Anomalies query + */ + logsMode?: LogsMode; + /** + * Whether a query is a Metrics, Logs or Annotations query + */ + queryMode?: CloudWatchQueryMode; + /** + * AWS region to query for the logs + */ + region: string; + /** + * Filter to return only anomalies that are 'SUPPRESSED', 'UNSUPPRESSED', or 'ALL' (default) + */ + suppressionState?: string; +} + export interface LogGroup { /** * AccountId of the log group diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index b370fc6206c..973f10bfb21 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -885,6 +885,11 @@ export interface Panel { * Depends on the panel plugin. See the plugin documentation for details. */ targets?: Array>; + /** + * Compare the current time range with a previous period + * For example "1d" to compare current period but shifted back 1 day + */ + timeCompare?: string; /** * Overrides the relative time range for individual panels, * which causes them to be different than what is selected in diff --git a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue index 5369503d6f2..ba637656ed8 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue +++ b/packages/grafana-schema/src/schema/dashboard/v2alpha0/dashboard.schema.cue @@ -409,6 +409,7 @@ QueryOptionsSpec: { interval?: string cacheTimeout?: string hideTimeOverride?: bool + timeCompare?: string } DataQueryKind: { diff --git a/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts b/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts index 19c324f1cd9..e9cbe9e649a 100644 --- a/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts +++ b/packages/grafana-schema/src/schema/dashboard/v2beta1/types.spec.gen.ts @@ -219,6 +219,7 @@ export interface QueryOptionsSpec { interval?: string; cacheTimeout?: string; hideTimeOverride?: boolean; + timeCompare?: string; } export const defaultQueryOptionsSpec = (): QueryOptionsSpec => ({ diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 2ea7c0f82c7..db36dfec348 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -42,7 +42,7 @@ "@testing-library/user-event": "14.6.1", "@types/jest": "^29.5.4", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@types/react-virtualized-auto-sizer": "1.0.8", diff --git a/packages/grafana-sql/src/components/ErrorBoundary.tsx b/packages/grafana-sql/src/components/ErrorBoundary.tsx index f7991415795..8500fb68fdf 100644 --- a/packages/grafana-sql/src/components/ErrorBoundary.tsx +++ b/packages/grafana-sql/src/components/ErrorBoundary.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { Component, ErrorInfo, PropsWithChildren } from 'react'; import { Trans } from '@grafana/i18n'; @@ -6,14 +6,14 @@ type Props = { fallBackComponent?: React.ReactNode; }; -export class ErrorBoundary extends React.Component, { hasError: boolean }> { - constructor(props: React.PropsWithChildren) { +export class ErrorBoundary extends Component, { hasError: boolean }> { + constructor(props: PropsWithChildren) { super(props); this.state = { hasError: false }; } - static getDerivedStateFromError() { - return { hasError: true }; + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + this.setState({ hasError: true }); } render() { diff --git a/packages/grafana-test-utils/package.json b/packages/grafana-test-utils/package.json index 70bf836f843..ecd8980173a 100644 --- a/packages/grafana-test-utils/package.json +++ b/packages/grafana-test-utils/package.json @@ -65,7 +65,7 @@ "@types/chance": "^1.1.7", "@types/jest": "29.5.14", "@types/lodash": "4.17.20", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "jest": "29.7.0", "typescript": "5.9.2" } diff --git a/packages/grafana-test-utils/src/handlers/api/search/handlers.ts b/packages/grafana-test-utils/src/handlers/api/search/handlers.ts index 2aebd03b174..d9fa090fbf1 100644 --- a/packages/grafana-test-utils/src/handlers/api/search/handlers.ts +++ b/packages/grafana-test-utils/src/handlers/api/search/handlers.ts @@ -17,12 +17,17 @@ const slugify = (str: string) => { .replace(/ +/g, '-'); }; +const typeFilterMap: Record = { + 'dash-db': 'dashboard', + 'dash-folder': 'folder', +}; + const getLegacySearchHandler = () => http.get('/api/search', ({ request }) => { const folderFilter = new URL(request.url).searchParams.get('folderUIDs') || null; const typeFilter = new URL(request.url).searchParams.get('type') || null; // Workaround for the fixture kind being 'dashboard' instead of 'dash-db' - const mappedTypeFilter = typeFilter === 'dash-db' ? 'dashboard' : typeFilter; + const mappedTypeFilter = typeFilter ? typeFilterMap[typeFilter] || typeFilter : null; const starredFilter = new URL(request.url).searchParams.get('starred') || null; const tagFilter = new URL(request.url).searchParams.getAll('tag') || null; diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 91dc005eadc..6c96682758b 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -165,7 +165,7 @@ "@types/is-hotkey": "0.1.10", "@types/jest": "29.5.14", "@types/mock-raf": "1.0.6", - "@types/node": "22.17.0", + "@types/node": "24.9.2", "@types/prismjs": "1.26.5", "@types/react": "18.3.18", "@types/react-color": "3.0.13", diff --git a/packages/grafana-ui/src/components/Alert/Alert.tsx b/packages/grafana-ui/src/components/Alert/Alert.tsx index 56b81d14351..8b3b55a41cc 100644 --- a/packages/grafana-ui/src/components/Alert/Alert.tsx +++ b/packages/grafana-ui/src/components/Alert/Alert.tsx @@ -26,6 +26,11 @@ export interface Props extends HTMLAttributes { topSpacing?: number; } +/** + * An alert displays an important message in a way that attracts the user's attention without interrupting the user's task. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-alert--docs + */ export const Alert = React.forwardRef( ( { diff --git a/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.tsx b/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.tsx index f53670b73a6..f78262d0548 100644 --- a/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.tsx +++ b/packages/grafana-ui/src/components/AutoSaveField/AutoSaveField.tsx @@ -21,6 +21,12 @@ export interface Props extends Omit { /** Input that will save its value on change */ children: (onChange: (newValue: T) => void) => React.ReactElement; } + +/** + * Used for form inputs that should save its content automatically. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-autosavefield--docs + */ export function AutoSaveField(props: Props) { const { invalid, diff --git a/packages/grafana-ui/src/components/Badge/Badge.tsx b/packages/grafana-ui/src/components/Badge/Badge.tsx index 201957ab534..53bcabc646d 100644 --- a/packages/grafana-ui/src/components/Badge/Badge.tsx +++ b/packages/grafana-ui/src/components/Badge/Badge.tsx @@ -47,6 +47,11 @@ const BadgeSkeleton: SkeletonComponent = ({ rootProps }) => { return ; }; +/** + * The badge component adds meta information to other content, for example about release status or new elements. You can add any `Icon` component or use the badge without an icon. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-badge--docs + */ export const Badge = attachSkeleton(BadgeComponent, BadgeSkeleton); const getSkeletonStyles = () => ({ diff --git a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx index 3392f059662..639bf41ca13 100644 --- a/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx +++ b/packages/grafana-ui/src/components/BarGauge/BarGauge.tsx @@ -56,6 +56,9 @@ export interface Props extends Themeable2 { isOverflow: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-bargauge--docs + */ export class BarGauge extends PureComponent { static defaultProps: Partial = { lcdCellWidth: 12, diff --git a/packages/grafana-ui/src/components/BigValue/BigValue.tsx b/packages/grafana-ui/src/components/BigValue/BigValue.tsx index 08bacf3e2af..870d350ba95 100644 --- a/packages/grafana-ui/src/components/BigValue/BigValue.tsx +++ b/packages/grafana-ui/src/components/BigValue/BigValue.tsx @@ -82,6 +82,11 @@ export interface Props extends Themeable2 { disableWideLayout?: boolean; } +/** + * Component for showing a value based on a [DisplayValue](https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/types/displayValue.ts#L5). + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-bigvalue--docs + */ export const BigValue = memo((props) => { const { onClick, className, hasLinks, theme, justifyMode = BigValueJustifyMode.Auto } = props; diff --git a/packages/grafana-ui/src/components/Button/Button.tsx b/packages/grafana-ui/src/components/Button/Button.tsx index c596904a39b..9e1d1c4a1a9 100644 --- a/packages/grafana-ui/src/components/Button/Button.tsx +++ b/packages/grafana-ui/src/components/Button/Button.tsx @@ -51,6 +51,9 @@ type CommonProps = BasePropsWithChildren | NoChildrenTooltip | NoChildrenAriaLab export type ButtonProps = CommonProps & ButtonHTMLAttributes; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-button--docs + */ export const Button = React.forwardRef( ( { diff --git a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx index 43aaff2bee2..e52b368f759 100644 --- a/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx +++ b/packages/grafana-ui/src/components/ButtonCascader/ButtonCascader.tsx @@ -28,6 +28,9 @@ export interface ButtonCascaderProps { hideDownIcon?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-buttoncascader--docs + */ export const ButtonCascader = (props: ButtonCascaderProps) => { const { onChange, className, loadData, icon, buttonProps, hideDownIcon, variant, disabled, ...rest } = props; const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx index ca3348c2b54..cd859f77256 100644 --- a/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx +++ b/packages/grafana-ui/src/components/CallToActionCard/CallToActionCard.tsx @@ -11,7 +11,11 @@ export interface CallToActionCardProps { className?: string; } -/** @deprecated Use instead */ +/** + * @deprecated Use `` instead. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-deprecated-calltoactioncard--docs + */ export const CallToActionCard = ({ message, callToActionElement, footer, className }: CallToActionCardProps) => { const css = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Card/Card.tsx b/packages/grafana-ui/src/components/Card/Card.tsx index 80194b56136..f1514f6f60e 100644 --- a/packages/grafana-ui/src/components/Card/Card.tsx +++ b/packages/grafana-ui/src/components/Card/Card.tsx @@ -51,6 +51,7 @@ const CardContext = React.createContext<{ /** * Generic card component * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-card--docs * @public */ export const Card: CardInterface = ({ diff --git a/packages/grafana-ui/src/components/Carousel/Carousel.tsx b/packages/grafana-ui/src/components/Carousel/Carousel.tsx index f3cc7892293..57f4ed59005 100644 --- a/packages/grafana-ui/src/components/Carousel/Carousel.tsx +++ b/packages/grafana-ui/src/components/Carousel/Carousel.tsx @@ -22,6 +22,11 @@ export interface CarouselProps { images: CarouselImage[]; } +/** + * The Carousel component displays a grid of image thumbnails that can be clicked to view full-sized images in a modal with navigation controls. It provides an elegant way to present collections of images or screenshots with fullscreen preview capabilities. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-carousel--docs + */ export const Carousel: React.FC = ({ images }) => { const [selectedIndex, setSelectedIndex] = useState(null); const [imageErrors, setImageErrors] = useState>({}); diff --git a/packages/grafana-ui/src/components/Cascader/Cascader.tsx b/packages/grafana-ui/src/components/Cascader/Cascader.tsx index ed8c1af953d..8f80b3b6093 100644 --- a/packages/grafana-ui/src/components/Cascader/Cascader.tsx +++ b/packages/grafana-ui/src/components/Cascader/Cascader.tsx @@ -321,4 +321,9 @@ class UnthemedCascader extends PureComponent { } } +/** + * The cascader component is a Select with a cascading flyout menu. When you have lots of options in your select, they can be hard to navigate from a regular dropdown list. In that case you can use the cascader to organize your options into groups hierarchically. Just like in the Select component, the cascader input doubles as a search field to quickly jump to a selection without navigating the list. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-cascader--docs + */ export const Cascader = withTheme2(UnthemedCascader); diff --git a/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx b/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx index 4a3b9e9b88a..1e9ab3e6de1 100644 --- a/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx +++ b/packages/grafana-ui/src/components/ClickOutsideWrapper/ClickOutsideWrapper.tsx @@ -13,6 +13,11 @@ export interface Props { children: React.ReactNode; } +/** + * A wrapper component that detects clicks outside of the elements by attaching event listener to `window` or `document` objects. Useful for components that require an action being triggered when a click outside has occurred, for example closing an overlay or popup. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/utilities-clickoutsidewrapper--docs + */ export function ClickOutsideWrapper({ includeButtonPress = true, parent = window, diff --git a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx index a42871dcde0..f1f00cdb25e 100644 --- a/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx +++ b/packages/grafana-ui/src/components/ClipboardButton/ClipboardButton.tsx @@ -21,6 +21,11 @@ export type Props = ButtonProps & { const SHOW_SUCCESS_DURATION = 2 * 1000; +/** + * A control for allowing the user to copy text to their clipboard. Uses native APIs on modern browsers, falling back to the old `document.execCommand('copy')` API on other browsers. The text to be copied should be provided via `getText` prop. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-clipboardbutton--docs + */ export function ClipboardButton({ onClipboardCopy, onClipboardError, diff --git a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx index 12c00333dfd..936d9837813 100644 --- a/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx +++ b/packages/grafana-ui/src/components/Collapse/CollapsableSection.tsx @@ -25,6 +25,11 @@ export interface Props { unmountContentWhenClosed?: boolean; } +/** + * A simple container for enabling collapsing/expanding of content. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-collapsablesection--docs + */ export const CollapsableSection = ({ label, isOpen, diff --git a/packages/grafana-ui/src/components/Collapse/Collapse.story.tsx b/packages/grafana-ui/src/components/Collapse/Collapse.story.tsx index 002a6f00093..23eda8f784e 100644 --- a/packages/grafana-ui/src/components/Collapse/Collapse.story.tsx +++ b/packages/grafana-ui/src/components/Collapse/Collapse.story.tsx @@ -2,6 +2,9 @@ import { action } from '@storybook/addon-actions'; import { useArgs } from '@storybook/preview-api'; import { Meta, StoryFn } from '@storybook/react'; +import { IconButton } from '../IconButton/IconButton'; +import { Stack } from '../Layout/Stack/Stack'; + import { Collapse, ControlledCollapse } from './Collapse'; import mdx from './Collapse.mdx'; @@ -22,7 +25,6 @@ const meta: Meta = { children: 'Panel data', isOpen: false, label: 'Collapse panel', - collapsible: true, }, argTypes: { onToggle: { action: 'toggled' }, @@ -57,4 +59,39 @@ Controlled.parameters = { }, }; +export const WithCustomLabel: StoryFn = (args) => { + const [, updateArgs] = useArgs(); + return ( + { + action('onToggle')({ isOpen: !args.isOpen }); + updateArgs({ isOpen: !args.isOpen }); + }} + label={ + + Collapse panel + + { + event.stopPropagation(); + action('onDeleteClick')(); + }} + aria-label="Delete" + name="trash-alt" + /> + + + } + > +

{args.children}

+
+ ); +}; +WithCustomLabel.parameters = { + controls: { + exclude: [...EXCLUDED_PROPS, 'label'], + }, +}; + export default meta; diff --git a/packages/grafana-ui/src/components/Collapse/Collapse.test.tsx b/packages/grafana-ui/src/components/Collapse/Collapse.test.tsx index 432ff8ef56e..817df10153d 100644 --- a/packages/grafana-ui/src/components/Collapse/Collapse.test.tsx +++ b/packages/grafana-ui/src/components/Collapse/Collapse.test.tsx @@ -42,7 +42,7 @@ describe('Collapse', () => { const onToggle = jest.fn(); const { user } = setup( - +
{contentText}
); diff --git a/packages/grafana-ui/src/components/Collapse/Collapse.tsx b/packages/grafana-ui/src/components/Collapse/Collapse.tsx index 57aec7a3ed4..195ea24b4d1 100644 --- a/packages/grafana-ui/src/components/Collapse/Collapse.tsx +++ b/packages/grafana-ui/src/components/Collapse/Collapse.tsx @@ -1,12 +1,11 @@ import { css, cx } from '@emotion/css'; -import { useState } from 'react'; +import { useId, useState } from 'react'; import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '../../themes/ThemeContext'; -import { clearButtonStyles } from '../Button/Button'; -import { Icon } from '../Icon/Icon'; +import { IconButton } from '../IconButton/IconButton'; const getStyles = (theme: GrafanaTheme2) => ({ collapse: css({ @@ -74,25 +73,21 @@ const getStyles = (theme: GrafanaTheme2) => ({ }, }), header: css({ + cursor: 'pointer', label: 'collapse__header', - padding: theme.spacing(1, 2, 1, 2), + padding: theme.spacing(1), display: 'flex', + gap: theme.spacing(1), }), - headerCollapsed: css({ - label: 'collapse__header--collapsed', - padding: theme.spacing(1, 2, 1, 2), + button: css({ + marginRight: 0, }), headerLabel: css({ label: 'collapse__header-label', fontWeight: theme.typography.fontWeightMedium, - marginRight: theme.spacing(1), fontSize: theme.typography.size.md, display: 'flex', - flex: '0 0 100%', - }), - icon: css({ - label: 'collapse__icon', - margin: theme.spacing(0.25, 1, 0, -1), + flex: 1, }), }); @@ -103,12 +98,12 @@ export interface Props { label: React.ReactNode; /** Indicates loading state of the content */ loading?: boolean; - /** Toggle collapsed header icon */ - collapsible?: boolean; /** Callback for the toggle functionality */ onToggle?: (isOpen: boolean) => void; /** Additional class name for the root element */ className?: string; + /** @deprecated this prop is no longer used and will be removed in Grafana 13 */ + collapsible?: boolean; } export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: React.PropsWithChildren) => { @@ -116,7 +111,6 @@ export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: React.Pr return ( { setOpen(!open); @@ -128,35 +122,43 @@ export const ControlledCollapse = ({ isOpen, onToggle, ...otherProps }: React.Pr ); }; -export const Collapse = ({ - isOpen, - label, - loading, - collapsible, - onToggle, - className, - children, -}: React.PropsWithChildren) => { - const buttonStyles = useStyles2(clearButtonStyles); +/** + * A content area, which can be horizontally collapsed and expanded. Can be used to hide extra information on the page. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-collapse--docs + */ +export const Collapse = ({ isOpen, label, loading, onToggle, className, children }: React.PropsWithChildren) => { const style = useStyles2(getStyles); + const labelId = useId(); + const contentId = useId(); + const onClickToggle = () => { if (onToggle) { onToggle(!isOpen); } }; - const panelClass = cx([style.collapse, className]); - const loaderClass = loading ? cx([style.loader, style.loaderActive]) : cx([style.loader]); - const headerClass = collapsible ? cx([style.header]) : cx([style.headerCollapsed]); + const loaderClass = loading ? cx([style.loader, style.loaderActive]) : style.loader; return (
- + {/* the inner button handles keyboard a11y. this is a convenience for mouse users */} + {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} +
+ +
+ {label} +
+
{isOpen && ( -
+
{children}
diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 819d60746db..f44b3f00fa4 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -84,6 +84,9 @@ export const colorPickerFactory = ( }; }; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-colorpicker--docs + */ export const ColorPicker = withTheme2(colorPickerFactory(ColorPickerPopover, 'ColorPicker')); export const SeriesColorPicker = withTheme2(colorPickerFactory(SeriesColorPickerPopover, 'SeriesColorPicker')); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx index 27f807051a8..5dd4e2acb25 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerInput.tsx @@ -19,6 +19,9 @@ export interface ColorPickerInputProps extends Omit( ({ value = '', onChange, returnColorAs = 'rgb', ...inputProps }, ref) => { const [currentColor, setColor] = useState(value); diff --git a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx index 2c0350d9329..6e38fa3148e 100644 --- a/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/SeriesColorPickerPopover.tsx @@ -12,6 +12,9 @@ export interface SeriesColorPickerPopoverProps extends ColorPickerProps, Popover onToggleAxis?: () => void; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-seriescolorpicker--docs + */ export const SeriesColorPickerPopover = (props: SeriesColorPickerPopoverProps) => { const { yaxis, onToggleAxis, color, ...colorPickerProps } = props; const yAxisLabel = t('grafana-ui.series-color-picker-popover.y-axis-usage', 'Use right y-axis'); diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.tsx index 9c4de9c2d8b..a4664c62441 100644 --- a/packages/grafana-ui/src/components/Combobox/Combobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/Combobox.tsx @@ -119,8 +119,10 @@ const noop = () => {}; export const VIRTUAL_OVERSCAN_ITEMS = 4; /** - * A performant Select replacement. + * A performant and accessible combobox component that supports both synchronous and asynchronous options loading. It provides type-ahead filtering, keyboard navigation, and virtual scrolling for handling large datasets efficiently. + * Replaces the Select component, and has better performance. * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-combobox--docs * @alpha */ export const Combobox = (props: ComboboxProps) => { diff --git a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx index 989bf3d0e2e..3a1e2856a68 100644 --- a/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx +++ b/packages/grafana-ui/src/components/Combobox/MultiCombobox.tsx @@ -35,6 +35,11 @@ interface MultiComboboxBaseProps export type MultiComboboxProps = MultiComboboxBaseProps & AutoSizeConditionals; +/** + * The behavior of the MultiCombobox is similar to that of the Combobox, but it allows you to select multiple options. For all non-multi behaviors, see the Combobox documentation. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-multicombobox--docs + */ export const MultiCombobox = (props: MultiComboboxProps) => { const { placeholder, diff --git a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx index 5ddfd473700..0a5e3db8688 100644 --- a/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx +++ b/packages/grafana-ui/src/components/ConfirmButton/ConfirmButton.tsx @@ -31,6 +31,11 @@ export interface Props { onCancel?(): void; } +/** + * The ConfirmButton is an interactive component that adds a double-confirm option to a clickable action. When clicked, the action is replaced by an inline confirmation with the option to cancel. In Grafana, this is used, for example, for editing values in settings tables. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-confirmbutton--docs + */ export const ConfirmButton = ({ children, className, diff --git a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx index ffcffa96f58..b57f9d8557b 100644 --- a/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx +++ b/packages/grafana-ui/src/components/ConfirmModal/ConfirmModal.tsx @@ -48,6 +48,11 @@ export interface ConfirmModalProps { disabled?: boolean; } +/** + * Used to request user for action confirmation, e.g. deleting items. Triggers provided `onConfirm` callback. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-confirmmodal--docs + */ export const ConfirmModal = ({ isOpen, title, diff --git a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx index 86802d58743..5a96497cdef 100644 --- a/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx +++ b/packages/grafana-ui/src/components/ContextMenu/ContextMenu.tsx @@ -22,6 +22,11 @@ export interface ContextMenuProps { renderHeader?: () => React.ReactNode; } +/** + * A menu displaying additional options when it's not possible to show them at all times due to a space constraint. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-contextmenu--docs + */ export const ContextMenu = React.memo( ({ x, y, onClose, focusOnOpen = true, renderMenuItems, renderHeader }: ContextMenuProps) => { const menuRef = useRef(null); diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx index 08671fcb108..f241ced1384 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx +++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.mdx @@ -5,6 +5,8 @@ import { DataSourceHttpSettings } from './DataSourceHttpSettings'; # DataSourceHttpSettings +> **Deprecated!** Use components from `@grafana/plugin-ui` instead, according to the [migration guide](https://github.com/grafana/plugin-ui/blob/main/src/components/ConfigEditor/migrating-from-datasource-http-settings.md) + Component for displaying the configuration options for a data source plugin. ### When to use diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx index e57f7747494..65f6ff77259 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx @@ -72,6 +72,8 @@ const LABEL_WIDTH = 26; /** * @deprecated Use components from `@grafana/plugin-ui` instead, according to the [migration guide](https://github.com/grafana/plugin-ui/blob/main/src/components/ConfigEditor/migrating-from-datasource-http-settings.md). + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-datasourcehttpsettings--docs */ export const DataSourceHttpSettings = (props: HttpSettingsProps) => { const { diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx index 28c88547023..a69a6d9fb18 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePicker/DatePicker.tsx @@ -19,7 +19,12 @@ export interface DatePickerProps { maxDate?: Date; } -/** @public */ +/** + * A component with a calendar view for selecting a date. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-datepicker--docs + * @public + * */ export const DatePicker = memo((props) => { const styles = useStyles2(getStyles); const { isOpen, onClose } = props; diff --git a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx index 4d2d6533a31..0103a1d21a5 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DatePickerWithInput/DatePickerWithInput.tsx @@ -27,7 +27,12 @@ export interface DatePickerWithInputProps extends Omit( ({ value, minDate, maxDate, onChange, closeOnSelect, placeholder = 'Date', ...rest }, ref) => { const [open, setOpen] = useState(false); diff --git a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx index 1f4eb4da47d..a7024681d42 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx @@ -60,6 +60,11 @@ export interface Props { timeZone?: TimeZone; } +/** + * A component for selecting a date *and* time. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-datetimepicker--docs + */ export const DateTimePicker = ({ date, maxDate, diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx index 74a19dd6efd..e07345bf111 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/RelativeTimeRangePicker.tsx @@ -41,6 +41,7 @@ type InputState = { }; /** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-relativetimerangepicker--docs * @internal */ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) { diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx index 67d3af9496a..90666dad8b3 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangeInput.tsx @@ -36,6 +36,11 @@ export interface TimeRangeInputProps { const noop = () => {}; +/** + * A variant of TimeRangePicker for use in forms. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-timerangeinput--docs + */ export const TimeRangeInput = ({ value, onChange, diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx index 3157b6ca837..b26a6e4c36d 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeRangePicker.tsx @@ -73,6 +73,9 @@ export interface State { isOpen: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-timerangepicker--docs + */ export function TimeRangePicker(props: TimeRangePickerProps) { const [isOpen, setOpen] = useState(false); diff --git a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx index f41279cb983..464dcc9fb24 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/TimeZonePicker.tsx @@ -31,6 +31,9 @@ export interface Props { openMenuOnFocus?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-timezonepicker--docs + */ export const TimeZonePicker = (props: Props) => { const { onChange, diff --git a/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx b/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx index 0207dcb67a8..2fe37d06d3f 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx +++ b/packages/grafana-ui/src/components/DateTimePickers/WeekStartPicker.tsx @@ -39,6 +39,9 @@ export function getWeekStart(override?: string): WeekStart { return 'monday'; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/date-time-pickers-weekstartpicker--docs + */ export const WeekStartPicker = (props: Props) => { const { onChange, width, autoFocus = false, onBlur, value, disabled = false, inputId } = props; const weekStarts: ComboboxOption[] = useMemo( diff --git a/packages/grafana-ui/src/components/Divider/Divider.tsx b/packages/grafana-ui/src/components/Divider/Divider.tsx index b9f5f580183..721ea8b58dc 100644 --- a/packages/grafana-ui/src/components/Divider/Divider.tsx +++ b/packages/grafana-ui/src/components/Divider/Divider.tsx @@ -9,6 +9,9 @@ interface DividerProps { spacing?: ThemeSpacingTokens; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-divider--docs + */ export const Divider = ({ direction = 'horizontal', spacing = 2 }: DividerProps) => { const styles = useStyles2(getStyles, spacing); diff --git a/packages/grafana-ui/src/components/Drawer/Drawer.tsx b/packages/grafana-ui/src/components/Drawer/Drawer.tsx index b81a3784b72..b7ee0252bfc 100644 --- a/packages/grafana-ui/src/components/Drawer/Drawer.tsx +++ b/packages/grafana-ui/src/components/Drawer/Drawer.tsx @@ -62,6 +62,11 @@ const drawerSizes = { lg: { width: '75vw', minWidth: 744 }, }; +/** + * Drawer is a slide in overlay that can be used to display additional information without hiding the main page content. It can be anchored to the left or right edge of the screen. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-drawer--docs + */ export function Drawer({ children, onClose, diff --git a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx index a2e23e78575..99f4db3b56b 100644 --- a/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx +++ b/packages/grafana-ui/src/components/Dropdown/ButtonSelect.tsx @@ -25,6 +25,8 @@ export interface Props extends HTMLAttributes { /** * @deprecated Use Combobox or Dropdown instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-buttonselect--docs */ const ButtonSelectComponent = (props: Props) => { const { className, options, value, onChange, narrow, variant, root, ...restProps } = props; diff --git a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx index 77cdcc92519..b098665db7b 100644 --- a/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx +++ b/packages/grafana-ui/src/components/Dropdown/Dropdown.tsx @@ -31,6 +31,11 @@ export interface Props { onVisibleChange?: (state: boolean) => void; } +/** + * Hook up a menu or other overlay to any trigger. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-dropdown--docs + */ export const Dropdown = React.memo(({ children, overlay, placement, offset, root, onVisibleChange }: Props) => { const [show, setShow] = useState(false); const transitionRef = useRef(null); diff --git a/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx b/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx index 2ddafbf865b..72f353b4ed7 100644 --- a/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx +++ b/packages/grafana-ui/src/components/EmptySearchResult/EmptySearchResult.tsx @@ -8,7 +8,11 @@ export interface Props { children: JSX.Element | string; } -/** @deprecated Use instead */ +/** + * @deprecated Use `` instead. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-deprecated-emptysearchresult--docs + */ const EmptySearchResult = ({ children }: Props) => { const styles = useStyles2(getStyles); return
{children}
; diff --git a/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx b/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx index a7ee3774666..7e9c73fb0f1 100644 --- a/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx +++ b/packages/grafana-ui/src/components/EmptyState/EmptyState.tsx @@ -38,6 +38,11 @@ interface Props { role?: AriaRole; } +/** + * The EmptyState component consists of a message and optionally an image, button, and additional information. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-emptystate--docs + */ export const EmptyState = ({ button, children, diff --git a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx index 63044d76d2f..818e919710c 100644 --- a/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/grafana-ui/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -1,6 +1,7 @@ -import { PureComponent, ReactNode, ComponentType, ErrorInfo } from 'react'; +import { PureComponent, ReactNode, ComponentType, ErrorInfo, memo } from 'react'; import { faro } from '@grafana/faro-web-sdk'; +import { t } from '@grafana/i18n'; import { Alert } from '../Alert/Alert'; @@ -33,6 +34,11 @@ interface State { errorInfo: ErrorInfo | null; } +/** + * A React component that catches errors in child components. Useful for logging or displaying a fallback UI in case of errors. More information about error boundaries is available at [React documentation website](https://reactjs.org/docs/error-boundaries.html). + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/utilities-errorboundary--docs + */ export class ErrorBoundary extends PureComponent { readonly state: State = { error: null, @@ -111,15 +117,9 @@ export interface ErrorBoundaryAlertProps { errorLogger?: (error: Error) => void; } -export class ErrorBoundaryAlert extends PureComponent { - static defaultProps: Partial = { - title: 'An unexpected error happened', - style: 'alertbox', - }; - - render() { - const { title, children, style, dependencies, errorLogger, boundaryName } = this.props; - +export const ErrorBoundaryAlert = memo( + ({ title, children, style = 'alertbox', dependencies, errorLogger, boundaryName }: ErrorBoundaryAlertProps) => { + const alertTitle = title ?? t('grafana-ui.error-boundary.title', 'An unexpected error happened'); return ( {({ error, errorInfo }) => { @@ -129,7 +129,7 @@ export class ErrorBoundaryAlert extends PureComponent { if (style === 'alertbox') { return ( - +
{error && error.toString()}
@@ -139,12 +139,14 @@ export class ErrorBoundaryAlert extends PureComponent { ); } - return ; + return ; }} ); } -} +); + +ErrorBoundaryAlert.displayName = 'ErrorBoundaryAlert'; /** * HOC for wrapping a component in an error boundary. diff --git a/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx b/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx index 6a67913e453..f1af47396f3 100644 --- a/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx +++ b/packages/grafana-ui/src/components/FeatureBadge/FeatureBadge.tsx @@ -8,6 +8,11 @@ export interface FeatureBadgeProps { tooltip?: string; } +/** + * A component for displaying information about different release stages of features, in accordance with the guidelines provided at [Grafana's Release Life Cycle](https://grafana.com/docs/release-life-cycle). + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-featurebadge--docs + */ export const FeatureBadge = ({ featureState, tooltip }: FeatureBadgeProps) => { const display = getPanelStateBadgeDisplayModel(featureState); return ; diff --git a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx index 120c7e08a09..ad1fa0b8b49 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileDropzone.tsx @@ -63,6 +63,11 @@ export interface DropzoneFile { retryUpload?: () => void; } +/** + * A dropzone component to use for file uploads. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-filedropzone--docs + */ export function FileDropzone({ options, children, diff --git a/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx b/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx index b13066621c6..e54d66b34b5 100644 --- a/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx +++ b/packages/grafana-ui/src/components/FileDropzone/FileListItem.tsx @@ -17,6 +17,11 @@ export interface FileListItemProps { removeFile?: (file: DropzoneFile) => void; } +/** + * A FileListItem component used for the FileDropzone component to show uploaded files. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-filelistitem--docs + */ export function FileListItem({ file: customFile, removeFile }: FileListItemProps) { const styles = useStyles2(getStyles); const { file, progress, error, abortUpload, retryUpload } = customFile; diff --git a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx index 528d4a7e193..5ad7662ff56 100644 --- a/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx +++ b/packages/grafana-ui/src/components/FileUpload/FileUpload.tsx @@ -27,6 +27,11 @@ export interface Props { showFileName?: boolean; } +/** + * A button-styled input that triggers file upload popup. Button text and accepted file extensions can be customized via `label` and `accepted` props respectively. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-fileupload--docs + */ export const FileUpload = ({ onFileUpload, className, diff --git a/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx index 48140e3940d..6fdbaa2e76d 100644 --- a/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx +++ b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx @@ -15,6 +15,11 @@ export interface FilterPillProps { icon?: IconName; } +/** + * A component used for quick toggling on/off filters. Mostly used in inline form components and transformation/query editors. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-filterpill--docs + */ export const FilterPill = ({ label, selected, onClick, icon = 'check' }: FilterPillProps) => { const styles = useStyles2(getStyles); const clearButton = useStyles2(clearButtonStyles); diff --git a/packages/grafana-ui/src/components/FormField/FormField.tsx b/packages/grafana-ui/src/components/FormField/FormField.tsx index 4d9bca9ded8..c4663db842e 100644 --- a/packages/grafana-ui/src/components/FormField/FormField.tsx +++ b/packages/grafana-ui/src/components/FormField/FormField.tsx @@ -24,6 +24,8 @@ export interface Props extends InputHTMLAttributes { * * For inline fields, use {@link InlineField}, {@link https://developers.grafana.com/ui/latest/index.html?path=/story/forms-inlinefield--basic See Storybook}. * @deprecated Please use the {@link Field} component, {@link https://developers.grafana.com/ui/latest/index.html?path=/story/forms-field--simple See Storybook}. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-deprecated-formfield--docs */ export const FormField = ({ label, diff --git a/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx b/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx index 6c9d6dd5334..f2e0527939a 100644 --- a/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx +++ b/packages/grafana-ui/src/components/FormattedValueDisplay/FormattedValueDisplay.tsx @@ -18,6 +18,11 @@ function fontSizeReductionFactor(fontSize: number) { return 0.6; } +/** + * Used to display a value, which also supports prefix and suffix. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-formattedvaluedisplay--docs + */ export const FormattedValueDisplay = ({ value, className, style, ...htmlProps }: Props) => { const hasPrefix = (value.prefix ?? '').length > 0; const hasSuffix = (value.suffix ?? '').length > 0; diff --git a/packages/grafana-ui/src/components/Forms/Checkbox.tsx b/packages/grafana-ui/src/components/Forms/Checkbox.tsx index 173f821e287..c20b0892e21 100644 --- a/packages/grafana-ui/src/components/Forms/Checkbox.tsx +++ b/packages/grafana-ui/src/components/Forms/Checkbox.tsx @@ -24,6 +24,9 @@ export interface CheckboxProps extends Omit, 'value' invalid?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-checkbox--docs + */ export const Checkbox = React.forwardRef( ( { label, description, value, htmlValue, onChange, disabled, className, indeterminate, invalid, ...inputProps }, diff --git a/packages/grafana-ui/src/components/Forms/Field.tsx b/packages/grafana-ui/src/components/Forms/Field.tsx index 1c80a2caf81..6c5884e04c9 100644 --- a/packages/grafana-ui/src/components/Forms/Field.tsx +++ b/packages/grafana-ui/src/components/Forms/Field.tsx @@ -43,6 +43,11 @@ export interface FieldProps extends HTMLAttributes { noMargin?: boolean; } +/** + * Field is the basic component for rendering form elements together with labels and description. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-field--docs + */ export const Field = React.forwardRef( ( { diff --git a/packages/grafana-ui/src/components/Forms/FieldArray.tsx b/packages/grafana-ui/src/components/Forms/FieldArray.tsx index fc0cda3b7ff..5c21767ab84 100644 --- a/packages/grafana-ui/src/components/Forms/FieldArray.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldArray.tsx @@ -9,6 +9,8 @@ export interface FieldArrayProps extends UseFieldArrayProps { /** * @deprecated use the `useFieldArray` hook from react-hook-form instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-fieldarray--docs */ export const FieldArray: FC = ({ name, control, children, ...rest }) => { const { fields, append, prepend, remove, swap, move, insert } = useFieldArray({ diff --git a/packages/grafana-ui/src/components/Forms/FieldSet.tsx b/packages/grafana-ui/src/components/Forms/FieldSet.tsx index 421ba3a5ad3..4f42cb96f8e 100644 --- a/packages/grafana-ui/src/components/Forms/FieldSet.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldSet.tsx @@ -14,6 +14,11 @@ export interface Props extends Omit, 'label'> { label?: React.ReactNode; } +/** + * Component used to group form elements inside a form, equivalent to HTML's [fieldset](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset) tag. Accepts optional label, which, if provided, is used as a text for the set's legend. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-fieldset--docs + */ export const FieldSet = ({ label, children, className, ...rest }: Props) => { const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx b/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx index 78f1aeab79e..3a3fd5b078b 100644 --- a/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx +++ b/packages/grafana-ui/src/components/Forms/FieldValidationMessage.tsx @@ -12,6 +12,11 @@ export interface FieldValidationMessageProps { horizontal?: boolean; } +/** + * Component for displaying a validation error message under an element. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-fieldvalidationmessage--docs + */ export const FieldValidationMessage = ({ children, horizontal, diff --git a/packages/grafana-ui/src/components/Forms/Form.tsx b/packages/grafana-ui/src/components/Forms/Form.tsx index 2eb45c69a0e..c251780be96 100644 --- a/packages/grafana-ui/src/components/Forms/Form.tsx +++ b/packages/grafana-ui/src/components/Forms/Form.tsx @@ -18,6 +18,8 @@ interface FormProps extends Omit({ defaultValues, diff --git a/packages/grafana-ui/src/components/Forms/InlineField.mdx b/packages/grafana-ui/src/components/Forms/InlineField.mdx index 1b13b8d3811..b08be513645 100644 --- a/packages/grafana-ui/src/components/Forms/InlineField.mdx +++ b/packages/grafana-ui/src/components/Forms/InlineField.mdx @@ -3,7 +3,7 @@ import { InlineField } from './InlineField'; # InlineField -A basic component for rendering form elements, like `Input`, `Select`, `Checkbox`, etc, inline together with `InlineLabel`. If the child element has `id` specified, the label's `htmlFor` attribute, pointing to the id, will be added. +A basic component for rendering form elements, like `Input`, `Checkbox`, `Combobox`, etc, inline together with `InlineLabel`. If the child element has `id` specified, the label's `htmlFor` attribute, pointing to the id, will be added. The width of the `InlineLabel` can be modified via `labelWidth` prop, which is a multiple of 8px. For example, an `InlineField` with `labelWidth={20}` will have a label 160px wide. diff --git a/packages/grafana-ui/src/components/Forms/InlineField.tsx b/packages/grafana-ui/src/components/Forms/InlineField.tsx index c63d87b585b..a2e7bd4f9e7 100644 --- a/packages/grafana-ui/src/components/Forms/InlineField.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineField.tsx @@ -29,6 +29,11 @@ export interface Props extends Omit, 'css'> { children: ReactNode | ReactNode[]; } +/** + * Used to align multiple InlineField components in one row. The row will wrap if the width of the children exceeds its own. Equivalent to the div with gf-form-inline class name. Multiple InlineFieldRows vertically stack on each other. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-inlinefieldrow--docs + */ export const InlineFieldRow = ({ children, className, ...htmlProps }: Props) => { const styles = useStyles2(getStyles); return ( diff --git a/packages/grafana-ui/src/components/Forms/InlineLabel.mdx b/packages/grafana-ui/src/components/Forms/InlineLabel.mdx index 9a451f07922..be5b5766a9d 100644 --- a/packages/grafana-ui/src/components/Forms/InlineLabel.mdx +++ b/packages/grafana-ui/src/components/Forms/InlineLabel.mdx @@ -3,7 +3,7 @@ import { InlineLabel } from './InlineLabel'; # InlineLabel -A horizontal variant of `Label`, primarily used in query editors. Can be combined with form components that expect a label, eg. `Input`, `Select`, `Checkbox`. +A horizontal variant of `Label`, primarily used in query editors. Can be combined with form components that expect a label, eg. `Input`, `Checkbox`, `Combobox`. If you need to add additional explanation, use the tooltip prop, which will render an info icon with tooltip inside the label. For query editor readability, the label text should be as short as possible (4 words or fewer). diff --git a/packages/grafana-ui/src/components/Forms/InlineLabel.tsx b/packages/grafana-ui/src/components/Forms/InlineLabel.tsx index fef27352c7f..63b7b7ccb46 100644 --- a/packages/grafana-ui/src/components/Forms/InlineLabel.tsx +++ b/packages/grafana-ui/src/components/Forms/InlineLabel.tsx @@ -25,6 +25,11 @@ export interface Props extends Omit category?: React.ReactNode[]; } +/** + * The label component can be used to label form inputs with a heading/"Option name" and a description. To automatically have the right arrangement of this component with a form input, use the `Field` component. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-label--docs + */ export const Label = ({ children, description, className, category, ...labelProps }: LabelProps) => { const styles = useStyles2(getLabelStyles); const categories = category?.map((c, i) => { diff --git a/packages/grafana-ui/src/components/Forms/Legend.tsx b/packages/grafana-ui/src/components/Forms/Legend.tsx index 7b48c149b7b..90af8e1229e 100644 --- a/packages/grafana-ui/src/components/Forms/Legend.tsx +++ b/packages/grafana-ui/src/components/Forms/Legend.tsx @@ -21,6 +21,11 @@ export const getLegendStyles = (theme: GrafanaTheme2) => { }; }; +/** + * Legend should be used to add a caption to a group of related form elements that have been grouped toegheter into a `FieldSet`. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-legend--docs + */ export const Legend = ({ children, className, ...legendProps }: LabelProps) => { const styles = useStyles2(getLegendStyles); diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx index b47ff9913d3..ebef41cbed8 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonGroup/RadioButtonGroup.tsx @@ -24,6 +24,11 @@ export interface RadioButtonGroupProps { invalid?: boolean; } +/** + * RadioButtonGroup is used to select a single value from multiple mutually exclusive options. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-radiobuttongroup--docs + */ export function RadioButtonGroup({ options, value, diff --git a/packages/grafana-ui/src/components/Forms/RadioButtonList/RadioButtonList.tsx b/packages/grafana-ui/src/components/Forms/RadioButtonList/RadioButtonList.tsx index a6366c64756..6127d143438 100644 --- a/packages/grafana-ui/src/components/Forms/RadioButtonList/RadioButtonList.tsx +++ b/packages/grafana-ui/src/components/Forms/RadioButtonList/RadioButtonList.tsx @@ -22,6 +22,11 @@ export interface RadioButtonListProps { className?: string; } +/** + * RadioButtonList is used to select a single value from multiple mutually exclusive options usually in a vertical manner. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-radiobuttonlist--docs + */ export function RadioButtonList({ name, id, diff --git a/packages/grafana-ui/src/components/Icon/Icon.tsx b/packages/grafana-ui/src/components/Icon/Icon.tsx index ed987489f14..fa4e1fac6d3 100644 --- a/packages/grafana-ui/src/components/Icon/Icon.tsx +++ b/packages/grafana-ui/src/components/Icon/Icon.tsx @@ -42,6 +42,11 @@ const getIconStyles = (theme: GrafanaTheme2) => { }; }; +/** + * Grafana's icon wrapper component. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/iconography-icon--docs + */ export const Icon = React.memo( React.forwardRef( ({ size = 'md', type = 'default', name, className, style, title = '', ...rest }, ref) => { diff --git a/packages/grafana-ui/src/components/IconButton/IconButton.tsx b/packages/grafana-ui/src/components/IconButton/IconButton.tsx index bbc4519f71f..f2d44eb2fa1 100644 --- a/packages/grafana-ui/src/components/IconButton/IconButton.tsx +++ b/packages/grafana-ui/src/components/IconButton/IconButton.tsx @@ -41,8 +41,18 @@ interface BasePropsWithAriaLabel extends BaseProps { ['aria-label']: string; } -export type Props = BasePropsWithTooltip | BasePropsWithAriaLabel; +interface BasePropsWithAriaLabelledBy extends BaseProps { + /** Reference to an element id that labels the button. No tooltip will be set in this case. */ + ['aria-labelledby']: string; +} +export type Props = BasePropsWithTooltip | BasePropsWithAriaLabel | BasePropsWithAriaLabelledBy; + +/** + * This component looks just like an icon but behaves like a button. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-iconbutton--docs + */ export const IconButton = React.forwardRef((props, ref) => { const { size = 'md', variant = 'secondary' } = props; let limitedIconSize: LimitedIconSize; diff --git a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx index cb445321333..bf3e0e7f69c 100644 --- a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx +++ b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx @@ -23,7 +23,11 @@ export interface InfoBoxProps extends Omit, onDismiss?: () => void; } -/** @deprecated use Alert with severity info */ +/** + * @deprecated use Alert with severity info. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-deprecated-infobox--docs + * */ export const InfoBox = React.memo( React.forwardRef( ({ title, className, children, branded, url, urlTitle, onDismiss, severity = 'info', ...otherProps }, ref) => { diff --git a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx index 6f189837c43..9af5cadaa74 100644 --- a/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx +++ b/packages/grafana-ui/src/components/InfoTooltip/InfoTooltip.tsx @@ -6,7 +6,11 @@ interface InfoTooltipProps extends Omit { children: PopoverContent; } -/** @deprecated Use instead */ +/** + * @deprecated Use instead. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-deprecated-infotooltip--docs + */ export const InfoTooltip = ({ children, ...restProps }: InfoTooltipProps) => { return ; }; diff --git a/packages/grafana-ui/src/components/InlineToast/InlineToast.tsx b/packages/grafana-ui/src/components/InlineToast/InlineToast.tsx index 178b0032b1e..de79c028d9f 100644 --- a/packages/grafana-ui/src/components/InlineToast/InlineToast.tsx +++ b/packages/grafana-ui/src/components/InlineToast/InlineToast.tsx @@ -24,6 +24,11 @@ export interface InlineToastProps { alternativePlacement?: Side; } +/** + * Used to indicate temporal status near fields/components, such as a *Saved* indicator next to a field, or a little *Copied!* indicator above a button. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-inlinetoast--docs + */ export function InlineToast({ referenceElement, children, suffixIcon, placement }: InlineToastProps) { const styles = useStyles2(getStyles); const theme = useTheme2(); diff --git a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx index 2cf17612d99..2e533cf065f 100644 --- a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx +++ b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx @@ -20,6 +20,11 @@ export interface Props extends InputProps { defaultValue?: string | number | readonly string[]; } +/** + * You can use it or regular text input. When used, AutoSizeInput resizes itself to the current content. For an array of data or tree-structured data, consider using `Combobox` or `Cascader` respectively. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-autosizeinput--docs + */ export const AutoSizeInput = React.forwardRef((props, ref) => { const { defaultValue = '', diff --git a/packages/grafana-ui/src/components/Input/Input.tsx b/packages/grafana-ui/src/components/Input/Input.tsx index 62aca888d03..4da6c9c22c7 100644 --- a/packages/grafana-ui/src/components/Input/Input.tsx +++ b/packages/grafana-ui/src/components/Input/Input.tsx @@ -34,6 +34,11 @@ interface StyleDeps { width?: number; } +/** + * Used for regular text input. For an array of data or tree-structured data, consider using `Combobox` or `Cascader` respectively. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-input--docs + */ export const Input = forwardRef((props, ref) => { const { className, diff --git a/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.tsx b/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.tsx index 4c0faebccf5..a046f24b89c 100644 --- a/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.tsx +++ b/packages/grafana-ui/src/components/InteractiveTable/InteractiveTable.tsx @@ -172,7 +172,13 @@ interface WithoutExpandableRow extends BaseProps = WithExpandableRow | WithoutExpandableRow; -/** @alpha */ +/** + * The InteractiveTable is used to display and select data efficiently. It allows for the display and modification of detailed information. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-interactivetable--docs + * + * @alpha + */ export function InteractiveTable({ className, columns, diff --git a/packages/grafana-ui/src/components/Layout/Box/Box.tsx b/packages/grafana-ui/src/components/Layout/Box/Box.tsx index c012eb9d5f3..a7d10e4c746 100644 --- a/packages/grafana-ui/src/components/Layout/Box/Box.tsx +++ b/packages/grafana-ui/src/components/Layout/Box/Box.tsx @@ -70,6 +70,11 @@ export interface BoxProps extends FlexProps, SizeProps, Omit; } +/** + * The Box Component is the most basic layout component. It can be used to build more complex components and layouts with properties that use our design tokens instead of using CSS. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-box--docs + */ export const Box = forwardRef>((props, ref) => { const { children, diff --git a/packages/grafana-ui/src/components/Layout/Grid/Grid.tsx b/packages/grafana-ui/src/components/Layout/Grid/Grid.tsx index 1573f881a04..ffba1eb88de 100644 --- a/packages/grafana-ui/src/components/Layout/Grid/Grid.tsx +++ b/packages/grafana-ui/src/components/Layout/Grid/Grid.tsx @@ -34,6 +34,11 @@ interface PropsWithMinColumnWidth extends GridPropsBase { /** 'columns' and 'minColumnWidth' are mutually exclusive */ type GridProps = PropsWithColumns | PropsWithMinColumnWidth; +/** + * The Grid component is a layout component that allows you to create a grid of columns and rows to organize content and elements. It is a wrapper around the [CSS Grid](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout) specification. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-grid--docs + */ export const Grid = forwardRef((props, ref) => { const { alignItems, children, gap, rowGap, columnGap, columns, minColumnWidth, ...rest } = props; const styles = useStyles2(getGridStyles, gap, rowGap, columnGap, columns, minColumnWidth, alignItems); diff --git a/packages/grafana-ui/src/components/Layout/Layout.tsx b/packages/grafana-ui/src/components/Layout/Layout.tsx index a2f34d44d06..71fe0803b83 100644 --- a/packages/grafana-ui/src/components/Layout/Layout.tsx +++ b/packages/grafana-ui/src/components/Layout/Layout.tsx @@ -33,6 +33,8 @@ export interface ContainerProps { /** * @deprecated use Stack component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-deprecated-groups--docs */ export const Layout = ({ children, diff --git a/packages/grafana-ui/src/components/Layout/Space.mdx b/packages/grafana-ui/src/components/Layout/Space.mdx index 47bc0cb8e54..dd5182a8cea 100644 --- a/packages/grafana-ui/src/components/Layout/Space.mdx +++ b/packages/grafana-ui/src/components/Layout/Space.mdx @@ -5,7 +5,7 @@ import { Space } from './Space'; # Space -The `Space` component is a component used to add space between elements. Horizontal space is added using the `h` prop, while vertical space is added using the `v` prop. When adding horizontal space between inline or inline-block elements, the `layout` props should be set to `inline`, otherwise the `block` value of the prop can be used. +The `Space` component is a component used to add space between elements. Horizontal space is added using the `h` prop, while vertical space is added using the `v` prop. When adding horizontal space between inline or inline-block elements, the `layout` prop should be set to `inline`, otherwise the `block` value of the prop can be used. ### Usage diff --git a/packages/grafana-ui/src/components/Layout/Space.tsx b/packages/grafana-ui/src/components/Layout/Space.tsx index 2df48501ea4..8a81bcb1eed 100644 --- a/packages/grafana-ui/src/components/Layout/Space.tsx +++ b/packages/grafana-ui/src/components/Layout/Space.tsx @@ -19,6 +19,11 @@ export interface SpaceProps { layout?: 'block' | 'inline'; } +/** + * The Space component is a component used to add space between elements. Horizontal space is added using the `h` prop, while vertical space is added using the `v` prop. When adding horizontal space between inline or inline-block elements, the `layout` prop should be set to `"inline"`, otherwise the `"block"` value of the prop can be used. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-space--docs + */ export const Space = ({ v = 0, h = 0, layout }: SpaceProps) => { return ; }; diff --git a/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx b/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx index e54cc724f2d..b3a4984b110 100644 --- a/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx +++ b/packages/grafana-ui/src/components/Layout/Stack/Stack.tsx @@ -21,7 +21,8 @@ interface StackProps extends FlexProps, SizeProps, Omit((props, ref) => { const { diff --git a/packages/grafana-ui/src/components/Link/TextLink.tsx b/packages/grafana-ui/src/components/Link/TextLink.tsx index d46a1ba3bc3..1f7ec1e16de 100644 --- a/packages/grafana-ui/src/components/Link/TextLink.tsx +++ b/packages/grafana-ui/src/components/Link/TextLink.tsx @@ -43,6 +43,11 @@ const svgSizes: { bodySmall: 'xs', }; +/** + * The TextLink component renders an anchor tag `` that takes users to another page, external or internal to Grafana. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/foundations-textlink--docs + */ export const TextLink = forwardRef( ( { href, color = 'link', external = false, inline = true, variant = 'body', weight, icon, children, ...rest }, diff --git a/packages/grafana-ui/src/components/List/List.tsx b/packages/grafana-ui/src/components/List/List.tsx index a58bb3c5d4e..d4b86807a7b 100644 --- a/packages/grafana-ui/src/components/List/List.tsx +++ b/packages/grafana-ui/src/components/List/List.tsx @@ -2,7 +2,11 @@ import { PureComponent } from 'react'; import { ListProps, AbstractList } from './AbstractList'; -/** @deprecated Use ul/li/arr.map directly instead */ +/** + * @deprecated Use ul/li/arr.map directly instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-deprecated-list--docs + */ // no point converting, this is deprecated // eslint-disable-next-line react-prefer-function-component/react-prefer-function-component export class List extends PureComponent> { diff --git a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx index 213f39b4243..1ac038fe75f 100644 --- a/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx +++ b/packages/grafana-ui/src/components/LoadingBar/LoadingBar.tsx @@ -18,6 +18,11 @@ const MAX_DURATION_MS = 4000; const DEFAULT_ANIMATION_DELAY = 300; const MAX_TRANSLATE_X = (100 / BAR_WIDTH) * 100; +/** + * The LoadingBar is used as a simple loading slider animation in the top of its container. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-loadingbar--docs + */ export function LoadingBar({ width, delay = DEFAULT_ANIMATION_DELAY, ariaLabel = 'Loading bar' }: LoadingBarProps) { const durationMs = Math.min(Math.max(Math.round(width * MILLISECONDS_PER_PIXEL), MIN_DURATION_MS), MAX_DURATION_MS); const styles = useStyles2(getStyles, delay, durationMs); diff --git a/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx b/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx index fb3ec626437..9f50ed9d072 100644 --- a/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx +++ b/packages/grafana-ui/src/components/LoadingPlaceholder/LoadingPlaceholder.tsx @@ -15,6 +15,9 @@ export interface LoadingPlaceholderProps extends HTMLAttributes } /** + * Loading indicator with a text. Used to alert a user to wait for an activity to complete. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-loadingplaceholder--docs * @public */ export const LoadingPlaceholder = ({ text, className, ...rest }: LoadingPlaceholderProps) => { diff --git a/packages/grafana-ui/src/components/Menu/Menu.tsx b/packages/grafana-ui/src/components/Menu/Menu.tsx index f77a26bb0e7..a1439aa5356 100644 --- a/packages/grafana-ui/src/components/Menu/Menu.tsx +++ b/packages/grafana-ui/src/components/Menu/Menu.tsx @@ -22,6 +22,9 @@ export interface MenuProps extends React.HTMLAttributes { onKeyDown?: React.KeyboardEventHandler; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-menu--docs + */ const MenuComp = React.forwardRef( ({ header, children, ariaLabel, onOpen, onClose, onKeyDown, ...otherProps }, forwardedRef) => { const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Modal/Modal.tsx b/packages/grafana-ui/src/components/Modal/Modal.tsx index 9937fe93f9f..fc00bc9e562 100644 --- a/packages/grafana-ui/src/components/Modal/Modal.tsx +++ b/packages/grafana-ui/src/components/Modal/Modal.tsx @@ -48,6 +48,9 @@ interface WithCustomTitleProps extends BaseProps { export type Props = WithStringTitleProps | WithCustomTitleProps; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-modal--docs + */ export function Modal(props: PropsWithChildren) { const { title, diff --git a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx index 276520b873d..a0e17c92b1d 100644 --- a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx +++ b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx @@ -196,6 +196,11 @@ class UnthemedCodeEditor extends PureComponent { } } +/** + * Monaco Code editor. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-codeeditor--docs + */ export const CodeEditor = withTheme2(UnthemedCodeEditor); const getStyles = (theme: GrafanaTheme2) => { diff --git a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx index 296d79212f6..93e59296166 100644 --- a/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx +++ b/packages/grafana-ui/src/components/PageLayout/PageToolbar.tsx @@ -34,7 +34,11 @@ export interface Props { forceShowLeftItems?: boolean; } -/** @deprecated Use Page instead */ +/** + * @deprecated Use Page instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-deprecated-pagetoolbar--docs + */ export const PageToolbar = memo( ({ title, diff --git a/packages/grafana-ui/src/components/Pagination/Pagination.tsx b/packages/grafana-ui/src/components/Pagination/Pagination.tsx index 70c447e059c..c0c25d4186b 100644 --- a/packages/grafana-ui/src/components/Pagination/Pagination.tsx +++ b/packages/grafana-ui/src/components/Pagination/Pagination.tsx @@ -21,6 +21,11 @@ export interface Props { className?: string; } +/** + * Component used for rendering a page selector below paginated content. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-pagination--docs + */ export const Pagination = ({ currentPage, numberOfPages, diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx index 16d67ea51db..05d3ae406e5 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx +++ b/packages/grafana-ui/src/components/PanelChrome/PanelChrome.tsx @@ -116,6 +116,10 @@ interface HoverHeader { export type PanelPadding = 'none' | 'md'; /** + * Component used for rendering content wrapped in the same style as grafana panels. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-panelchrome--docs + * * @internal */ export function PanelChrome({ diff --git a/packages/grafana-ui/src/components/PanelContainer/PanelContainer.tsx b/packages/grafana-ui/src/components/PanelContainer/PanelContainer.tsx index 5279c363675..fd68335b2a7 100644 --- a/packages/grafana-ui/src/components/PanelContainer/PanelContainer.tsx +++ b/packages/grafana-ui/src/components/PanelContainer/PanelContainer.tsx @@ -8,7 +8,11 @@ import { useStyles2 } from '../../themes/ThemeContext'; type Props = DetailedHTMLProps, HTMLDivElement>; // TODO: Reimplement this with Box -/** @deprecated Use Box instead */ +/** + * @deprecated Use Box instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/layout-deprecated-panelcontainer--docs + */ export const PanelContainer = ({ children, className, ...props }: Props) => { const styles = useStyles2(getStyles); return ( diff --git a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx index 698e61c466b..9935f2323d1 100644 --- a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx +++ b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx @@ -22,6 +22,8 @@ export interface PluginSignatureBadgeProps extends HTMLAttributes { plugins: Array>; runOnChangeDebounced: Function; @@ -238,6 +232,16 @@ export class UnThemedQueryField extends PureComponent { diff --git a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx index 128c56b1cb6..3dcc25e8875 100644 --- a/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx +++ b/packages/grafana-ui/src/components/RadialGauge/RadialGauge.tsx @@ -72,6 +72,9 @@ export type RadialGradientMode = 'none' | 'auto'; export type RadialTextMode = 'auto' | 'value_and_name' | 'value' | 'name' | 'none'; export type RadialShape = 'circle' | 'gauge'; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-radialgauge--docs + */ export function RadialGauge(props: RadialGaugeProps) { const { width = 256, diff --git a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx index d99958e88d1..363222fcad6 100644 --- a/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx +++ b/packages/grafana-ui/src/components/RefreshPicker/RefreshPicker.tsx @@ -29,6 +29,11 @@ export interface Props { isOnCanvas?: boolean; } +/** + * This component is used on dashboards to refresh visualizations. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-refreshpicker--docs + */ export class RefreshPicker extends PureComponent { static offOption = { label: 'Off', diff --git a/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.tsx b/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.tsx index d5ec22c2440..3fb806bc162 100644 --- a/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.tsx +++ b/packages/grafana-ui/src/components/RenderUserContentAsHTML/RenderUserContentAsHTML.tsx @@ -9,6 +9,11 @@ export interface RenderUserContentAsHTMLProps content: string; } +/** + * Abstraction layer component for sanitizing and rendering an html content. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/utilities-renderusercontentashtml--docs + */ export function RenderUserContentAsHTML({ component, content, diff --git a/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.tsx b/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.tsx index af7907b9562..60da6f97a8d 100644 --- a/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.tsx +++ b/packages/grafana-ui/src/components/ScrollContainer/ScrollContainer.tsx @@ -17,6 +17,11 @@ interface Props extends Omit>( ( { diff --git a/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx b/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx index 4bfaac7c603..238d3adf3ea 100644 --- a/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx +++ b/packages/grafana-ui/src/components/SecretFormField/SecretFormField.tsx @@ -32,6 +32,8 @@ export interface Props extends Omit, 'onRe * to the user (like datasource passwords). * * @deprecated Please use the {@link SecretInput} component with a {@link Field} instead, {@link https://developers.grafana.com/ui/latest/index.html?path=/story/forms-secretinput--basic as seen in Storybook} + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-deprecated-secretformfield--docs */ export const SecretFormField = ({ label = 'Password', diff --git a/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx index b7cc7b41a72..23286c5a393 100644 --- a/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx +++ b/packages/grafana-ui/src/components/SecretInput/SecretInput.tsx @@ -14,6 +14,11 @@ export type Props = React.ComponentProps & { export const CONFIGURED_TEXT = 'configured'; export const RESET_BUTTON_TEXT = 'Reset'; +/** + * Used for secret/password input. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-secretinput--docs + */ export const SecretInput = ({ isConfigured, onReset, ...props }: Props) => ( {!isConfigured && } diff --git a/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx b/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx index b7196732660..a10ad157120 100644 --- a/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx +++ b/packages/grafana-ui/src/components/SecretTextArea/SecretTextArea.tsx @@ -32,6 +32,8 @@ const getStyles = (theme: GrafanaTheme2) => { /** * Text area that does not disclose an already configured value but lets the user reset the current value and enter a new one. * Typically useful for asymmetric cryptography keys. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-secrettextarea--docs */ export const SecretTextArea = ({ isConfigured, onReset, ...props }: Props) => { const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Segment/Segment.tsx b/packages/grafana-ui/src/components/Segment/Segment.tsx index 3abec60b774..b39399965c4 100644 --- a/packages/grafana-ui/src/components/Segment/Segment.tsx +++ b/packages/grafana-ui/src/components/Segment/Segment.tsx @@ -20,6 +20,9 @@ export interface SegmentSyncProps extends SegmentProps, Omit({ options, value, diff --git a/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx b/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx index 804b1bbe92f..8a3caf6e73e 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentAsync.tsx @@ -29,6 +29,9 @@ export interface SegmentAsyncProps extends SegmentProps, Omit({ value, onChange, diff --git a/packages/grafana-ui/src/components/Segment/SegmentInput.tsx b/packages/grafana-ui/src/components/Segment/SegmentInput.tsx index 22e9411297e..bcb46c93f04 100644 --- a/packages/grafana-ui/src/components/Segment/SegmentInput.tsx +++ b/packages/grafana-ui/src/components/Segment/SegmentInput.tsx @@ -20,6 +20,9 @@ export interface SegmentInputProps const FONT_SIZE = 14; +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-segmentinput--docs + */ export function SegmentInput({ value: initialValue, onChange, diff --git a/packages/grafana-ui/src/components/Select/Select.tsx b/packages/grafana-ui/src/components/Select/Select.tsx index f543fa5dab5..7aaa338ae1c 100644 --- a/packages/grafana-ui/src/components/Select/Select.tsx +++ b/packages/grafana-ui/src/components/Select/Select.tsx @@ -10,11 +10,20 @@ import { VirtualizedSelectAsyncProps, } from './types'; -/** @deprecated Use Combobox component instead */ +/** + * @deprecated Use Combobox component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function Select(props: SelectCommonProps & Rest) { return ; } +/** + * @deprecated Use Combobox component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function MultiSelect(props: MultiSelectCommonProps & Rest) { // @ts-ignore return ; @@ -25,17 +34,29 @@ export interface AsyncSelectProps extends Omit, 'options value?: T | SelectableValue | null; } -/** @deprecated Use Combobox component instead */ +/** + * @deprecated Use Combobox component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function AsyncSelect(props: AsyncSelectProps & Rest) { return ; } -/** @deprecated Use Combobox component instead - it's virtualised by default! */ +/** + * @deprecated Use Combobox component instead - it's virtualised by default! + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function VirtualizedSelect(props: VirtualizedSelectProps & Rest) { return ; } -/** @deprecated Use Combobox component instead - it's virtualised by default! */ +/** + * @deprecated Use Combobox component instead - it's virtualised by default! + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function AsyncVirtualizedSelect(props: VirtualizedSelectAsyncProps & Rest) { return ; } @@ -45,6 +66,11 @@ interface AsyncMultiSelectProps extends Omit, 'opti value?: Array>; } +/** + * @deprecated Use Combobox component instead + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-select--docs + */ export function AsyncMultiSelect(props: AsyncMultiSelectProps & Rest) { // @ts-ignore return ; diff --git a/packages/grafana-ui/src/components/Slider/RangeSlider.tsx b/packages/grafana-ui/src/components/Slider/RangeSlider.tsx index 794ea5b795f..6b791fd7211 100644 --- a/packages/grafana-ui/src/components/Slider/RangeSlider.tsx +++ b/packages/grafana-ui/src/components/Slider/RangeSlider.tsx @@ -15,6 +15,8 @@ import { RangeSliderProps } from './types'; * @public * * RichHistoryQueriesTab uses this Range Component + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-rangeslider--docs */ export const RangeSlider = ({ min, diff --git a/packages/grafana-ui/src/components/Slider/Slider.test.tsx b/packages/grafana-ui/src/components/Slider/Slider.test.tsx index e24ccd7e751..1fb4ab1c03a 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.test.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.test.tsx @@ -33,6 +33,18 @@ describe('Slider', () => { expect(sliderInput).toHaveValue('10'); }); + it('hides the slider input if showInput is false', () => { + render(); + + const slider = screen.getByRole('slider'); + const sliderInput = screen.queryByRole('textbox'); + + expect(slider).toHaveAttribute('aria-valuemin', '10'); + expect(slider).toHaveAttribute('aria-valuemax', '20'); + expect(slider).toHaveAttribute('aria-valuenow', '10'); + expect(sliderInput).not.toBeInTheDocument(); + }); + it('renders correct contents with a value', () => { render(); diff --git a/packages/grafana-ui/src/components/Slider/Slider.tsx b/packages/grafana-ui/src/components/Slider/Slider.tsx index e8c377070bf..d8d310fa92b 100644 --- a/packages/grafana-ui/src/components/Slider/Slider.tsx +++ b/packages/grafana-ui/src/components/Slider/Slider.tsx @@ -13,6 +13,8 @@ import { SliderProps } from './types'; /** * @public + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-slider--docs */ export const Slider = ({ min, @@ -27,6 +29,7 @@ export const Slider = ({ marks, included, inputId, + showInput = true, }: SliderProps) => { const isHorizontal = orientation === 'horizontal'; const styles = useStyles2(getStyles, isHorizontal, Boolean(marks)); @@ -112,17 +115,19 @@ export const Slider = ({ included={included} /> - + {showInput && ( + + )} ); diff --git a/packages/grafana-ui/src/components/Slider/types.ts b/packages/grafana-ui/src/components/Slider/types.ts index 812a8bed7d7..880bfd2f712 100644 --- a/packages/grafana-ui/src/components/Slider/types.ts +++ b/packages/grafana-ui/src/components/Slider/types.ts @@ -14,6 +14,8 @@ interface CommonSliderProps { marks?: SliderMarks; /** If the value is true, it means a continuous value interval, otherwise, it is a independent value. */ included?: boolean; + /** Controls visibility of the input field. Defaults to true. */ + showInput?: boolean; } export interface SliderProps extends CommonSliderProps { value?: number; diff --git a/packages/grafana-ui/src/components/Spinner/Spinner.tsx b/packages/grafana-ui/src/components/Spinner/Spinner.tsx index 78905e29eec..af1c3b60aca 100644 --- a/packages/grafana-ui/src/components/Spinner/Spinner.tsx +++ b/packages/grafana-ui/src/components/Spinner/Spinner.tsx @@ -29,6 +29,10 @@ interface PropsWithDeprecatedSize extends Omit { /** * @public + * + * Spinner is `fa-spinner` icon animated. It is used to alert a user to wait for an activity to complete. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-spinner--docs */ export const Spinner = ({ className, diff --git a/packages/grafana-ui/src/components/Splitter/useSplitter.ts b/packages/grafana-ui/src/components/Splitter/useSplitter.ts index b5343ae80aa..18fb95a532e 100644 --- a/packages/grafana-ui/src/components/Splitter/useSplitter.ts +++ b/packages/grafana-ui/src/components/Splitter/useSplitter.ts @@ -49,6 +49,11 @@ const propsForDirection = { }, } as const; +/** + * The splitter creates two resizable panes, either horizontally or vertically. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/utilities-usesplitter--docs + */ export function useSplitter(options: UseSplitterOptions) { const { direction, diff --git a/packages/grafana-ui/src/components/Switch/Switch.tsx b/packages/grafana-ui/src/components/Switch/Switch.tsx index 0a8654b1425..d2b63a2d76a 100644 --- a/packages/grafana-ui/src/components/Switch/Switch.tsx +++ b/packages/grafana-ui/src/components/Switch/Switch.tsx @@ -14,6 +14,11 @@ export interface Props extends Omit, 'value'> { invalid?: boolean; } +/** + * Switch is a representation of an on-off state – like a light switch. So you can use Switch to toggle binary states. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-switch--docs + */ export const Switch = forwardRef( ({ value, checked, onChange, id, label, disabled, invalid = false, ...inputProps }, ref) => { if (checked) { diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx index acedb2091a7..453fdb237b0 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/SparklineCell.tsx @@ -17,6 +17,7 @@ import { import { measureText } from '../../../../utils/measureText'; import { FormattedValueDisplay } from '../../../FormattedValueDisplay/FormattedValueDisplay'; import { Sparkline } from '../../../Sparkline/Sparkline'; +import { MaybeWrapWithLink } from '../components/MaybeWrapWithLink'; import { SparklineCellProps, TableCellStyles } from '../types'; import { getAlignmentFactor, getCellOptions, prepareSparklineValue } from '../utils'; @@ -38,7 +39,11 @@ export const SparklineCell = (props: SparklineCellProps) => { const sparkline = prepareSparklineValue(value, field); if (!sparkline) { - return <>{field.config.noValue || t('grafana-ui.table.sparkline.no-data', 'no data')}; + return ( + + {field.config.noValue || t('grafana-ui.table.sparkline.no-data', 'no data')} + + ); } // Get the step from the first two values to null-fill the x-axis based on timerange @@ -87,10 +92,10 @@ export const SparklineCell = (props: SparklineCellProps) => { } return ( - <> + {valueElement} - + ); }; @@ -107,8 +112,12 @@ function getTableSparklineCellOptions(field: Field): TableSparklineCellOptions { export const getStyles: TableCellStyles = (theme, { textAlign }) => css({ - width: '100%', - gap: theme.spacing(1), - justifyContent: 'space-between', - ...(textAlign === 'right' && { flexDirection: 'row-reverse' }), + '&, & > a': { + width: '100%', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: theme.spacing(1), + ...(textAlign === 'right' && { flexDirection: 'row-reverse' }), + }, }); diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx index 5f64c58773a..a3772785d63 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx @@ -94,7 +94,6 @@ import { predicateByName, shouldTextOverflow, shouldTextWrap, - withDataLinksActionsTooltip, getSummaryCellTextAlign, parseStyleJson, IS_SAFARI_26, @@ -406,7 +405,6 @@ export function TableNG(props: TableNGProps) { const result: FromFieldsResult = { columns: [], cellRootRenderers: {}, - colsWithTooltip: {}, }; let lastRowIdx = -1; @@ -464,7 +462,6 @@ export function TableNG(props: TableNGProps) { const shouldOverflow = !IS_SAFARI_26 && rowHeight !== 'auto' && (shouldTextOverflow(field) || Boolean(maxRowHeight)); const textWrap = rowHeight === 'auto' || shouldTextWrap(field); - const withTooltip = withDataLinksActionsTooltip(field, cellType); const canBeColorized = canFieldBeColorized(cellType, applyToRowBgFn); const cellStyleOptions: TableCellStyleOptions = { textAlign, @@ -473,8 +470,6 @@ export function TableNG(props: TableNGProps) { maxHeight: maxRowHeight, }; - result.colsWithTooltip[displayName] = withTooltip; - const defaultCellStyles = getDefaultCellStyles(theme, cellStyleOptions); const cellSpecificStyles = getCellSpecificStyles(cellType, field, theme, cellStyleOptions); const linkStyles = getLinkStyles(theme, canBeColorized); @@ -737,7 +732,7 @@ export function TableNG(props: TableNGProps) { ); const [nestedFieldWidths] = useColWidths(firstRowNestedData?.fields ?? [], availableWidth); - const { columns, cellRootRenderers, colsWithTooltip } = useMemo(() => { + const { columns, cellRootRenderers } = useMemo(() => { const result = fromFields(visibleFields, widths); // if nested frames are present, augment the columns to include the nested table expander column. @@ -805,7 +800,6 @@ export function TableNG(props: TableNGProps) { const field = columns[column.idx].field; if ( - colsWithTooltip[getDisplayName(field)] && target instanceof HTMLElement && // this walks up the tree to find either a faux link wrapper or the cell root // it then only proceeds if we matched the faux link wrapper diff --git a/packages/grafana-ui/src/components/Table/TableNG/components/MaybeWrapWithLink.tsx b/packages/grafana-ui/src/components/Table/TableNG/components/MaybeWrapWithLink.tsx index b43728dd1e8..7758bf8fd20 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/components/MaybeWrapWithLink.tsx +++ b/packages/grafana-ui/src/components/Table/TableNG/components/MaybeWrapWithLink.tsx @@ -34,4 +34,5 @@ export const MaybeWrapWithLink = memo(({ field, rowIdx, children }: MaybeWrapWit // raw value return children; }); + MaybeWrapWithLink.displayName = 'MaybeWrapWithLink'; diff --git a/packages/grafana-ui/src/components/Table/TableNG/types.ts b/packages/grafana-ui/src/components/Table/TableNG/types.ts index c4d63b2826b..b828dfddbb6 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/types.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/types.ts @@ -308,7 +308,6 @@ export type CellRootRenderer = (key: React.Key, props: CellRendererProps; - colsWithTooltip: Record; } export interface FooterFieldState extends FieldState { diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts index e3ab23b3e0e..867ee96b983 100644 --- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts +++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts @@ -955,15 +955,6 @@ export function getApplyToRowBgFn( } } -/** @internal */ -export function withDataLinksActionsTooltip(field: Field, cellType: TableCellDisplayMode) { - return ( - cellType !== TableCellDisplayMode.DataLinks && - cellType !== TableCellDisplayMode.Actions && - (field.config.links?.length ?? 0) + (field.config.actions?.length ?? 0) > 1 - ); -} - /** @internal */ export function canFieldBeColorized( cellType: TableCellDisplayMode, diff --git a/packages/grafana-ui/src/components/Table/TableRT/Table.tsx b/packages/grafana-ui/src/components/Table/TableRT/Table.tsx index 4df81505431..ea909d4642f 100644 --- a/packages/grafana-ui/src/components/Table/TableRT/Table.tsx +++ b/packages/grafana-ui/src/components/Table/TableRT/Table.tsx @@ -40,6 +40,11 @@ const COLUMN_MIN_WIDTH = 150; const FOOTER_ROW_HEIGHT = 36; const NO_DATA_TEXT = 'No data'; +/** + * Used for displaying tabular data + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-table--docs + */ export const Table = memo((props: Props) => { const { ariaLabel, diff --git a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx index 57c3b9943e4..dfd967e04e3 100644 --- a/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx +++ b/packages/grafana-ui/src/components/TableInputCSV/TableInputCSV.tsx @@ -102,7 +102,11 @@ export class UnThemedTableInputCSV extends PureComponent { } } -/** @deprecated */ +/** + * @deprecated + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-deprecated-tableinputcsv--docs + */ export const TableInputCSV = withTheme2(UnThemedTableInputCSV); TableInputCSV.displayName = 'TableInputCSV'; diff --git a/packages/grafana-ui/src/components/Tabs/Tab.tsx b/packages/grafana-ui/src/components/Tabs/Tab.tsx index 6eb695df437..ae58e136aeb 100644 --- a/packages/grafana-ui/src/components/Tabs/Tab.tsx +++ b/packages/grafana-ui/src/components/Tabs/Tab.tsx @@ -31,6 +31,9 @@ export interface TabProps extends HTMLProps { disabled?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-tabs--docs + */ export const Tab = React.forwardRef( ( { diff --git a/packages/grafana-ui/src/components/Tabs/TabContent.tsx b/packages/grafana-ui/src/components/Tabs/TabContent.tsx index a22f6102dba..9bceb46f3c8 100644 --- a/packages/grafana-ui/src/components/Tabs/TabContent.tsx +++ b/packages/grafana-ui/src/components/Tabs/TabContent.tsx @@ -9,6 +9,9 @@ interface Props extends HTMLAttributes { children: ReactNode; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-tabs--docs + */ export const TabContent = ({ children, className, ...restProps }: Props) => { const styles = useStyles2(getTabContentStyle); diff --git a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx index 8ba4405dcfa..c33734f7ea9 100644 --- a/packages/grafana-ui/src/components/Tabs/TabsBar.tsx +++ b/packages/grafana-ui/src/components/Tabs/TabsBar.tsx @@ -13,6 +13,11 @@ export interface Props { hideBorder?: boolean; } +/** + * A composition component for rendering a TabBar with Tabs for navigation. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-tabs--docs + */ export const TabsBar = forwardRef(({ children, className, hideBorder = false }, ref) => { const styles = useStyles2(getStyles); diff --git a/packages/grafana-ui/src/components/Tags/Tag.tsx b/packages/grafana-ui/src/components/Tags/Tag.tsx index e1dde995bad..1d529dfcc24 100644 --- a/packages/grafana-ui/src/components/Tags/Tag.tsx +++ b/packages/grafana-ui/src/components/Tags/Tag.tsx @@ -57,6 +57,11 @@ const TagSkeleton: SkeletonComponent = ({ rootProps }) => { return ; }; +/** + * Used for displaying metadata, for example to add more details to search results. Background and border colors are generated from the tag name. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-tag--docs + */ export const Tag = attachSkeleton(TagComponent, TagSkeleton); const getSkeletonStyles = () => ({ diff --git a/packages/grafana-ui/src/components/Tags/TagList.tsx b/packages/grafana-ui/src/components/Tags/TagList.tsx index d128c64610b..729fc920f28 100644 --- a/packages/grafana-ui/src/components/Tags/TagList.tsx +++ b/packages/grafana-ui/src/components/Tags/TagList.tsx @@ -73,6 +73,11 @@ const TagListSkeleton: SkeletonComponent = ({ rootProps }) => { ); }; +/** + * List of tags with predefined margins and positioning. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/information-taglist--docs + */ export const TagList = attachSkeleton(TagListComponent, TagListSkeleton); const getSkeletonStyles = (theme: GrafanaTheme2) => ({ diff --git a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx index 7bcdbb6852a..1b6e78213da 100644 --- a/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx +++ b/packages/grafana-ui/src/components/TagsInput/TagsInput.tsx @@ -29,6 +29,11 @@ export interface Props { autoColors?: boolean; } +/** + * A set of an input field and a button next to it that allows the user to add new tags. The added tags are previewed next to the input and can be removed by clicking the "X" icon. You can customize the width of the input. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-tagsinput--docs + */ export const TagsInput = forwardRef( ( { diff --git a/packages/grafana-ui/src/components/Text/Text.tsx b/packages/grafana-ui/src/components/Text/Text.tsx index 55b88ee4011..56cb2b529bb 100644 --- a/packages/grafana-ui/src/components/Text/Text.tsx +++ b/packages/grafana-ui/src/components/Text/Text.tsx @@ -29,6 +29,11 @@ export interface TextProps extends Omit, 'clas children: NonNullable; } +/** + * The Text component can be used to apply typography styles in a simple way, without the need of extra css. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/foundations-text--docs + */ export const Text = React.forwardRef( ( { element = 'span', variant, weight, color, truncate, italic, textAlignment, children, tabular, ...restProps }, diff --git a/packages/grafana-ui/src/components/TextArea/TextArea.tsx b/packages/grafana-ui/src/components/TextArea/TextArea.tsx index 1c84344348f..6c80d370dc6 100644 --- a/packages/grafana-ui/src/components/TextArea/TextArea.tsx +++ b/packages/grafana-ui/src/components/TextArea/TextArea.tsx @@ -11,6 +11,11 @@ export interface Props extends Omit, 'size'> { invalid?: boolean; } +/** + * Use for multi line inputs like descriptions. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/inputs-textarea--docs + */ export const TextArea = forwardRef(({ invalid, className, ...props }, ref) => { const styles = useStyles2(getTextAreaStyle, invalid); diff --git a/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx b/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx index 991490e36aa..84f13eaf112 100644 --- a/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx +++ b/packages/grafana-ui/src/components/Toggletip/Toggletip.tsx @@ -48,6 +48,11 @@ export interface ToggletipProps { onOpen?: () => void; } +/** + * Toggletips, similar to Tooltips, provide contextual support for users when needed. They are hidden by default, a UI trigger or text link are clicked to set them to their visible state. Toggletips, unlike tooltips, are persistent until a user takes action to dismiss them by clicking on the required “X” (close) trigger. Toggletips are capable of containing varying types of complex content including interactive components, buttons, and dropdowns. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-toggletip--docs + */ export const Toggletip = memo( ({ children, diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx index 376b1b3fc38..40b468d73fb 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButton.tsx @@ -41,6 +41,11 @@ export type ToolbarButtonProps = CommonProps & ButtonHTMLAttributes( ( { diff --git a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButtonRow.tsx b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButtonRow.tsx index 000713c57c4..54608a3acbd 100644 --- a/packages/grafana-ui/src/components/ToolbarButton/ToolbarButtonRow.tsx +++ b/packages/grafana-ui/src/components/ToolbarButton/ToolbarButtonRow.tsx @@ -17,6 +17,11 @@ export interface Props extends HTMLAttributes { alignment?: 'left' | 'right'; } +/** + * A container for multiple ToolbarButtons. Provides automatic overflow behaviour when the buttons no longer fit in the container. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/navigation-toolbarbuttonrow--docs + */ export const ToolbarButtonRow = forwardRef( ({ alignment = 'left', className, children, ...rest }, ref) => { // null/undefined are valid react children so we need to filter them out to prevent unnecessary padding diff --git a/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx b/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx index b44d48ddf38..665e2d7bf6c 100644 --- a/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx +++ b/packages/grafana-ui/src/components/Tooltip/Tooltip.tsx @@ -34,6 +34,9 @@ export interface TooltipProps { interactive?: boolean; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/overlays-tooltip--docs + */ export const Tooltip = forwardRef( ({ children, theme, interactive, show, placement, content }, forwardedRef) => { const arrowRef = useRef(null); diff --git a/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx b/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx index dff45cb2a18..ac53b424bdb 100644 --- a/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx +++ b/packages/grafana-ui/src/components/UnitPicker/UnitPicker.tsx @@ -16,6 +16,9 @@ function formatCreateLabel(input: string) { return `Custom unit: ${input}`; } +/** + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-unitpicker--docs + */ export class UnitPicker extends PureComponent { onChange = (value: SelectableValue) => { this.props.onChange(value.value); diff --git a/packages/grafana-ui/src/components/UsersIndicator/UserIcon.tsx b/packages/grafana-ui/src/components/UsersIndicator/UserIcon.tsx index 3d8dd97f962..ca5ff1f665c 100644 --- a/packages/grafana-ui/src/components/UsersIndicator/UserIcon.tsx +++ b/packages/grafana-ui/src/components/UsersIndicator/UserIcon.tsx @@ -51,6 +51,11 @@ const getUserInitials = (name?: string) => { return `${first?.[0] ?? ''}${last?.[0] ?? ''}`.toUpperCase(); }; +/** + * UserIcon renders a user icon and displays the user's name or initials along with the user's active status or last viewed date. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/iconography-usericon--docs + */ export const UserIcon = ({ userView, className, diff --git a/packages/grafana-ui/src/components/UsersIndicator/UsersIndicator.tsx b/packages/grafana-ui/src/components/UsersIndicator/UsersIndicator.tsx index 44518ce25c7..66c9a0bddca 100644 --- a/packages/grafana-ui/src/components/UsersIndicator/UsersIndicator.tsx +++ b/packages/grafana-ui/src/components/UsersIndicator/UsersIndicator.tsx @@ -16,6 +16,12 @@ export interface UsersIndicatorProps { /** onClick handler for the user number indicator */ onClick?: () => void; } + +/** + * A component that displays a set of user icons indicating which users are currently active. If there are too many users to display all the icons, it will collapse the icons into a single icon with a number indicating the number of additional users. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/iconography-usersindicator--docs + */ export const UsersIndicator = ({ users, onClick, limit = 4 }: UsersIndicatorProps) => { const styles = useStyles2(getStyles); if (!users.length) { diff --git a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx index 128a038ea8d..8647140c615 100644 --- a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx +++ b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx @@ -37,6 +37,11 @@ export interface ValuePickerProps { buttonCss?: string; } +/** + * A component that looks like a button but transforms into a select when clicked. + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/pickers-valuepicker--docs + */ export function ValuePicker({ 'aria-label': ariaLabel, label, diff --git a/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx b/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx index 6194b3ac6b2..11b5ed6dc00 100644 --- a/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx +++ b/packages/grafana-ui/src/components/VizLayout/VizLayout.tsx @@ -29,6 +29,8 @@ export interface VizLayoutComponentType extends FC { /** * @beta + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-vizlayout--docs */ export const VizLayout: VizLayoutComponentType = ({ width, height, legend, children }) => { const theme = useTheme2(); diff --git a/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx b/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx index f07c3abff22..0a8dc48d3a9 100644 --- a/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx +++ b/packages/grafana-ui/src/components/VizLegend/VizLegend.tsx @@ -13,6 +13,8 @@ import { mapMouseEventToMode } from './utils'; /** * @public + * + * https://developers.grafana.com/ui/latest/index.html?path=/docs/plugins-vizlegend--docs */ export function VizLegend({ items, diff --git a/pkg/api/ds_query.go b/pkg/api/ds_query.go index dd33ba97817..6d27fbfc404 100644 --- a/pkg/api/ds_query.go +++ b/pkg/api/ds_query.go @@ -76,6 +76,8 @@ func (hs *HTTPServer) QueryMetricsV2(c *contextmodel.ReqContext) response.Respon var resp *backend.QueryDataResponse var err error + + hs.log.Debug("QueryMetricsV2: request received", "time_in_query", handleTimeInQuery) if handleTimeInQuery { resp, err = hs.queryDataService.QueryDataNew(c.Req.Context(), c.SignedInUser, c.SkipDSCache, reqDTO) } else { diff --git a/pkg/api/ds_query_test.go b/pkg/api/ds_query_test.go index e330f8f4438..11e12800056 100644 --- a/pkg/api/ds_query_test.go +++ b/pkg/api/ds_query_test.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/infra/db/dbtest" "github.com/grafana/grafana/pkg/infra/localcache" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" pluginClient "github.com/grafana/grafana/pkg/plugins/manager/client" @@ -86,6 +87,7 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) { server := SetupAPITestServer(t, func(hs *HTTPServer) { hs.queryDataService = qds hs.QuotaService = quotatest.New(false, nil) + hs.log = log.New("test-logger") }) t.Run("Status code is 400 when data source response has an error", func(t *testing.T) { @@ -252,6 +254,7 @@ func TestDataSourceQueryError(t *testing.T) { err := r.Add(context.Background(), p) require.NoError(t, err) ds := &fakeDatasources.FakeDataSourceService{} + hs.log = log.New("test-logger") hs.queryDataService = query.ProvideService( cfg, &fakeDatasources.FakeCacheService{}, diff --git a/pkg/api/short_url_test.go b/pkg/api/short_url_test.go index 6d3da5fbe0c..116d4f70328 100644 --- a/pkg/api/short_url_test.go +++ b/pkg/api/short_url_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/shorturls" @@ -31,7 +32,7 @@ func TestShortURLAPIEndpoint(t *testing.T) { Path: cmd.Path, } service := &fakeShortURLService{ - createShortURLFunc: func(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { + createShortURLFunc: func(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { return createResp, nil }, createConvertShortURLToDTO: func(shortURL *shorturls.ShortUrl, appURL string) *dtos.ShortURL { @@ -81,7 +82,7 @@ func createShortURLScenario(t *testing.T, desc string, url string, routePattern } type fakeShortURLService struct { - createShortURLFunc func(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) + createShortURLFunc func(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) createConvertShortURLToDTO func(shortURL *shorturls.ShortUrl, appURL string) *dtos.ShortURL } @@ -89,11 +90,11 @@ func (s *fakeShortURLService) List(ctx context.Context, orgID int64) ([]*shortur return nil, nil } -func (s *fakeShortURLService) GetShortURLByUID(ctx context.Context, user *user.SignedInUser, uid string) (*shorturls.ShortUrl, error) { +func (s *fakeShortURLService) GetShortURLByUID(ctx context.Context, user identity.Requester, uid string) (*shorturls.ShortUrl, error) { return nil, nil } -func (s *fakeShortURLService) CreateShortURL(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { +func (s *fakeShortURLService) CreateShortURL(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { if s.createShortURLFunc != nil { return s.createShortURLFunc(ctx, user, cmd) } diff --git a/pkg/apimachinery/apis/common/v0alpha1/unstructured.go b/pkg/apimachinery/apis/common/v0alpha1/unstructured.go index db4c2e73302..3046d898049 100644 --- a/pkg/apimachinery/apis/common/v0alpha1/unstructured.go +++ b/pkg/apimachinery/apis/common/v0alpha1/unstructured.go @@ -52,7 +52,7 @@ func (u *Unstructured) SetUnstructuredContent(content map[string]any) { // MarshalJSON ensures that the unstructured object produces proper // JSON when passed to Go's standard JSON library. -func (u *Unstructured) MarshalJSON() ([]byte, error) { +func (u Unstructured) MarshalJSON() ([]byte, error) { return json.Marshal(u.Object) } diff --git a/pkg/apimachinery/identity/context.go b/pkg/apimachinery/identity/context.go index 664f519dd8f..1ce76389f59 100644 --- a/pkg/apimachinery/identity/context.go +++ b/pkg/apimachinery/identity/context.go @@ -125,6 +125,10 @@ var serviceIdentityPermissions = getWildcardPermissions( "datasources:query", "datasources:read", "datasources:delete", + "library.panels:create", // ActionLibraryPanelsCreate + "library.panels:read", // ActionLibraryPanelsRead + "library.panels:write", // ActionLibraryPanelsWrite + "library.panels:delete", // ActionLibraryPanelsDelete "alert.provisioning:write", "alert.provisioning.secrets:read", "users:read", // accesscontrol.ActionUsersRead, diff --git a/pkg/apis/query/v0alpha1/query.go b/pkg/apis/query/v0alpha1/query.go index ef40c17ff45..f6ad172c348 100644 --- a/pkg/apis/query/v0alpha1/query.go +++ b/pkg/apis/query/v0alpha1/query.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" + "github.com/grafana/grafana/pkg/expr" ) // Generic query request with shared time across all values @@ -28,6 +29,14 @@ type QueryDataResponse struct { backend.QueryDataResponse `json:",inline"` } +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type SQLSchemas struct { + metav1.TypeMeta `json:",inline"` + + // Backend wrapper (external dependency) + expr.SQLSchemas `json:"sqlSchemas,inline"` +} + // GetResponseCode return the right status code for the response by checking the responses. func GetResponseCode(rsp *backend.QueryDataResponse) int { if rsp == nil { diff --git a/pkg/apis/query/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/query/v0alpha1/zz_generated.deepcopy.go index 38a7bd0b115..ad159a4a6cb 100644 --- a/pkg/apis/query/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/query/v0alpha1/zz_generated.deepcopy.go @@ -262,3 +262,29 @@ func (in *QueryTypeDefinitionList) DeepCopyObject() runtime.Object { } return nil } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SQLSchemas) DeepCopyInto(out *SQLSchemas) { + *out = *in + out.TypeMeta = in.TypeMeta + out.SQLSchemas = in.SQLSchemas.DeepCopy() + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SQLSchemas. +func (in *SQLSchemas) DeepCopy() *SQLSchemas { + if in == nil { + return nil + } + out := new(SQLSchemas) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SQLSchemas) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/pkg/apis/query/v0alpha1/zz_generated.openapi.go b/pkg/apis/query/v0alpha1/zz_generated.openapi.go index 2ee9ed95395..68f42312ce9 100644 --- a/pkg/apis/query/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/query/v0alpha1/zz_generated.openapi.go @@ -23,6 +23,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryDataResponse": schema_pkg_apis_query_v0alpha1_QueryDataResponse(ref), "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryTypeDefinition": schema_pkg_apis_query_v0alpha1_QueryTypeDefinition(ref), "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryTypeDefinitionList": schema_pkg_apis_query_v0alpha1_QueryTypeDefinitionList(ref), + "github.com/grafana/grafana/pkg/apis/query/v0alpha1.SQLSchemas": schema_pkg_apis_query_v0alpha1_SQLSchemas(ref), } } @@ -482,3 +483,29 @@ func schema_pkg_apis_query_v0alpha1_QueryTypeDefinitionList(ref common.Reference "github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryTypeDefinition", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } + +func schema_pkg_apis_query_v0alpha1_SQLSchemas(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} diff --git a/pkg/expr/sql/db_test.go b/pkg/expr/sql/db_test.go index ff5ecac022f..c67c23b32b9 100644 --- a/pkg/expr/sql/db_test.go +++ b/pkg/expr/sql/db_test.go @@ -278,18 +278,6 @@ func TestNaNBecomesNull(t *testing.T) { require.NoError(t, err) } -func TestErrorsFromGoMySQLServerAreFlagged(t *testing.T) { - const GmsNotImplemented = "TRUNCATE" // not implemented in go-mysql-server as of 2025-04-11 - - db := DB{} - - query := `SELECT ` + GmsNotImplemented + `(123.456, 2);` - - _, err := db.QueryFrames(context.Background(), &testTracer{}, "sqlExpressionRefId", query, nil) - require.Error(t, err) - require.Contains(t, err.Error(), "error from the sql expression engine") -} - func TestFrameToSQLAndBack_JSONRoundtrip(t *testing.T) { expectedFrame := &data.Frame{ RefID: "json_test", diff --git a/pkg/expr/sql/frame_table.go b/pkg/expr/sql/frame_table.go index 2ac897b5007..245efc6e655 100644 --- a/pkg/expr/sql/frame_table.go +++ b/pkg/expr/sql/frame_table.go @@ -30,7 +30,7 @@ func (ft *FrameTable) String() string { return ft.Name() } -func schemaFromFrame(frame *data.Frame) mysql.Schema { +func SchemaFromFrame(frame *data.Frame) mysql.Schema { schema := make(mysql.Schema, len(frame.Fields)) for i, field := range frame.Fields { @@ -48,7 +48,7 @@ func schemaFromFrame(frame *data.Frame) mysql.Schema { // Schema implements the mysql.Table interface func (ft *FrameTable) Schema() mysql.Schema { if ft.schema == nil { - ft.schema = schemaFromFrame(ft.Frame) + ft.schema = SchemaFromFrame(ft.Frame) } return ft.schema } diff --git a/pkg/expr/sql_schema.go b/pkg/expr/sql_schema.go new file mode 100644 index 00000000000..c3851eed8bd --- /dev/null +++ b/pkg/expr/sql_schema.go @@ -0,0 +1,231 @@ +package expr + +import ( + "context" + "reflect" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr/mathexp" + "github.com/grafana/grafana/pkg/expr/sql" +) + +// BasicColumn represents the column type for data that is input to a SQL expression. +type BasicColumn struct { + Name string `json:"name"` + MySQLType string `json:"mysqlType"` + Nullable bool `json:"nullable"` + DataFrameFieldType data.FieldType `json:"dataFrameFieldType"` +} + +// SchemaInfo provides information and some sample data for data that could be an input +// to a SQL expression. +type SchemaInfo struct { + Columns []BasicColumn `json:"columns"` + SampleRows [][]any `json:"sampleRows"` + Error string `json:"error,omitempty"` +} + +// SQLSchemas returns info about what the Schema for a DS query will be like if the +// query were to be used an input to SQL expressions. So effectively post SQL expressions input +// conversion. +// There is a a manual DeepCopy at the end of this file that will need to be updated when this our the +// underlying structs are change. The hack script will also need to be run to update the Query service API +// generated types. +type SQLSchemas map[string]SchemaInfo + +// GetSQLSchemas returns what the schemas are for SQL expressions for all DS queries +// in the request. It executes the queries to get the schemas. +// Intended use is for autocomplete and AI, so used during the authoring/editing experience only. +func (s *Service) GetSQLSchemas(ctx context.Context, req Request) (SQLSchemas, error) { + // Extract DS Nodes and Execute Them + // Building the pipeline is maybe not best, as it can have more errors. + filtered := make([]Query, 0, len(req.Queries)) + for _, q := range req.Queries { + if NodeTypeFromDatasourceUID(q.DataSource.UID) == TypeDatasourceNode { + filtered = append(filtered, q) + } + } + req.Queries = filtered + pipeline, err := s.buildPipeline(ctx, &req) + if err != nil { + return nil, err + } + + var schemas = make(SQLSchemas) + + for _, node := range pipeline { + // For now, execute calls convert at the end, so we are being lazy and running the full conversion. Longer run we want to run without + // full conversion and just get the schema. Maybe conversion should be + dsNode := node.(*DSNode) + // Make all input to SQL + dsNode.isInputToSQLExpr = true + + // TODO: check where time is coming from, don't recall + res, err := dsNode.Execute(ctx, time.Now(), mathexp.Vars{}, s) + if err != nil { + schemas[dsNode.RefID()] = SchemaInfo{Error: err.Error()} + continue + // we want to continue and get the schemas we can + } + if res.Error != nil { + schemas[dsNode.RefID()] = SchemaInfo{Error: res.Error.Error()} + continue + // we want to continue and get the schemas we can + } + + frames := res.Values.AsDataFrames(dsNode.RefID()) + if len(frames) == 0 { + schemas[dsNode.RefID()] = SchemaInfo{Error: "no data"} + } + frame := frames[0] + + schema := sql.SchemaFromFrame(frame) + columns := make([]BasicColumn, 0, len(schema)) + for _, col := range schema { + fT, _ := sql.MySQLColToFieldType(col) + columns = append(columns, BasicColumn{ + Name: col.Name, + MySQLType: col.Type.String(), + Nullable: col.Nullable, + DataFrameFieldType: fT, + }) + } + + // Cap at 3 rows. + const maxRows = 3 + n := frame.Rows() + if n > maxRows { + n = maxRows + } + sampleRows := make([][]any, 0, n) + for i := 0; i < n; i++ { + sampleRows = append(sampleRows, frame.RowCopy(i)) + } + + schemas[dsNode.RefID()] = SchemaInfo{Columns: columns, SampleRows: sampleRows} + } + + return schemas, nil +} + +// DeepCopy returns a deep copy of the schema. +// Used AI to make it, the kubernetes one doesn't like any or interface{} +func (s SQLSchemas) DeepCopy() SQLSchemas { + if s == nil { + return nil + } + out := make(SQLSchemas, len(s)) + for k, v := range s { + out[k] = SchemaInfo{ + Columns: copyColumns(v.Columns), + SampleRows: deepCopySampleRows2D(v.SampleRows), + Error: v.Error, + } + } + return out +} + +func copyColumns(in []BasicColumn) []BasicColumn { + if in == nil { + return nil + } + out := make([]BasicColumn, len(in)) + copy(out, in) // BasicColumn is value-only, so this suffices + return out +} + +// Deep-copy [][]any preserving nil vs empty slices and cloning elements. +func deepCopySampleRows2D(in [][]any) [][]any { + if in == nil { + return nil + } + out := make([][]any, len(in)) + for i, row := range in { + if row == nil { + // preserve nil inner slice + continue + } + newRow := make([]any, len(row)) + for j, v := range row { + newRow[j] = deepCopyAny(v) + } + out[i] = newRow + } + return out +} + +// Recursively clone pointers, maps, slices, arrays, and interfaces. +// Structs are copied by value (shallow for their internals). +func deepCopyAny(v any) any { + if v == nil { + return nil + } + return deepCopyRV(reflect.ValueOf(v)).Interface() +} + +func deepCopyRV(rv reflect.Value) reflect.Value { + if !rv.IsValid() { + return rv + } + + switch rv.Kind() { + case reflect.Ptr: + if rv.IsNil() { + return rv + } + elemCopy := deepCopyRV(rv.Elem()) + newPtr := reflect.New(rv.Type().Elem()) + if elemCopy.Type().AssignableTo(newPtr.Elem().Type()) { + newPtr.Elem().Set(elemCopy) + } else if elemCopy.Type().ConvertibleTo(newPtr.Elem().Type()) { + newPtr.Elem().Set(elemCopy.Convert(newPtr.Elem().Type())) + } else { + newPtr.Elem().Set(rv.Elem()) // fallback: shallow + } + return newPtr + + case reflect.Interface: + if rv.IsNil() { + return rv + } + return deepCopyRV(rv.Elem()) + + case reflect.Map: + if rv.IsNil() { + return reflect.Zero(rv.Type()) + } + newMap := reflect.MakeMapWithSize(rv.Type(), rv.Len()) + for _, k := range rv.MapKeys() { + newMap.SetMapIndex(deepCopyRV(k), deepCopyRV(rv.MapIndex(k))) + } + return newMap + + case reflect.Slice: + if rv.IsNil() { + return reflect.Zero(rv.Type()) + } + n := rv.Len() + newSlice := reflect.MakeSlice(rv.Type(), n, n) + for i := 0; i < n; i++ { + newSlice.Index(i).Set(deepCopyRV(rv.Index(i))) + } + return newSlice + + case reflect.Array: + n := rv.Len() + newArr := reflect.New(rv.Type()).Elem() + for i := 0; i < n; i++ { + newArr.Index(i).Set(deepCopyRV(rv.Index(i))) + } + return newArr + + case reflect.Struct: + // Value copy (OK unless the struct contains references you also want deep-copied). + return rv + + default: + // Scalars (string, bool, numbers), etc. + return rv + } +} diff --git a/pkg/kinds/dashboard/dashboard_spec_gen.go b/pkg/kinds/dashboard/dashboard_spec_gen.go index 2ebe81760c9..43877fbca7b 100644 --- a/pkg/kinds/dashboard/dashboard_spec_gen.go +++ b/pkg/kinds/dashboard/dashboard_spec_gen.go @@ -191,6 +191,9 @@ type Panel struct { TimeShift *string `json:"timeShift,omitempty"` // Controls if the timeFrom or timeShift overrides are shown in the panel header HideTimeOverride *bool `json:"hideTimeOverride,omitempty"` + // Compare the current time range with a previous period + // For example "1d" to compare current period but shifted back 1 day + TimeCompare *string `json:"timeCompare,omitempty"` // Dynamically load the panel LibraryPanel *LibraryPanelRef `json:"libraryPanel,omitempty"` // Sets panel queries cache timeout. diff --git a/pkg/operators/provisioning/repo_operator.go b/pkg/operators/provisioning/repo_operator.go index f96653a1d87..1651a126ffe 100644 --- a/pkg/operators/provisioning/repo_operator.go +++ b/pkg/operators/provisioning/repo_operator.go @@ -90,6 +90,7 @@ func RunRepoController(deps server.OperatorDependencies) error { statusPatcher, deps.Registerer, tracer, + controllerCfg.parallelOperations, ) if err != nil { return fmt.Errorf("failed to create repository controller: %w", err) @@ -107,6 +108,7 @@ func RunRepoController(deps server.OperatorDependencies) error { type repoControllerConfig struct { provisioningControllerConfig workerCount int + parallelOperations int allowedTargets []string allowImageRendering bool minSyncInterval time.Duration @@ -128,6 +130,7 @@ func getRepoControllerConfig(cfg *setting.Cfg, registry prometheus.Registerer) ( provisioningControllerConfig: *controllerCfg, allowedTargets: allowedTargets, workerCount: cfg.SectionWithEnvOverrides("operator").Key("worker_count").MustInt(1), + parallelOperations: cfg.SectionWithEnvOverrides("operator").Key("parallel_operations").MustInt(10), allowImageRendering: cfg.SectionWithEnvOverrides("provisioning").Key("allow_image_rendering").MustBool(false), minSyncInterval: cfg.SectionWithEnvOverrides("provisioning").Key("min_sync_interval").MustDuration(1 * time.Minute), }, nil diff --git a/pkg/plugins/manager/loader/angular/angularinspector/angularinspector.go b/pkg/plugins/manager/loader/angular/angularinspector/angularinspector.go index e67b04b69f2..98840189389 100644 --- a/pkg/plugins/manager/loader/angular/angularinspector/angularinspector.go +++ b/pkg/plugins/manager/loader/angular/angularinspector/angularinspector.go @@ -35,6 +35,11 @@ func NewPatternListInspector(detectorsProvider angulardetector.DetectorsProvider } func (i *PatternsListInspector) Inspect(ctx context.Context, p *plugins.Plugin) (isAngular bool, err error) { + // CDN plugins are ignored because they should not be using Angular + if p.Class == plugins.ClassCDN { + return false, nil + } + f, err := p.FS.Open("module.js") if err != nil { if errors.Is(err, plugins.ErrFileNotExist) { diff --git a/pkg/plugins/manager/loader/angular/angularinspector/angularinspector_test.go b/pkg/plugins/manager/loader/angular/angularinspector/angularinspector_test.go index f45ae982d09..47c5af990b9 100644 --- a/pkg/plugins/manager/loader/angular/angularinspector/angularinspector_test.go +++ b/pkg/plugins/manager/loader/angular/angularinspector/angularinspector_test.go @@ -27,17 +27,17 @@ func (d *fakeDetector) String() string { } func TestPatternsListInspector(t *testing.T) { - plugin := &plugins.Plugin{ - FS: plugins.NewInMemoryFS(map[string][]byte{"module.js": nil}), - } - for _, tc := range []struct { name string + plugin *plugins.Plugin fakeDetectors []*fakeDetector exp func(t *testing.T, r bool, err error, fakeDetectors []*fakeDetector) }{ { name: "calls the detectors in sequence until true is returned", + plugin: &plugins.Plugin{ + FS: plugins.NewInMemoryFS(map[string][]byte{"module.js": nil}), + }, fakeDetectors: []*fakeDetector{ {returns: false}, {returns: true}, @@ -53,6 +53,9 @@ func TestPatternsListInspector(t *testing.T) { }, { name: "calls the detectors in sequence and returns false as default", + plugin: &plugins.Plugin{ + FS: plugins.NewInMemoryFS(map[string][]byte{"module.js": nil}), + }, fakeDetectors: []*fakeDetector{ {returns: false}, {returns: false}, @@ -65,13 +68,30 @@ func TestPatternsListInspector(t *testing.T) { }, }, { - name: "empty detectors should return false", + name: "empty detectors should return false", + plugin: &plugins.Plugin{ + FS: plugins.NewInMemoryFS(map[string][]byte{"module.js": nil}), + }, fakeDetectors: nil, exp: func(t *testing.T, r bool, err error, fakeDetectors []*fakeDetector) { require.NoError(t, err) require.False(t, r, "inspector should return false") }, }, + { + name: "CDN plugins return false without calling detectors", + plugin: &plugins.Plugin{ + Class: plugins.ClassCDN, + }, + fakeDetectors: []*fakeDetector{ + {returns: true}, + }, + exp: func(t *testing.T, r bool, err error, fakeDetectors []*fakeDetector) { + require.NoError(t, err) + require.False(t, r, "inspector should return false for CDN plugins") + require.Equal(t, 0, fakeDetectors[0].calls, "detectors should not be called for CDN plugins") + }, + }, } { t.Run(tc.name, func(t *testing.T) { detectors := make([]angulardetector.AngularDetector, 0, len(tc.fakeDetectors)) @@ -79,7 +99,7 @@ func TestPatternsListInspector(t *testing.T) { detectors = append(detectors, angulardetector.AngularDetector(d)) } inspector := NewPatternListInspector(&angulardetector.StaticDetectorsProvider{Detectors: detectors}) - r, err := inspector.Inspect(context.Background(), plugin) + r, err := inspector.Inspect(context.Background(), tc.plugin) tc.exp(t, r, err, tc.fakeDetectors) }) } diff --git a/pkg/registry/apis/dashboard/libary_panel.go b/pkg/registry/apis/dashboard/libary_panel.go index c22594ff590..aa2b2705bb1 100644 --- a/pkg/registry/apis/dashboard/libary_panel.go +++ b/pkg/registry/apis/dashboard/libary_panel.go @@ -5,16 +5,17 @@ import ( "fmt" "strconv" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy" - "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - apierrors "k8s.io/apimachinery/pkg/api/errors" + "github.com/grafana/grafana/pkg/services/libraryelements" ) var ( @@ -29,9 +30,9 @@ var ( ) type LibraryPanelStore struct { - Access legacy.DashboardAccess - ResourceInfo utils.ResourceInfo - AccessControl accesscontrol.AccessControl + Access legacy.DashboardAccess + ResourceInfo utils.ResourceInfo + service libraryelements.Service } func (s *LibraryPanelStore) New() runtime.Object { @@ -57,15 +58,70 @@ func (s *LibraryPanelStore) ConvertToTable(ctx context.Context, object runtime.O } func (s *LibraryPanelStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) { - return nil, fmt.Errorf("method not yet implemented") + user, err := identity.GetRequester(ctx) + if err != nil { + return nil, err + } + cmd, err := libraryelements.ToCreateLibraryElementCommand(obj) + if err != nil { + return nil, err + } + + // NOTE: this includes all access control checks + out, err := s.service.CreateElement(ctx, user, *cmd) + if err != nil { + return nil, err + } + if out.UID == "" { + return nil, fmt.Errorf("created library panel has empty UID") + } + return s.Get(ctx, out.UID, &metav1.GetOptions{}) } func (s *LibraryPanelStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - return nil, false, fmt.Errorf("method not yet implemented") + user, err := identity.GetRequester(ctx) + if err != nil { + return nil, false, err + } + old, err := s.Get(ctx, name, &metav1.GetOptions{}) + if err != nil { + return nil, false, err + } + + // NOTE: this includes all access control checks + obj, err := objInfo.UpdatedObject(ctx, old) + if err != nil { + return nil, false, err + } + + cmd, err := libraryelements.ToPatchLibraryElementCommand(obj) + if err != nil { + return nil, false, err + } + + out, err := s.service.PatchLibraryElement(ctx, user, *cmd, name) + if err != nil { + return nil, false, err + } + if out.UID == "" { + return nil, false, fmt.Errorf("created library panel has empty UID") + } + obj, err = s.Get(ctx, out.UID, &metav1.GetOptions{}) + return obj, false, err } func (s *LibraryPanelStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - return nil, false, fmt.Errorf("method not yet implemented") + user, err := identity.GetRequester(ctx) + if err != nil { + return nil, false, err + } + + // NOTE: this includes all access control checks + _, err = s.service.DeleteLibraryElement(ctx, user, name) + if err != nil { + return nil, false, err + } + return nil, true, nil } func (s *LibraryPanelStore) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { diff --git a/pkg/registry/apis/dashboard/mutate.go b/pkg/registry/apis/dashboard/mutate.go index 3703e14c796..c193c17cf56 100644 --- a/pkg/registry/apis/dashboard/mutate.go +++ b/pkg/registry/apis/dashboard/mutate.go @@ -26,6 +26,19 @@ func (b *DashboardsAPIBuilder) Mutate(ctx context.Context, a admission.Attribute if op != admission.Create && op != admission.Update { return nil } + + switch a.GetResource().Resource { + case dashboardV0.DASHBOARD_RESOURCE: + return b.mutateDashboard(ctx, a) + + case dashboardV0.LIBRARY_PANEL_RESOURCE: + return nil // nothing needed + } + + return fmt.Errorf("unexpected resource: %+v", a.GetResource()) +} + +func (b *DashboardsAPIBuilder) mutateDashboard(ctx context.Context, a admission.Attributes) (err error) { var internalID int64 obj := a.GetObject() meta, err := utils.MetaAccessor(obj) diff --git a/pkg/registry/apis/dashboard/mutation_test.go b/pkg/registry/apis/dashboard/mutation_test.go index 4c940160716..ad34c3b3218 100644 --- a/pkg/registry/apis/dashboard/mutation_test.go +++ b/pkg/registry/apis/dashboard/mutation_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" @@ -180,10 +179,10 @@ func TestDashboardAPIBuilder_Mutate(t *testing.T) { err := b.Mutate(context.Background(), admission.NewAttributesRecord( tt.inputObj, nil, - schema.GroupVersionKind{}, + dashv1.DashboardResourceInfo.GroupVersionKind(), "", "test", - schema.GroupVersionResource{}, + dashv1.DashboardResourceInfo.GroupVersionResource(), "", tt.operation, operationOptions, diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index dadc2c079b4..b21877720cd 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -50,6 +50,7 @@ import ( dashsvc "github.com/grafana/grafana/pkg/services/dashboards/service" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/librarypanels" "github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/services/quota" @@ -68,6 +69,8 @@ var ( _ builder.APIGroupVersionsProvider = (*DashboardsAPIBuilder)(nil) _ builder.OpenAPIPostProcessor = (*DashboardsAPIBuilder)(nil) _ builder.APIGroupRouteProvider = (*DashboardsAPIBuilder)(nil) + _ builder.APIGroupMutation = (*DashboardsAPIBuilder)(nil) + _ builder.APIGroupValidation = (*DashboardsAPIBuilder)(nil) ) const ( @@ -108,6 +111,7 @@ type DashboardsAPIBuilder struct { minRefreshInterval string dualWriter dualwrite.Service folderClientProvider client.K8sHandlerProvider + libraryPanels libraryelements.Service // for legacy library panels isStandalone bool // skips any handling including anything to do with legacy storage } @@ -135,6 +139,7 @@ func RegisterAPIService( libraryPanelSvc librarypanels.Service, restConfigProvider apiserver.RestConfigProvider, userService user.Service, + libraryPanels libraryelements.Service, ) *DashboardsAPIBuilder { dbp := legacysql.NewDatabaseProvider(sql) namespacer := request.GetNamespaceMapper(cfg) @@ -157,6 +162,7 @@ func RegisterAPIService( minRefreshInterval: cfg.MinRefreshInterval, dualWriter: dual, folderClientProvider: newSimpleFolderClientProvider(folderClient), + libraryPanels: libraryPanels, legacy: &DashboardStorage{ Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, libraryPanelSvc, sorter, dashboardPermissionsSvc, accessControl, features), @@ -229,26 +235,35 @@ func (b *DashboardsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { } func (b *DashboardsAPIBuilder) AllowedV0Alpha1Resources() []string { - return []string{dashv0.DashboardKind().Plural()} + return []string{ + dashv0.DashboardKind().Plural(), + dashv0.LIBRARY_PANEL_RESOURCE, + } } // Validate validates dashboard operations for the apiserver func (b *DashboardsAPIBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) { op := a.GetOperation() - // Handle different operations - switch op { - case admission.Delete: - return b.validateDelete(ctx, a) - case admission.Create: - return b.validateCreate(ctx, a, o) - case admission.Update: - return b.validateUpdate(ctx, a, o) - case admission.Connect: - return nil + switch a.GetResource().Resource { + case dashv0.DASHBOARD_RESOURCE: + // Handle different operations + switch op { + case admission.Delete: + return b.validateDelete(ctx, a) + case admission.Create: + return b.validateCreate(ctx, a, o) + case admission.Update: + return b.validateUpdate(ctx, a, o) + case admission.Connect: + return nil + } + + case dashv0.LIBRARY_PANEL_RESOURCE: + return nil // OK for now } - return nil + return fmt.Errorf("unsupported validation: %+v", a.GetResource()) } // validateDelete checks if a dashboard can be deleted @@ -642,12 +657,13 @@ func (b *DashboardsAPIBuilder) storageForVersion( return err } - // Expose read only library panels - if libraryPanels != nil { + // Expose read library panels + //nolint:staticcheck // not yet migrated to OpenFeature + if libraryPanels != nil && b.features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { legacyLibraryStore := &LibraryPanelStore{ - Access: b.legacy.Access, - ResourceInfo: *libraryPanels, - AccessControl: b.accessControl, + Access: b.legacy.Access, + ResourceInfo: *libraryPanels, + service: b.libraryPanels, } unifiedLibraryStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, *libraryPanels, opts.OptsGetter) diff --git a/pkg/registry/apis/iam/legacy/team.go b/pkg/registry/apis/iam/legacy/team.go index 51e68506fbd..874dea3dff9 100644 --- a/pkg/registry/apis/iam/legacy/team.go +++ b/pkg/registry/apis/iam/legacy/team.go @@ -2,6 +2,7 @@ package legacy import ( "context" + "database/sql" "errors" "fmt" "time" @@ -129,18 +130,18 @@ func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo, return nil, fmt.Errorf("expected non zero orgID") } - sql, err := s.sql(ctx) + sqlConn, err := s.sql(ctx) if err != nil { return nil, err } - req := newListTeams(sql, &query) + req := newListTeams(sqlConn, &query) q, err := sqltemplate.Execute(sqlQueryTeamsTemplate, req) if err != nil { return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamsTemplate.Name(), err) } - rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) + rows, err := sqlConn.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) defer func() { if rows != nil { _ = rows.Close() @@ -155,11 +156,21 @@ func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo, var lastID int64 for rows.Next() { t := team.Team{} - err = rows.Scan(&t.ID, &t.UID, &t.Name, &t.Email, &t.ExternalUID, &t.IsProvisioned, &t.Created, &t.Updated) + var externalUID sql.NullString + var isProvisioned sql.NullBool + err = rows.Scan(&t.ID, &t.UID, &t.Name, &t.Email, &externalUID, &isProvisioned, &t.Created, &t.Updated) if err != nil { return res, err } + if externalUID.Valid { + t.ExternalUID = externalUID.String + } + + if isProvisioned.Valid { + t.IsProvisioned = isProvisioned.Bool + } + lastID = t.ID res.Teams = append(res.Teams, t) if len(res.Teams) > int(query.Pagination.Limit)-1 { @@ -170,7 +181,7 @@ func (s *legacySQLStore) ListTeams(ctx context.Context, ns claims.NamespaceInfo, } if query.UID == "" { - res.RV, err = sql.GetResourceVersion(ctx, "team", "updated") + res.RV, err = sqlConn.GetResourceVersion(ctx, "team", "updated") } return res, err diff --git a/pkg/registry/apis/provisioning/controller/finalizers.go b/pkg/registry/apis/provisioning/controller/finalizers.go index d9c41ee4c15..5bd468fe660 100644 --- a/pkg/registry/apis/provisioning/controller/finalizers.go +++ b/pkg/registry/apis/provisioning/controller/finalizers.go @@ -7,8 +7,11 @@ import ( "slices" "sort" "strings" + "sync/atomic" "time" + "github.com/grafana/dskit/concurrency" + "k8s.io/apimachinery/pkg/api/errors" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" @@ -27,6 +30,7 @@ type finalizer struct { lister resources.ResourceLister clientFactory resources.ClientFactory metrics *finalizerMetrics + maxWorkers int } func (f *finalizer) process(ctx context.Context, @@ -113,27 +117,73 @@ func (f *finalizer) processExistingItems( // Safe deletion order sortResourceListForDeletion(items) - count := 0 + var dashboards, folderItems []*provisioning.ResourceListItem for _, item := range items.Items { - res, _, err := clients.ForResource(ctx, schema.GroupVersionResource{ + if item.Group == folders.GroupVersion.Group { + folderItems = append(folderItems, &item) + } else { + dashboards = append(dashboards, &item) + } + } + + processItem := func(jobCtx context.Context, item *provisioning.ResourceListItem) error { + res, _, err := clients.ForResource(jobCtx, schema.GroupVersionResource{ Group: item.Group, Resource: item.Resource, }) if err != nil { logger.Error("error getting client for resource", "resource", item.Resource, "error", err) - return count, err + return err } - err = cb(res, &item) + err = cb(res, item) if err != nil { + if errors.IsNotFound(err) { + logger.Info("resource not found, skipping", "name", item.Name, "group", item.Group, "resource", item.Resource) + return nil + } logger.Error("error processing item", "name", item.Name, "error", err) - return count, fmt.Errorf("processing item: %w", err) - } else { + return fmt.Errorf("processing item: %w", err) + } + return nil + } + + processGroup := func(group []*provisioning.ResourceListItem) (int, error) { + var processed int64 + err := concurrency.ForEachJob(ctx, len(group), f.maxWorkers, func(ctx context.Context, idx int) error { + jobCtx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + item := group[idx] + if err := processItem(jobCtx, item); err != nil { + return err + } + atomic.AddInt64(&processed, 1) + return nil + }) + return int(processed), err + } + + count := 0 + + if len(dashboards) > 0 { + processed, err := processGroup(dashboards) + if err != nil { + return processed, err + } + count += processed + } + + if len(folderItems) > 0 { + for _, item := range folderItems { + if err := processItem(ctx, item); err != nil { + return count, err + } count++ } } - logger.Info("processed orphan items", "items", count) + + logger.Info("processed items", "items", count) return count, nil } diff --git a/pkg/registry/apis/provisioning/controller/finalizers_test.go b/pkg/registry/apis/provisioning/controller/finalizers_test.go index 140363a0507..4df89107bbe 100644 --- a/pkg/registry/apis/provisioning/controller/finalizers_test.go +++ b/pkg/registry/apis/provisioning/controller/finalizers_test.go @@ -2,10 +2,15 @@ package controller import ( "context" + "fmt" + "sync" + "sync/atomic" "testing" + "time" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" + mock "github.com/stretchr/testify/mock" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -14,6 +19,8 @@ import ( "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/dynamic" + "github.com/grafana/grafana-app-sdk/logging" + folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/apps/provisioning/pkg/repository" "github.com/grafana/grafana/pkg/registry/apis/provisioning/resources" @@ -140,7 +147,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -164,12 +171,12 @@ func TestFinalizer_process(t *testing.T) { } clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -196,7 +203,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -220,12 +227,12 @@ func TestFinalizer_process(t *testing.T) { } clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -253,7 +260,7 @@ func TestFinalizer_process(t *testing.T) { clientFactory := resources.NewMockClientFactory(t) clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(nil, assert.AnError) @@ -275,7 +282,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(nil, assert.AnError) @@ -286,7 +293,7 @@ func TestFinalizer_process(t *testing.T) { clients := resources.NewMockResourceClients(t) clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) @@ -308,7 +315,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -327,12 +334,12 @@ func TestFinalizer_process(t *testing.T) { clients := resources.NewMockResourceClients(t) clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -357,7 +364,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -381,12 +388,12 @@ func TestFinalizer_process(t *testing.T) { } clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -414,7 +421,7 @@ func TestFinalizer_process(t *testing.T) { resourceLister := resources.NewMockResourceLister(t) resourceLister. - On("List", context.Background(), "default", "my-repo"). + On("List", mock.Anything, "default", "my-repo"). Once(). Return(&provisioning.ResourceList{ Items: []provisioning.ResourceListItem{ @@ -438,12 +445,12 @@ func TestFinalizer_process(t *testing.T) { } clientFactory. - On("Clients", context.Background(), "default"). + On("Clients", mock.Anything, "default"). Once(). Return(clients, nil) clients. - On("ForResource", context.Background(), schema.GroupVersionResource{ + On("ForResource", mock.Anything, schema.GroupVersionResource{ Group: "dashboard.grafana.app", Resource: "dashboards", }). @@ -560,3 +567,142 @@ func TestSortResourceListForDeletion(t *testing.T) { }) } } + +func TestFinalizer_processExistingItems_Concurrency(t *testing.T) { + testCases := []struct { + name string + dashboardCount int + folderCount int + maxWorkers int + expectedConcurrency bool + }{ + { + name: "Multiple dashboards processed concurrently", + dashboardCount: 10, + folderCount: 0, + maxWorkers: 5, + expectedConcurrency: true, + }, + { + name: "Single worker processes dashboards sequentially", + dashboardCount: 5, + folderCount: 0, + maxWorkers: 1, + expectedConcurrency: false, + }, + { + name: "Folders processed sequentially regardless of maxWorkers", + dashboardCount: 0, + folderCount: 5, + maxWorkers: 10, + expectedConcurrency: false, + }, + { + name: "Mixed dashboards and folders - dashboards concurrent, folders sequential", + dashboardCount: 10, + folderCount: 3, + maxWorkers: 5, + expectedConcurrency: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Will be used to track concurrent executions + var ( + concurrentCount int64 + maxConcurrent int64 + mu sync.Mutex + ) + + items := provisioning.ResourceList{Items: []provisioning.ResourceListItem{}} + + for i := 0; i < tc.dashboardCount; i++ { + items.Items = append(items.Items, provisioning.ResourceListItem{ + Group: "dashboard.grafana.app", + Resource: "dashboards", + Name: fmt.Sprintf("dashboard-%d", i), + }) + } + + for i := 0; i < tc.folderCount; i++ { + items.Items = append(items.Items, provisioning.ResourceListItem{ + Group: folders.GroupVersion.Group, + Resource: "folders", + Name: fmt.Sprintf("folder-%d", i), + }) + } + + resourceLister := resources.NewMockResourceLister(t) + resourceLister. + On("List", mock.Anything, "default", "my-repo"). + Return(&items, nil) + + clientFactory := resources.NewMockClientFactory(t) + clients := resources.NewMockResourceClients(t) + + client := &mockDynamicClient{ + deleteFunc: func(ctx context.Context, name string, options metav1.DeleteOptions, subresources ...string) error { + // Track concurrent executions + current := atomic.AddInt64(&concurrentCount, 1) + defer atomic.AddInt64(&concurrentCount, -1) + + mu.Lock() + if current > maxConcurrent { + maxConcurrent = current + } + mu.Unlock() + + // Simulate slow client to allow concurrency to build up + time.Sleep(1 * time.Second) + + return nil + }, + } + + clientFactory. + On("Clients", mock.Anything, "default"). + Return(clients, nil) + + clients. + On("ForResource", mock.Anything, mock.Anything). + Return(client, schema.GroupVersionKind{}, nil) + + metrics := registerFinalizerMetrics(prometheus.NewRegistry()) + f := &finalizer{ + lister: resourceLister, + clientFactory: clientFactory, + metrics: &metrics, + maxWorkers: tc.maxWorkers, + } + + repo := &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-repo", + Namespace: "default", + }, + } + + count, err := f.processExistingItems( + context.Background(), + repo, + f.removeResources(context.Background(), logging.DefaultLogger), + ) + + assert.NoError(t, err) + assert.Equal(t, tc.dashboardCount+tc.folderCount, count) + + if tc.expectedConcurrency { + // When concurrent, max concurrent should be > 1 + assert.Greater(t, maxConcurrent, int64(1), + "Expected concurrent execution but maxConcurrent was %d", maxConcurrent) + // Should not exceed maxWorkers + assert.LessOrEqual(t, maxConcurrent, int64(tc.maxWorkers)) + } else { + // When sequential, max concurrent should be 1 + assert.Equal(t, int64(1), maxConcurrent, + "Expected sequential execution but maxConcurrent was %d", maxConcurrent) + } + }) + } +} diff --git a/pkg/registry/apis/provisioning/controller/repository.go b/pkg/registry/apis/provisioning/controller/repository.go index 70e5c3b4a87..681619711e1 100644 --- a/pkg/registry/apis/provisioning/controller/repository.go +++ b/pkg/registry/apis/provisioning/controller/repository.go @@ -84,6 +84,7 @@ func NewRepositoryController( statusPatcher StatusPatcher, registry prometheus.Registerer, tracer tracing.Tracer, + parallelOperations int, ) (*RepositoryController, error) { finalizerMetrics := registerFinalizerMetrics(registry) @@ -104,6 +105,7 @@ func NewRepositoryController( lister: resourceLister, clientFactory: clients, metrics: &finalizerMetrics, + maxWorkers: parallelOperations, }, jobs: jobs, logger: logging.DefaultLogger.With("logger", loggerName), diff --git a/pkg/registry/apis/provisioning/register.go b/pkg/registry/apis/provisioning/register.go index 8797d940408..485f6bfe5d5 100644 --- a/pkg/registry/apis/provisioning/register.go +++ b/pkg/registry/apis/provisioning/register.go @@ -798,6 +798,7 @@ func (b *APIBuilder) GetPostStartHooks() (map[string]genericapiserver.PostStartH b.statusPatcher, b.registry, b.tracer, + 10, ) if err != nil { return err diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index f63cb850bc7..30a1ae6b567 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -241,25 +241,40 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O }), nil } -func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuilder, httpreq *http.Request, responder responderWrapper, connectLogger log.Logger) (*backend.QueryDataResponse, error) { - var jsonQueries = make([]*simplejson.Json, 0, len(raw.Queries)) - for _, query := range raw.Queries { - dsRef, err := getValidDataSourceRef(ctx, query.Datasource, query.DatasourceID, b.legacyDatasourceLookup) - if err != nil { - connectLogger.Error("error getting valid datasource ref", err) - } - if dsRef != nil { - query.Datasource = dsRef +type preparedQuery struct { + mReq dtos.MetricRequest + cache datasources.CacheService + headers map[string]string + logger log.Logger + builder dsquerierclient.QSDatasourceClientBuilder + exprSvc *expr.Service + reportMetrics func() +} + +func prepareQuery( + ctx context.Context, + raw query.QueryDataRequest, + b QueryAPIBuilder, + httpreq *http.Request, + connectLogger log.Logger, +) (*preparedQuery, error) { + // Normalize DS refs and build []*simplejson.Json + jsonQueries := make([]*simplejson.Json, 0, len(raw.Queries)) + for _, q := range raw.Queries { + if dsRef, derr := getValidDataSourceRef(ctx, q.Datasource, q.DatasourceID, b.legacyDatasourceLookup); derr != nil { + connectLogger.Error("error getting valid datasource ref", "err", derr) + } else if dsRef != nil { + q.Datasource = dsRef } - jsonBytes, err := json.Marshal(query) + jsonBytes, err := json.Marshal(q) if err != nil { - connectLogger.Error("error marshalling", err) + connectLogger.Error("error marshalling query", "err", err) } - sjQuery, _ := simplejson.NewJson(jsonBytes) + sjQuery, err := simplejson.NewJson(jsonBytes) if err != nil { - connectLogger.Error("error unmarshalling", err) + connectLogger.Error("error creating simplejson for query", "err", err) } jsonQueries = append(jsonQueries, sjQuery) @@ -274,13 +289,11 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil cache := &MyCacheService{ legacy: b.legacyDatasourceLookup, } - headers := ExtractKnownHeaders(httpreq.Header) instance, err := b.instanceProvider.GetInstance(ctx, connectLogger, headers) if err != nil { connectLogger.Error("failed to get instance configuration settings", "err", err) - responder.Error(err) return nil, err } @@ -288,12 +301,14 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil dsQuerierLoggerWithSlug := instance.GetLogger() + // Datasource client qsDsClientBuilder qsDsClientBuilder := dsquerierclient.NewQsDatasourceClientBuilderWithInstance( instance, ctx, dsQuerierLoggerWithSlug, ) + // Expressions service exprService := expr.ProvideService( &setting.Cfg{ ExpressionsEnabled: instanceConfig.ExpressionsEnabled, @@ -310,17 +325,37 @@ func handleQuery(ctx context.Context, raw query.QueryDataRequest, b QueryAPIBuil qsDsClientBuilder, ) - qdr, err := service.QueryData(ctx, dsQuerierLoggerWithSlug, cache, exprService, mReq, qsDsClientBuilder, headers) + return &preparedQuery{ + mReq: mReq, + cache: cache, + headers: headers, + logger: dsQuerierLoggerWithSlug, + builder: qsDsClientBuilder, + exprSvc: exprService, + reportMetrics: func() { instance.ReportMetrics() }, + }, nil +} - // tell the `instance` structure that it can now report - // metrics that are only reported once during a request - instance.ReportMetrics() +func handlePreparedQuery(ctx context.Context, pq *preparedQuery) (*backend.QueryDataResponse, error) { + resp, err := service.QueryData(ctx, pq.logger, pq.cache, pq.exprSvc, pq.mReq, pq.builder, pq.headers) + pq.reportMetrics() + return resp, err +} +func handleQuery( + ctx context.Context, + raw query.QueryDataRequest, + b QueryAPIBuilder, + httpreq *http.Request, + responder responderWrapper, + connectLogger log.Logger, +) (*backend.QueryDataResponse, error) { + pq, err := prepareQuery(ctx, raw, b, httpreq, connectLogger) if err != nil { - return qdr, err + responder.Error(err) + return nil, err } - - return qdr, nil + return handlePreparedQuery(ctx, pq) } type responderWrapper struct { diff --git a/pkg/registry/apis/query/register.go b/pkg/registry/apis/query/register.go index 109de13276c..221b589b511 100644 --- a/pkg/registry/apis/query/register.go +++ b/pkg/registry/apis/query/register.go @@ -161,6 +161,7 @@ func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) { &query.QueryDataResponse{}, &query.QueryTypeDefinition{}, &query.QueryTypeDefinitionList{}, + &query.SQLSchemas{}, ) } @@ -201,6 +202,8 @@ func (b *QueryAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIG // The query endpoint -- NOTE, this uses a rewrite hack to allow requests without a name parameter storage["query"] = newQueryREST(b) + storage["sqlschemas"] = newSQLSchemasREST(b) + // Register the expressions query schemas err := queryschema.RegisterQueryTypes(b.queryTypes, storage) diff --git a/pkg/registry/apis/query/sql_schema.go b/pkg/registry/apis/query/sql_schema.go new file mode 100644 index 00000000000..10ec3dc2526 --- /dev/null +++ b/pkg/registry/apis/query/sql_schema.go @@ -0,0 +1,170 @@ +package query + +import ( + "context" + "net/http" + "strconv" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/expr" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + errorsK8s "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/registry/rest" + + query "github.com/grafana/grafana/pkg/apis/query/v0alpha1" + "github.com/grafana/grafana/pkg/infra/log" + service "github.com/grafana/grafana/pkg/services/query" + "github.com/grafana/grafana/pkg/web" +) + +type sqlSchemaREST struct { + logger log.Logger + builder *QueryAPIBuilder +} + +var ( + _ rest.Storage = (*sqlSchemaREST)(nil) + _ rest.SingularNameProvider = (*sqlSchemaREST)(nil) + _ rest.Connecter = (*sqlSchemaREST)(nil) + _ rest.Scoper = (*sqlSchemaREST)(nil) + _ rest.StorageMetadata = (*sqlSchemaREST)(nil) +) + +func newSQLSchemasREST(builder *QueryAPIBuilder) *sqlSchemaREST { + return &sqlSchemaREST{ + logger: log.New("query.sqlschemas"), + builder: builder, + } +} + +func (r *sqlSchemaREST) New() runtime.Object { + // This is added as the "ResponseType" regardless what ProducesObject() says :) + return &query.SQLSchemas{} +} + +func (r *sqlSchemaREST) Destroy() {} + +func (r *sqlSchemaREST) NamespaceScoped() bool { + return true +} + +func (r *sqlSchemaREST) GetSingularName() string { + return "SQLSchema" // Used for the +} + +func (r *sqlSchemaREST) ProducesMIMETypes(verb string) []string { + return []string{"application/json"} // and parquet! +} + +func (r *sqlSchemaREST) ProducesObject(verb string) interface{} { + return &query.SQLSchemas{} +} + +func (r *sqlSchemaREST) ConnectMethods() []string { + return []string{"POST"} +} + +func (r *sqlSchemaREST) NewConnectOptions() (runtime.Object, bool, string) { + return nil, false, "" // true means you can use the trailing path as a variable +} + +// called by mt query service and also when queryServiceFromUI is enabled, can be both mt and st +func (r *sqlSchemaREST) Connect(connectCtx context.Context, name string, _ runtime.Object, incomingResponder rest.Responder) (http.Handler, error) { + // See: /pkg/services/apiserver/builder/helper.go#L34 + // The name is set with a rewriter hack + if name != "name" { + r.logger.Debug("Connect name is not name") + return nil, errorsK8s.NewNotFound(schema.GroupResource{}, name) + } + b := r.builder + + return http.HandlerFunc(func(w http.ResponseWriter, httpreq *http.Request) { + ctx, span := b.tracer.Start(httpreq.Context(), "QueryService.GetSQLSchemas") + defer span.End() + ctx = request.WithNamespace(ctx, request.NamespaceValue(connectCtx)) + traceId := span.SpanContext().TraceID() + connectLogger := b.log.New("traceId", traceId.String(), "rule_uid", httpreq.Header.Get("X-Rule-Uid")) + responder := newResponderWrapper(incomingResponder, + func(statusCode *int, obj runtime.Object) { + if *statusCode/100 == 4 { + span.SetStatus(codes.Error, strconv.Itoa(*statusCode)) + } + + if *statusCode >= 500 { + o, ok := obj.(*query.QueryDataResponse) + if ok && o.Responses != nil { + for refId, response := range o.Responses { + if response.ErrorSource == backend.ErrorSourceDownstream { + *statusCode = http.StatusBadRequest //force this to be a 400 since it's downstream + span.SetStatus(codes.Error, strconv.Itoa(*statusCode)) + span.SetAttributes(attribute.String("error.source", "downstream")) + break + } else if response.Error != nil { + connectLogger.Debug("500 error without downstream error source", "error", response.Error, "errorSource", response.ErrorSource, "refId", refId) + span.SetStatus(codes.Error, "500 error without downstream error source") + } else { + span.SetStatus(codes.Error, "500 error without downstream error source and no Error message") + span.SetAttributes(attribute.String("error.ref_id", refId)) + } + } + } + } + }, + + func(err error) { + connectLogger.Error("error caught in handler", "err", err) + span.SetStatus(codes.Error, "query error") + + if err == nil { + return + } + + span.RecordError(err) + }) + + raw := &query.QueryDataRequest{} + err := web.Bind(httpreq, raw) + if err != nil { + connectLogger.Error("Hit unexpected error when reading query", "err", err) + err = errorsK8s.NewBadRequest("error reading query") + responder.Error(err) + return + } + + qdr, err := handleSQLSchemaQuery(ctx, *raw, *b, httpreq, *responder, connectLogger) + if err != nil { + responder.Error(err) + return + } + + responder.Object(200, &query.SQLSchemas{ + SQLSchemas: qdr, + }) + }), nil +} + +func handlePreparedSQLSchema(ctx context.Context, pq *preparedQuery) (expr.SQLSchemas, error) { + resp, err := service.GetSQLSchemas(ctx, pq.logger, pq.cache, pq.exprSvc, pq.mReq, pq.builder, pq.headers) + pq.reportMetrics() + return resp, err +} + +func handleSQLSchemaQuery( + ctx context.Context, + raw query.QueryDataRequest, + b QueryAPIBuilder, + httpreq *http.Request, + responder responderWrapper, + connectLogger log.Logger, +) (expr.SQLSchemas, error) { + pq, err := prepareQuery(ctx, raw, b, httpreq, connectLogger) + if err != nil { + responder.Error(err) + return nil, err + } + return handlePreparedSQLSchema(ctx, pq) +} diff --git a/pkg/registry/apis/secret/contracts/encryption.go b/pkg/registry/apis/secret/contracts/encryption.go index f24fe73a544..73a4952cac2 100644 --- a/pkg/registry/apis/secret/contracts/encryption.go +++ b/pkg/registry/apis/secret/contracts/encryption.go @@ -1,6 +1,10 @@ package contracts -import "context" +import ( + "context" + + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" +) // EncryptionManager is an envelope encryption service in charge of encrypting/decrypting secrets. type EncryptionManager interface { @@ -8,17 +12,23 @@ type EncryptionManager interface { // For those specific use cases where the encryption operation cannot be moved outside // the database transaction, look at database-specific methods present at the specific // implementation present at manager.EncryptionService. - Encrypt(ctx context.Context, namespace string, payload []byte) ([]byte, error) - Decrypt(ctx context.Context, namespace string, payload []byte) ([]byte, error) + Encrypt(ctx context.Context, namespace xkube.Namespace, payload []byte) (EncryptedPayload, error) + Decrypt(ctx context.Context, namespace xkube.Namespace, payload EncryptedPayload) ([]byte, error) +} + +type EncryptedPayload struct { + DataKeyID string + EncryptedData []byte } type EncryptedValue struct { - Namespace string - Name string - Version int64 - EncryptedData []byte - Created int64 - Updated int64 + EncryptedPayload + + Namespace string + Name string + Version int64 + Created int64 + Updated int64 } // ListOpts defines pagination options for listing encrypted values. @@ -28,10 +38,10 @@ type ListOpts struct { } type EncryptedValueStorage interface { - Create(ctx context.Context, namespace, name string, version int64, encryptedData []byte) (*EncryptedValue, error) - Update(ctx context.Context, namespace, name string, version int64, encryptedData []byte) error - Get(ctx context.Context, namespace, name string, version int64) (*EncryptedValue, error) - Delete(ctx context.Context, namespace, name string, version int64) error + Create(ctx context.Context, namespace xkube.Namespace, name string, version int64, encryptedData EncryptedPayload) (*EncryptedValue, error) + Update(ctx context.Context, namespace xkube.Namespace, name string, version int64, encryptedData EncryptedPayload) error + Get(ctx context.Context, namespace xkube.Namespace, name string, version int64) (*EncryptedValue, error) + Delete(ctx context.Context, namespace xkube.Namespace, name string, version int64) error } type GlobalEncryptedValueStorage interface { @@ -39,6 +49,10 @@ type GlobalEncryptedValueStorage interface { CountAll(ctx context.Context, untilTime *int64) (int64, error) } +type EncryptedValueMigrationExecutor interface { + Execute(ctx context.Context) (int, error) +} + type ConsolidationService interface { Consolidate(ctx context.Context) error } diff --git a/pkg/registry/apis/secret/contracts/keeper.go b/pkg/registry/apis/secret/contracts/keeper.go index 5558a4db3b5..a204bb5b1a7 100644 --- a/pkg/registry/apis/secret/contracts/keeper.go +++ b/pkg/registry/apis/secret/contracts/keeper.go @@ -96,10 +96,10 @@ func (s ExternalID) String() string { // Keeper is the interface for secret keepers. type Keeper interface { - Store(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace, name string, version int64, exposedValueOrRef string) (ExternalID, error) - Update(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace, name string, version int64, exposedValueOrRef string) error - Expose(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace, name string, version int64) (secretv1beta1.ExposedSecureValue, error) - Delete(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace, name string, version int64) error + Store(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64, exposedValueOrRef string) (ExternalID, error) + Update(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64, exposedValueOrRef string) error + Expose(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64) (secretv1beta1.ExposedSecureValue, error) + Delete(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64) error } // Service is the interface for secret keeper services. diff --git a/pkg/registry/apis/secret/encryption/manager/manager.go b/pkg/registry/apis/secret/encryption/manager/manager.go index f48b350c1bd..024c8aca05a 100644 --- a/pkg/registry/apis/secret/encryption/manager/manager.go +++ b/pkg/registry/apis/secret/encryption/manager/manager.go @@ -1,10 +1,8 @@ package manager import ( - "bytes" "context" "crypto/rand" - "encoding/base64" "errors" "fmt" "strconv" @@ -20,13 +18,10 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/encryption" "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/util" ) -const ( - keyIdDelimiter = '#' -) - type EncryptionManager struct { tracer trace.Tracer store contracts.DataKeyStorage @@ -99,12 +94,9 @@ func (s *EncryptionManager) registerUsageMetrics() { }) } -// TODO: Why do we need to use a global variable for this? -var b64 = base64.RawStdEncoding - -func (s *EncryptionManager) Encrypt(ctx context.Context, namespace string, payload []byte) ([]byte, error) { +func (s *EncryptionManager) Encrypt(ctx context.Context, namespace xkube.Namespace, payload []byte) (contracts.EncryptedPayload, error) { ctx, span := s.tracer.Start(ctx, "EnvelopeEncryptionManager.Encrypt", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), )) defer span.End() @@ -128,34 +120,30 @@ func (s *EncryptionManager) Encrypt(ctx context.Context, namespace string, paylo id, dataKey, err = s.currentDataKey(ctx, namespace, label) if err != nil { s.log.Error("Failed to get current data key", "error", err, "label", label) - return nil, err + return contracts.EncryptedPayload{}, err } var encrypted []byte encrypted, err = s.cipher.Encrypt(ctx, payload, string(dataKey)) if err != nil { s.log.Error("Failed to encrypt secret", "error", err) - return nil, err + return contracts.EncryptedPayload{}, err } - prefix := make([]byte, b64.EncodedLen(len(id))+2) - b64.Encode(prefix[1:], []byte(id)) - prefix[0] = keyIdDelimiter - prefix[len(prefix)-1] = keyIdDelimiter + encryptedPayload := contracts.EncryptedPayload{ + DataKeyID: id, + EncryptedData: encrypted, + } - blob := make([]byte, len(prefix)+len(encrypted)) - copy(blob, prefix) - copy(blob[len(prefix):], encrypted) - - return blob, nil + return encryptedPayload, nil } // currentDataKey looks up for current data key in cache or database by name, and decrypts it. // If there's no current data key in cache nor in database it generates a new random data key, // and stores it into both the in-memory cache and database (encrypted by the encryption provider). -func (s *EncryptionManager) currentDataKey(ctx context.Context, namespace string, label string) (string, []byte, error) { +func (s *EncryptionManager) currentDataKey(ctx context.Context, namespace xkube.Namespace, label string) (string, []byte, error) { ctx, span := s.tracer.Start(ctx, "EnvelopeEncryptionManager.CurrentDataKey", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), attribute.String("label", label), )) defer span.End() @@ -166,14 +154,14 @@ func (s *EncryptionManager) currentDataKey(ctx context.Context, namespace string defer s.mtx.Unlock() // We try to fetch the data key, either from cache or database - id, dataKey, err := s.dataKeyByLabel(ctx, namespace, label) + id, dataKey, err := s.dataKeyByLabel(ctx, namespace.String(), label) if err != nil { return "", nil, err } // If no existing data key was found, create a new one if dataKey == nil { - id, dataKey, err = s.newDataKey(ctx, namespace, label) + id, dataKey, err = s.newDataKey(ctx, namespace.String(), label) if err != nil { return "", nil, err } @@ -264,9 +252,9 @@ func newRandomDataKey() ([]byte, error) { return rawDataKey, nil } -func (s *EncryptionManager) Decrypt(ctx context.Context, namespace string, payload []byte) ([]byte, error) { +func (s *EncryptionManager) Decrypt(ctx context.Context, namespace xkube.Namespace, payload contracts.EncryptedPayload) ([]byte, error) { ctx, span := s.tracer.Start(ctx, "EnvelopeEncryptionManager.Decrypt", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), )) defer span.End() @@ -285,50 +273,28 @@ func (s *EncryptionManager) Decrypt(ctx context.Context, namespace string, paylo } }() - if len(payload) == 0 { + if len(payload.EncryptedData) == 0 { err = fmt.Errorf("unable to decrypt empty payload") return nil, err } - payload = payload[1:] - endOfKey := bytes.Index(payload, []byte{keyIdDelimiter}) - if endOfKey == -1 { - err = fmt.Errorf("could not find valid key id in encrypted payload") - return nil, err - } - b64Key := payload[:endOfKey] - payload = payload[endOfKey+1:] - keyId := make([]byte, b64.DecodedLen(len(b64Key))) - _, err = b64.Decode(keyId, b64Key) - if err != nil { + if payload.DataKeyID == "" { + err = fmt.Errorf("unable to decrypt empty data key id") return nil, err } - dataKey, err := s.dataKeyById(ctx, namespace, string(keyId)) + dataKey, err := s.dataKeyById(ctx, namespace.String(), payload.DataKeyID) if err != nil { - s.log.FromContext(ctx).Error("Failed to lookup data key by id", "id", string(keyId), "error", err) + s.log.FromContext(ctx).Error("Failed to lookup data key by id", "id", payload.DataKeyID, "error", err) return nil, err } var decrypted []byte - decrypted, err = s.cipher.Decrypt(ctx, payload, string(dataKey)) + decrypted, err = s.cipher.Decrypt(ctx, payload.EncryptedData, string(dataKey)) return decrypted, err } -func (s *EncryptionManager) GetDecryptedValue(ctx context.Context, namespace string, sjd map[string][]byte, key, fallback string) string { - if value, ok := sjd[key]; ok { - decryptedData, err := s.Decrypt(ctx, namespace, value) - if err != nil { - return fallback - } - - return string(decryptedData) - } - - return fallback -} - // dataKeyById looks up for data key in the database and returns it decrypted. func (s *EncryptionManager) dataKeyById(ctx context.Context, namespace, id string) ([]byte, error) { ctx, span := s.tracer.Start(ctx, "EnvelopeEncryptionManager.GetDataKey", trace.WithAttributes( diff --git a/pkg/registry/apis/secret/encryption/manager/manager_test.go b/pkg/registry/apis/secret/encryption/manager/manager_test.go index a6c1c06bcfa..dc63fb461c9 100644 --- a/pkg/registry/apis/secret/encryption/manager/manager_test.go +++ b/pkg/registry/apis/secret/encryption/manager/manager_test.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/secret/encryption" "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service" osskmsproviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/secret/database" @@ -34,7 +35,7 @@ func TestMain(m *testing.M) { func TestEncryptionService_EnvelopeEncryption(t *testing.T) { svc := setupTestService(t) ctx := context.Background() - namespace := "test-namespace" + namespace := xkube.Namespace("test-namespace") t.Run("encrypting should create DEK", func(t *testing.T) { plaintext := []byte("very secret string") @@ -46,7 +47,7 @@ func TestEncryptionService_EnvelopeEncryption(t *testing.T) { require.NoError(t, err) assert.Equal(t, plaintext, decrypted) - keys, err := svc.store.ListDataKeys(ctx, namespace) + keys, err := svc.store.ListDataKeys(ctx, namespace.String()) require.NoError(t, err) assert.Equal(t, len(keys), 1) }) @@ -61,7 +62,7 @@ func TestEncryptionService_EnvelopeEncryption(t *testing.T) { require.NoError(t, err) assert.Equal(t, plaintext, decrypted) - keys, err := svc.store.ListDataKeys(ctx, namespace) + keys, err := svc.store.ListDataKeys(ctx, namespace.String()) require.NoError(t, err) assert.Equal(t, len(keys), 1) }) @@ -212,7 +213,7 @@ func TestEncryptionService_UseCurrentProvider(t *testing.T) { } encryptionManager.providerConfig.CurrentProvider = encryption.ProviderID("fakeProvider.v1") - namespace := "test-namespace" + namespace := xkube.Namespace("test-namespace") encrypted, _ := encryptionManager.Encrypt(context.Background(), namespace, []byte{}) assert.True(t, fake.encryptCalled) assert.False(t, fake.decryptCalled) @@ -241,7 +242,7 @@ func TestEncryptionService_UseCurrentProvider(t *testing.T) { func TestEncryptionService_SecretKeyVersionUpgrade(t *testing.T) { ctx := context.Background() - namespace := "test-namespace" + namespace := xkube.Namespace("test-namespace") // Generate random keys for testing oldKey := util.GenerateShortUID() + util.GenerateShortUID() // 32 chars @@ -416,16 +417,30 @@ func (p *fakeProvider) Decrypt(_ context.Context, _ []byte) ([]byte, error) { func TestEncryptionService_Decrypt(t *testing.T) { ctx := context.Background() - namespace := "test-namespace" + namespace := xkube.Namespace("test-namespace") t.Run("empty payload should fail", func(t *testing.T) { svc := setupTestService(t) - _, err := svc.Decrypt(context.Background(), namespace, []byte("")) + _, err := svc.Decrypt(context.Background(), namespace, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte(""), + }) require.Error(t, err) assert.Equal(t, "unable to decrypt empty payload", err.Error()) }) + t.Run("empty data key id should fail", func(t *testing.T) { + svc := setupTestService(t) + _, err := svc.Decrypt(context.Background(), namespace, contracts.EncryptedPayload{ + DataKeyID: "", + EncryptedData: []byte("some payload"), + }) + require.Error(t, err) + + assert.Equal(t, "unable to decrypt empty data key id", err.Error()) + }) + t.Run("ee encrypted payload with ee enabled should work", func(t *testing.T) { svc := setupTestService(t) ciphertext, err := svc.Encrypt(ctx, namespace, []byte("grafana")) @@ -442,7 +457,7 @@ func TestIntegration_SecretsService(t *testing.T) { ctx := context.Background() someData := []byte(`some-data`) - namespace := "test-namespace" + namespace := xkube.Namespace("test-namespace") tcs := map[string]func(*testing.T, db.DB, contracts.EncryptionManager){ "regular": func(t *testing.T, _ db.DB, svc contracts.EncryptionManager) { @@ -562,7 +577,7 @@ func TestIntegration_SecretsService(t *testing.T) { require.NoError(t, err) ctx := context.Background() - namespace := "test-namespace" + namespace := xkube.Namespace("test-namespace") // Here's what actually matters and varies on each test: look at the test case name. // diff --git a/pkg/registry/apis/secret/garbagecollectionworker/worker.go b/pkg/registry/apis/secret/garbagecollectionworker/worker.go index 4754f06c02d..e10bbed600e 100644 --- a/pkg/registry/apis/secret/garbagecollectionworker/worker.go +++ b/pkg/registry/apis/secret/garbagecollectionworker/worker.go @@ -104,7 +104,7 @@ func (w *Worker) Cleanup(ctx context.Context, sv *secretv1beta1.SecureValue) err } // Keeper deletion is idempotent - if err := keeper.Delete(ctx, keeperCfg, sv.Namespace, sv.Name, sv.Status.Version); err != nil { + if err := keeper.Delete(ctx, keeperCfg, xkube.Namespace(sv.Namespace), sv.Name, sv.Status.Version); err != nil { return fmt.Errorf("deleting secure value from keeper: %w", err) } diff --git a/pkg/registry/apis/secret/garbagecollectionworker/worker_test.go b/pkg/registry/apis/secret/garbagecollectionworker/worker_test.go index 344d2bcf605..23467d69b39 100644 --- a/pkg/registry/apis/secret/garbagecollectionworker/worker_test.go +++ b/pkg/registry/apis/secret/garbagecollectionworker/worker_test.go @@ -9,6 +9,7 @@ import ( secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/storage/secret/encryption" "github.com/mitchellh/copystructure" "github.com/stretchr/testify/require" @@ -58,7 +59,7 @@ func TestBasic(t *testing.T) { require.NoError(t, err) // Get the secret value once to make sure it's reachable - exposedValue, err := keeper.Expose(t.Context(), keeperCfg, sv.Namespace, sv.Name, sv.Status.Version) + exposedValue, err := keeper.Expose(t.Context(), keeperCfg, xkube.Namespace(sv.Namespace), sv.Name, sv.Status.Version) require.NoError(t, err) require.NotEmpty(t, exposedValue.DangerouslyExposeAndConsumeValue()) @@ -78,7 +79,7 @@ func TestBasic(t *testing.T) { require.Empty(t, svs) // Try to get the secreet value again to make sure it's been deleted from the keeper - exposedValue, err = keeper.Expose(t.Context(), keeperCfg, sv.Namespace, sv.Name, sv.Status.Version) + exposedValue, err = keeper.Expose(t.Context(), keeperCfg, xkube.Namespace(sv.Namespace), sv.Name, sv.Status.Version) require.ErrorIs(t, err, encryption.ErrEncryptedValueNotFound) require.Empty(t, exposedValue) }) diff --git a/pkg/registry/apis/secret/register.go b/pkg/registry/apis/secret/register.go index 879741dc948..155bc20d5ff 100644 --- a/pkg/registry/apis/secret/register.go +++ b/pkg/registry/apis/secret/register.go @@ -25,7 +25,7 @@ func RegisterDependencies( } // We shouldn't need to create the DB in HG, as that will use the MT api server. - if cfg.StackID == "" || cfg.SecretsManagement.IsDeveloperMode { + if cfg.SecretsManagement.RunSecretsDBMigrations { // Some DBs that claim to be MySQL/Postgres-compatible might not support table locking. lockDatabase := cfg.Raw.Section("database").Key("migration_locking").MustBool(true) diff --git a/pkg/registry/apis/secret/secretkeeper/secretkeeper.go b/pkg/registry/apis/secret/secretkeeper/secretkeeper.go index fc65573ac61..25241f5c15b 100644 --- a/pkg/registry/apis/secret/secretkeeper/secretkeeper.go +++ b/pkg/registry/apis/secret/secretkeeper/secretkeeper.go @@ -1,11 +1,15 @@ package secretkeeper import ( + "fmt" + "go.opentelemetry.io/otel/trace" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" + "github.com/grafana/grafana/pkg/registry/apis/secret" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper" + "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" ) @@ -20,11 +24,18 @@ func ProvideService( tracer trace.Tracer, store contracts.EncryptedValueStorage, encryptionManager contracts.EncryptionManager, + migrationExecutor contracts.EncryptedValueMigrationExecutor, reg prometheus.Registerer, + cfg *setting.Cfg, + _ *secret.DependencyRegisterer, // noop import so wire runs DB migrations before instantiating this service -- can be nil when manually instantiating ) (*OSSKeeperService, error) { + systemKeeper, err := sqlkeeper.NewSQLKeeper(tracer, encryptionManager, store, migrationExecutor, reg, cfg) + if err != nil { + return nil, fmt.Errorf("failed to create system keeper: %w", err) + } + return &OSSKeeperService{ - // TODO: rename to system keeper or something like that - systemKeeper: sqlkeeper.NewSQLKeeper(tracer, encryptionManager, store, reg), + systemKeeper: systemKeeper, }, nil } diff --git a/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go b/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go index 84baaf207b0..f60dcc91188 100644 --- a/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go +++ b/pkg/registry/apis/secret/secretkeeper/secretkeeper_test.go @@ -12,6 +12,7 @@ import ( osskmsproviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders" "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager" "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper" + "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/storage/secret/database" @@ -65,7 +66,8 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*OSSKeeperService, error) require.NoError(t, err) // Initialize the keeper service - keeperService, err := ProvideService(tracer, encValueStore, encryptionManager, nil) + keeperService, err := ProvideService(tracer, encValueStore, encryptionManager, &testutils.NoopMigrationExecutor{}, nil, cfg, nil) + require.NoError(t, err) return keeperService, err } diff --git a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go index bfe3972f72b..294eff1a7af 100644 --- a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go +++ b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go @@ -5,9 +5,12 @@ import ( "fmt" "time" + "github.com/grafana/grafana-app-sdk/logging" secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/metrics" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" + "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -26,20 +29,36 @@ func NewSQLKeeper( tracer trace.Tracer, encryptionManager contracts.EncryptionManager, store contracts.EncryptedValueStorage, + migrationExecutor contracts.EncryptedValueMigrationExecutor, reg prometheus.Registerer, -) *SQLKeeper { + cfg *setting.Cfg, +) (*SQLKeeper, error) { + // Only run the migration if running as an MT api server + if cfg.SecretsManagement.RunDataKeyMigration && cfg.SecretsManagement.RunSecretsDBMigrations { + // Run the encrypted value store migration before anything else, otherwise operations may fail + // TODO: This does not need to be here forever, but we may currently have on-prem deployments using GSM, so it needs to be here for now. + // Periodically assess whether it is safe to remove - most likely for G13 should be fine. + log := logging.FromContext(context.Background()) + log.Debug("sqlkeeper: executing encrypted value store migration") + rowsAffected, err := migrationExecutor.Execute(context.Background()) + log.Debug("sqlkeeper: encrypted value store migration completed", "rows_affected", rowsAffected) + if err != nil { + return nil, fmt.Errorf("error encountered during encrypted value store migration: %w", err) + } + } + return &SQLKeeper{ tracer: tracer, encryptionManager: encryptionManager, store: store, metrics: metrics.NewKeeperMetrics(reg), - } + }, nil } -func (s *SQLKeeper) Store(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace, name string, version int64, exposedValueOrRef string) (contracts.ExternalID, error) { +func (s *SQLKeeper) Store(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64, exposedValueOrRef string) (contracts.ExternalID, error) { ctx, span := s.tracer.Start(ctx, "SQLKeeper.Store", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), attribute.String("name", name), attribute.Int64("version", version)), ) @@ -63,9 +82,9 @@ func (s *SQLKeeper) Store(ctx context.Context, cfg secretv1beta1.KeeperConfig, n return contracts.ExternalID(""), nil } -func (s *SQLKeeper) Expose(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace, name string, version int64) (secretv1beta1.ExposedSecureValue, error) { +func (s *SQLKeeper) Expose(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64) (secretv1beta1.ExposedSecureValue, error) { ctx, span := s.tracer.Start(ctx, "SQLKeeper.Expose", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), attribute.String("name", name), attribute.Int64("version", version), )) @@ -77,7 +96,7 @@ func (s *SQLKeeper) Expose(ctx context.Context, cfg secretv1beta1.KeeperConfig, return "", fmt.Errorf("unable to get encrypted value: %w", err) } - exposedBytes, err := s.encryptionManager.Decrypt(ctx, namespace, encryptedValue.EncryptedData) + exposedBytes, err := s.encryptionManager.Decrypt(ctx, namespace, encryptedValue.EncryptedPayload) if err != nil { return "", fmt.Errorf("unable to decrypt value: %w", err) } @@ -88,9 +107,9 @@ func (s *SQLKeeper) Expose(ctx context.Context, cfg secretv1beta1.KeeperConfig, return exposedValue, nil } -func (s *SQLKeeper) Delete(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace, name string, version int64) error { +func (s *SQLKeeper) Delete(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64) error { ctx, span := s.tracer.Start(ctx, "SQLKeeper.Delete", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), attribute.String("name", name), attribute.Int64("version", version), )) @@ -107,9 +126,9 @@ func (s *SQLKeeper) Delete(ctx context.Context, cfg secretv1beta1.KeeperConfig, return nil } -func (s *SQLKeeper) Update(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace, name string, version int64, exposedValueOrRef string) error { +func (s *SQLKeeper) Update(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64, exposedValueOrRef string) error { ctx, span := s.tracer.Start(ctx, "SQLKeeper.Update", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), attribute.String("name", name), attribute.Int64("version", version), )) diff --git a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go index 1333cf894c7..34249227790 100644 --- a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go +++ b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go @@ -1,6 +1,7 @@ package sqlkeeper_test import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -8,6 +9,7 @@ import ( secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/tests/testsuite" ) @@ -16,10 +18,10 @@ func TestMain(m *testing.M) { } func Test_SQLKeeperSetup(t *testing.T) { - namespace1 := "namespace1" + namespace1 := xkube.Namespace("namespace1") name1 := "name1" version1 := int64(1) - namespace2 := "namespace2" + namespace2 := xkube.Namespace("namespace2") name2 := "name2" plaintext1 := "very secret string in namespace 1" plaintext2 := "very secret string in namespace 2" @@ -145,4 +147,47 @@ func Test_SQLKeeperSetup(t *testing.T) { err = sut.SQLKeeper.Update(t.Context(), nil, namespace1, "non_existing_name", version1, plaintext2) require.Error(t, err) }) + + t.Run("data key migration only runs if both secrets db migrations are enabled", func(t *testing.T) { + t.Parallel() + + m := &mockMigrationExecutor{} + + testutils.Setup(t, testutils.WithMutateCfg(func(cfg *testutils.SetupConfig) { + cfg.RunSecretsDBMigrations = false + cfg.RunDataKeyMigration = false + cfg.DataKeyMigrationExecutor = m + })) + assert.False(t, m.wasExecuted) + + testutils.Setup(t, testutils.WithMutateCfg(func(cfg *testutils.SetupConfig) { + cfg.RunSecretsDBMigrations = true + cfg.RunDataKeyMigration = false + cfg.DataKeyMigrationExecutor = m + })) + assert.False(t, m.wasExecuted) + + testutils.Setup(t, testutils.WithMutateCfg(func(cfg *testutils.SetupConfig) { + cfg.RunSecretsDBMigrations = false + cfg.RunDataKeyMigration = true + cfg.DataKeyMigrationExecutor = m + })) + assert.False(t, m.wasExecuted) + + testutils.Setup(t, testutils.WithMutateCfg(func(cfg *testutils.SetupConfig) { + cfg.RunSecretsDBMigrations = true + cfg.RunDataKeyMigration = true + cfg.DataKeyMigrationExecutor = m + })) + assert.True(t, m.wasExecuted) + }) +} + +type mockMigrationExecutor struct { + wasExecuted bool +} + +func (m *mockMigrationExecutor) Execute(ctx context.Context) (int, error) { + m.wasExecuted = true + return 0, nil } diff --git a/pkg/registry/apis/secret/service/consolidation.go b/pkg/registry/apis/secret/service/consolidation.go index b0baea659c4..bbe7d9084e6 100644 --- a/pkg/registry/apis/secret/service/consolidation.go +++ b/pkg/registry/apis/secret/service/consolidation.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" otelcodes "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" ) @@ -60,21 +61,21 @@ func (s *ConsolidationService) Consolidate(ctx context.Context) (err error) { for _, ev := range encryptedValues { // Decrypt the value using its old data key. - decryptedValue, err := s.encryptionManager.Decrypt(ctx, ev.Namespace, ev.EncryptedData) + decryptedValue, err := s.encryptionManager.Decrypt(ctx, xkube.Namespace(ev.Namespace), ev.EncryptedPayload) if err != nil { logging.FromContext(ctx).Error("Failed to decrypt value", "namespace", ev.Namespace, "name", ev.Name, "error", err) continue } // Re-encrypt the value using a new data key. - reEncryptedValue, err := s.encryptionManager.Encrypt(ctx, ev.Namespace, decryptedValue) + reEncryptedValue, err := s.encryptionManager.Encrypt(ctx, xkube.Namespace(ev.Namespace), decryptedValue) if err != nil { logging.FromContext(ctx).Error("Failed to re-encrypt value", "namespace", ev.Namespace, "name", ev.Name, "error", err) continue } // Update the encrypted value in the store. - err = s.encryptedValueStore.Update(ctx, ev.Namespace, ev.Name, ev.Version, reEncryptedValue) + err = s.encryptedValueStore.Update(ctx, xkube.Namespace(ev.Namespace), ev.Name, ev.Version, reEncryptedValue) if err != nil { logging.FromContext(ctx).Error("Failed to update encrypted value", "namespace", ev.Namespace, "name", ev.Name, "error", err) continue diff --git a/pkg/registry/apis/secret/service/consolidation_test.go b/pkg/registry/apis/secret/service/consolidation_test.go index 638ced19a81..381cf956436 100644 --- a/pkg/registry/apis/secret/service/consolidation_test.go +++ b/pkg/registry/apis/secret/service/consolidation_test.go @@ -97,7 +97,7 @@ func TestConsolidation(t *testing.T) { require.NoError(t, err) originalDecryptedValues = append(originalDecryptedValues, decryptedValue.DangerouslyExposeAndConsumeValue()) - encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, xkube.Namespace(tc.namespace), tc.name, 1) require.NoError(t, err) require.NotNil(t, encryptedValue) originalEncryptedData = append(originalEncryptedData, encryptedValue.EncryptedData) @@ -115,7 +115,7 @@ func TestConsolidation(t *testing.T) { require.Equal(t, originalDecryptedValues[i], decryptedValue.DangerouslyExposeAndConsumeValue()) // Verify that the encrypted data has changed (indicating re-encryption) - encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, xkube.Namespace(tc.namespace), tc.name, 1) require.NoError(t, err) require.NotEqual(t, originalEncryptedData[i], encryptedValue.EncryptedData) } @@ -174,7 +174,7 @@ func TestConsolidation(t *testing.T) { require.NoError(t, err) initialDecryptedValues = append(initialDecryptedValues, decryptedValue.DangerouslyExposeAndConsumeValue()) - encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, xkube.Namespace(tc.namespace), tc.name, 1) require.NoError(t, err) initialEncryptedData = append(initialEncryptedData, encryptedValue.EncryptedData) } @@ -223,7 +223,7 @@ func TestConsolidation(t *testing.T) { require.NoError(t, err) newSecretDecryptedValues = append(newSecretDecryptedValues, decryptedValue.DangerouslyExposeAndConsumeValue()) - encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, xkube.Namespace(tc.namespace), tc.name, 1) require.NoError(t, err) newSecretEncryptedData = append(newSecretEncryptedData, encryptedValue.EncryptedData) } @@ -252,7 +252,7 @@ func TestConsolidation(t *testing.T) { require.Equal(t, initialDecryptedValues[i], decryptedValue.DangerouslyExposeAndConsumeValue()) // Verify that the encrypted data has changed (indicating re-encryption) - encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, xkube.Namespace(tc.namespace), tc.name, 1) require.NoError(t, err) require.NotEqual(t, initialEncryptedData[i], encryptedValue.EncryptedData) } @@ -275,7 +275,7 @@ func TestConsolidation(t *testing.T) { // Verify that the encrypted data has changed from what it was when first created // (indicating it was re-encrypted during consolidation) - encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, tc.namespace, tc.name, 1) + encryptedValue, err := sut.EncryptedValueStorage.Get(ctx, xkube.Namespace(tc.namespace), tc.name, 1) require.NoError(t, err) require.NotEqual(t, newSecretEncryptedData[i], encryptedValue.EncryptedData) } diff --git a/pkg/registry/apis/secret/service/secure_value.go b/pkg/registry/apis/secret/service/secure_value.go index 2ad69b759db..cc4e6c80c72 100644 --- a/pkg/registry/apis/secret/service/secure_value.go +++ b/pkg/registry/apis/secret/service/secure_value.go @@ -146,7 +146,7 @@ func (s *SecureValueService) Update(ctx context.Context, newSecureValue *secretv } logging.FromContext(ctx).Debug("retrieved keeper", "namespace", newSecureValue.Namespace, "keeperName", newSecureValue.Spec.Keeper, "type", keeperCfg.Type()) - secret, err := keeper.Expose(ctx, keeperCfg, newSecureValue.Namespace, newSecureValue.Name, currentVersion.Status.Version) + secret, err := keeper.Expose(ctx, keeperCfg, xkube.Namespace(newSecureValue.Namespace), newSecureValue.Name, currentVersion.Status.Version) if err != nil { return nil, false, fmt.Errorf("reading secret value from keeper: %w", err) } @@ -191,7 +191,7 @@ func (s *SecureValueService) createNewVersion(ctx context.Context, sv *secretv1b // TODO: can we stop using external id? // TODO: store uses only the namespace and returns and id. It could be a kv instead. // TODO: check that the encrypted store works with multiple versions - externalID, err := keeper.Store(ctx, keeperCfg, createdSv.Namespace, createdSv.Name, createdSv.Status.Version, sv.Spec.Value.DangerouslyExposeAndConsumeValue()) + externalID, err := keeper.Store(ctx, keeperCfg, xkube.Namespace(createdSv.Namespace), createdSv.Name, createdSv.Status.Version, sv.Spec.Value.DangerouslyExposeAndConsumeValue()) if err != nil { return nil, fmt.Errorf("storing secure value in keeper: %w", err) } diff --git a/pkg/registry/apis/secret/testutils/testutils.go b/pkg/registry/apis/secret/testutils/testutils.go index 50190dbb3ff..6a5f1e40d30 100644 --- a/pkg/registry/apis/secret/testutils/testutils.go +++ b/pkg/registry/apis/secret/testutils/testutils.go @@ -40,7 +40,10 @@ import ( ) type SetupConfig struct { - KeeperService contracts.KeeperService + KeeperService contracts.KeeperService + DataKeyMigrationExecutor contracts.EncryptedValueMigrationExecutor + RunSecretsDBMigrations bool + RunDataKeyMigration bool } func defaultSetupCfg() SetupConfig { @@ -92,6 +95,8 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { CurrentEncryptionProvider: "secret_key.v1", ConfiguredKMSProviders: map[string]map[string]string{"secret_key.v1": {"secret_key": defaultKey}}, GCWorkerEnabled: false, + RunSecretsDBMigrations: setupCfg.RunSecretsDBMigrations, + RunDataKeyMigration: setupCfg.RunDataKeyMigration, GCWorkerMaxBatchSize: 2, GCWorkerMaxConcurrentCleanups: 2, } @@ -126,7 +131,17 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { globalEncryptedValueStorage, err := encryptionstorage.ProvideGlobalEncryptedValueStorage(database, tracer) require.NoError(t, err) - sqlKeeper := sqlkeeper.NewSQLKeeper(tracer, encryptionManager, encryptedValueStorage, nil) + // Initialize a noop migration executor for the sql keeper so it doesn't interfere with initialization, or use the one provided + fakeMigrationExecutor := setupCfg.DataKeyMigrationExecutor + if fakeMigrationExecutor == nil { + fakeMigrationExecutor = &NoopMigrationExecutor{} + } + sqlKeeper, err := sqlkeeper.NewSQLKeeper(tracer, encryptionManager, encryptedValueStorage, fakeMigrationExecutor, nil, cfg) + require.NoError(t, err) + + // Initialize a real migration executor for test + realMigrationExecutor, err := encryptionstorage.ProvideEncryptedValueMigrationExecutor(database, tracer, encryptedValueStorage, globalEncryptedValueStorage) + require.NoError(t, err) var keeperService contracts.KeeperService = newKeeperServiceWrapper(sqlKeeper) @@ -158,39 +173,41 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut { keeperService) return Sut{ - SecureValueService: secureValueService, - SecureValueMetadataStorage: secureValueMetadataStorage, - DecryptStorage: decryptStorage, - DecryptService: decryptService, - EncryptedValueStorage: encryptedValueStorage, - GlobalEncryptedValueStorage: globalEncryptedValueStorage, - SQLKeeper: sqlKeeper, - Database: database, - AccessClient: accessClient, - ConsolidationService: consolidationService, - EncryptionManager: encryptionManager, - GlobalDataKeyStore: globalDataKeyStore, - GarbageCollectionWorker: garbageCollectionWorker, - Clock: clock, - KeeperService: keeperService, - KeeperMetadataStorage: keeperMetadataStorage, + SecureValueService: secureValueService, + SecureValueMetadataStorage: secureValueMetadataStorage, + DecryptStorage: decryptStorage, + DecryptService: decryptService, + EncryptedValueStorage: encryptedValueStorage, + GlobalEncryptedValueStorage: globalEncryptedValueStorage, + EncryptedValueMigrationExecutor: realMigrationExecutor, + SQLKeeper: sqlKeeper, + Database: database, + AccessClient: accessClient, + ConsolidationService: consolidationService, + EncryptionManager: encryptionManager, + GlobalDataKeyStore: globalDataKeyStore, + GarbageCollectionWorker: garbageCollectionWorker, + Clock: clock, + KeeperService: keeperService, + KeeperMetadataStorage: keeperMetadataStorage, } } type Sut struct { - SecureValueService contracts.SecureValueService - SecureValueMetadataStorage contracts.SecureValueMetadataStorage - DecryptStorage contracts.DecryptStorage - DecryptService decryptcontracts.DecryptService - EncryptedValueStorage contracts.EncryptedValueStorage - GlobalEncryptedValueStorage contracts.GlobalEncryptedValueStorage - SQLKeeper *sqlkeeper.SQLKeeper - Database *database.Database - AccessClient types.AccessClient - ConsolidationService contracts.ConsolidationService - EncryptionManager contracts.EncryptionManager - GlobalDataKeyStore contracts.GlobalDataKeyStorage - GarbageCollectionWorker *garbagecollectionworker.Worker + SecureValueService contracts.SecureValueService + SecureValueMetadataStorage contracts.SecureValueMetadataStorage + DecryptStorage contracts.DecryptStorage + DecryptService decryptcontracts.DecryptService + EncryptedValueStorage contracts.EncryptedValueStorage + GlobalEncryptedValueStorage contracts.GlobalEncryptedValueStorage + EncryptedValueMigrationExecutor contracts.EncryptedValueMigrationExecutor + SQLKeeper *sqlkeeper.SQLKeeper + Database *database.Database + AccessClient types.AccessClient + ConsolidationService contracts.ConsolidationService + EncryptionManager contracts.EncryptionManager + GlobalDataKeyStore contracts.GlobalDataKeyStorage + GarbageCollectionWorker *garbagecollectionworker.Worker // The fake clock passed to implementations to make testing easier Clock *FakeClock KeeperService contracts.KeeperService @@ -366,3 +383,10 @@ func (c *FakeClock) Now() time.Time { func (c *FakeClock) AdvanceBy(duration time.Duration) { c.Current = c.Current.Add(duration) } + +type NoopMigrationExecutor struct { +} + +func (e *NoopMigrationExecutor) Execute(ctx context.Context) (int, error) { + return 0, nil +} diff --git a/pkg/registry/apps/plugins/register.go b/pkg/registry/apps/plugins/register.go index 2929efda78f..e95b120cd66 100644 --- a/pkg/registry/apps/plugins/register.go +++ b/pkg/registry/apps/plugins/register.go @@ -1,20 +1,15 @@ package plugins import ( - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/apiserver/pkg/registry/generic" - "k8s.io/apiserver/pkg/registry/rest" restclient "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-app-sdk/simple" pluginsappapis "github.com/grafana/grafana/apps/plugins/pkg/apis" - pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1" pluginsapp "github.com/grafana/grafana/apps/plugins/pkg/app" "github.com/grafana/grafana/pkg/services/apiserver/appinstaller" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" ) @@ -26,16 +21,13 @@ var ( type PluginsAppInstaller struct { appsdkapiserver.AppInstaller - cfg *setting.Cfg } func RegisterAppInstaller( cfg *setting.Cfg, features featuremgmt.FeatureToggles, ) (*PluginsAppInstaller, error) { - installer := &PluginsAppInstaller{ - cfg: cfg, - } + installer := &PluginsAppInstaller{} specificConfig := any(nil) provider := simple.NewAppProvider(pluginsappapis.LocalManifest(), specificConfig, pluginsapp.New) appConfig := app.Config{ @@ -51,21 +43,6 @@ func RegisterAppInstaller( return installer, nil } -func (p *PluginsAppInstaller) InstallAPIs( - server appsdkapiserver.GenericAPIServer, - restOptsGetter generic.RESTOptionsGetter, -) error { - pluginMetaGVR := pluginsv0alpha1.PluginMetaKind().GroupVersionResource() - replacedStorage := map[schema.GroupVersionResource]rest.Storage{ - pluginMetaGVR: pluginsapp.NewPluginMetaStorage(request.GetNamespaceMapper(p.cfg)), - } - wrappedServer := &customStorageWrapper{ - wrapped: server, - replace: replacedStorage, - } - return p.AppInstaller.InstallAPIs(wrappedServer, restOptsGetter) -} - // GetAuthorizer returns the authorizer for the plugins app. func (p *PluginsAppInstaller) GetAuthorizer() authorizer.Authorizer { return pluginsapp.GetAuthorizer() diff --git a/pkg/registry/apps/shorturl/conversions.go b/pkg/registry/apps/shorturl/conversions.go index 979106d3bda..c23f014adbd 100644 --- a/pkg/registry/apps/shorturl/conversions.go +++ b/pkg/registry/apps/shorturl/conversions.go @@ -21,10 +21,17 @@ func convertToK8sResource(v *shorturls.ShortUrl, namespacer request.NamespaceMap status := shorturl.ShortURLStatus{ LastSeenAt: v.LastSeenAt, } + + // resourceVersion can't be 0, since we are using the lastSeenAt value, when it's zero we default to current time + resourceVersion := fmt.Sprintf("%d", v.LastSeenAt) + if v.LastSeenAt == 0 { + resourceVersion = fmt.Sprintf("%d", time.Now().UnixMilli()) + } + p := &shorturl.ShortURL{ ObjectMeta: metav1.ObjectMeta{ Name: v.Uid, - ResourceVersion: fmt.Sprintf("%d", v.LastSeenAt), + ResourceVersion: resourceVersion, CreationTimestamp: metav1.NewTime(time.UnixMilli(v.CreatedAt)), Namespace: namespacer(v.OrgId), }, diff --git a/pkg/registry/apps/shorturl/legacy_storage.go b/pkg/registry/apps/shorturl/legacy_storage.go index c9462359236..de36560b679 100644 --- a/pkg/registry/apps/shorturl/legacy_storage.go +++ b/pkg/registry/apps/shorturl/legacy_storage.go @@ -16,9 +16,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/shorturls" - "github.com/grafana/grafana/pkg/services/user" ) var ( @@ -87,13 +85,7 @@ func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.Ge return nil, err } - // Convert any identity.Requester to *user.SignedInUser - signedInUser, err := convertRequesterToSignedInUser(requester) - if err != nil { - return nil, fmt.Errorf("failed to convert requester: %w", err) - } - - dto, err := s.service.GetShortURLByUID(ctx, signedInUser, name) + dto, err := s.service.GetShortURLByUID(ctx, requester, name) if err != nil || dto == nil { if errors.Is(err, shorturls.ErrShortURLNotFound) || err == nil { err = k8serrors.NewNotFound(shorturl.ShortURLKind().GroupVersionResource().GroupResource(), name) @@ -114,12 +106,6 @@ func (s *legacyStorage) Create(ctx context.Context, return nil, err } - // Convert any identity.Requester to *user.SignedInUser - signedInUser, err := convertRequesterToSignedInUser(requester) - if err != nil { - return nil, fmt.Errorf("failed to convert requester: %w", err) - } - if createValidation != nil { if err := createValidation(ctx, obj.DeepCopyObject()); err != nil { return nil, err @@ -133,7 +119,7 @@ func (s *legacyStorage) Create(ctx context.Context, Path: p.Spec.Path, UID: p.Name, } - out, err := s.service.CreateShortURL(ctx, signedInUser, cmd) + out, err := s.service.CreateShortURL(ctx, requester, cmd) if err != nil { return nil, err } @@ -154,13 +140,7 @@ func (s *legacyStorage) Update(ctx context.Context, return nil, false, err } - // Convert any identity.Requester to *user.SignedInUser - signedInUser, err := convertRequesterToSignedInUser(requester) - if err != nil { - return nil, false, fmt.Errorf("failed to convert requester: %w", err) - } - - shortURL, err := s.service.GetShortURLByUID(ctx, signedInUser, name) + shortURL, err := s.service.GetShortURLByUID(ctx, requester, name) if err != nil || shortURL == nil { if errors.Is(err, shorturls.ErrShortURLNotFound) || err == nil { err = k8serrors.NewNotFound(shorturl.ShortURLKind().GroupVersionResource().GroupResource(), name) @@ -173,7 +153,7 @@ func (s *legacyStorage) Update(ctx context.Context, return nil, false, err } // Fetch the updated short URL to return - updatedLegacyShortURL, err := s.service.GetShortURLByUID(ctx, signedInUser, name) + updatedLegacyShortURL, err := s.service.GetShortURLByUID(ctx, requester, name) if err != nil { return nil, false, err } @@ -199,27 +179,3 @@ func (s *legacyStorage) Delete(ctx context.Context, name string, deleteValidatio func (s *legacyStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { return nil, fmt.Errorf("DeleteCollection for shorturl not implemented") } - -// convertRequesterToSignedInUser converts any identity.Requester to *user.SignedInUser -// This is needed because some legacy shorturls service methods still expect SignedInUser -func convertRequesterToSignedInUser(requester identity.Requester) (*user.SignedInUser, error) { - // If it's already a SignedInUser, return it directly - if signedInUser, ok := requester.(*user.SignedInUser); ok { - return signedInUser, nil - } - - // If it's a StaticRequester (service identity), convert it - if staticRequester, ok := requester.(*identity.StaticRequester); ok { - return &user.SignedInUser{ - UserID: staticRequester.UserID, // Used for CreatedBy field - OrgID: staticRequester.OrgID, // Used in SQL queries - }, nil - } - - // If it's an authn.Identity, use its SignedInUser method - if authnIdentity, ok := requester.(*authn.Identity); ok { - return authnIdentity.SignedInUser(), nil - } - - return nil, fmt.Errorf("unsupported identity type") -} diff --git a/pkg/registry/apps/shorturl/register.go b/pkg/registry/apps/shorturl/register.go index a44593ceabe..423eae92073 100644 --- a/pkg/registry/apps/shorturl/register.go +++ b/pkg/registry/apps/shorturl/register.go @@ -5,6 +5,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/rest" restclient "k8s.io/client-go/rest" "github.com/grafana/grafana-app-sdk/app" @@ -86,3 +87,18 @@ func (s *ShortURLAppInstaller) GetLegacyStorage(requested schema.GroupVersionRes ) return legacyStore } + +func (s *ShortURLAppInstaller) GetLegacyStatus(requested schema.GroupVersionResource, unified *appsdkapiserver.StatusREST) rest.Storage { + gvr := shorturl.ShortURLKind().GroupVersionResource() + if requested.String() != gvr.String() { + return nil + } + return &statusDualWriter{ + gv: gvr.GroupVersion(), + status: unified, + legacy: &legacyStorage{ + service: s.service, + namespacer: s.namespacer, + }, + } +} diff --git a/pkg/registry/apps/shorturl/status.go b/pkg/registry/apps/shorturl/status.go new file mode 100644 index 00000000000..ed9fe063a98 --- /dev/null +++ b/pkg/registry/apps/shorturl/status.go @@ -0,0 +1,111 @@ +package shorturl + +import ( + "context" + "errors" + "fmt" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/rest" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + + "github.com/grafana/grafana-app-sdk/k8s/apiserver" + "github.com/grafana/grafana-app-sdk/logging" + shorturl "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/services/shorturls" +) + +type statusDualWriter struct { + gv schema.GroupVersion + status *apiserver.StatusREST + legacy *legacyStorage +} + +var ( + _ rest.Patcher = (*statusDualWriter)(nil) + _ rest.Storage = (*statusDualWriter)(nil) + _ rest.ResetFieldsStrategy = (*statusDualWriter)(nil) +) + +// Destroy implements rest.Storage. +func (s *statusDualWriter) Destroy() {} + +// New implements rest.Storage. +func (s *statusDualWriter) New() runtime.Object { + return s.legacy.New() +} + +// Get implements rest.Patcher. +func (s *statusDualWriter) Get(ctx context.Context, name string, options *v1.GetOptions) (runtime.Object, error) { + return s.legacy.Get(ctx, name, options) +} + +// Update implements rest.Patcher. +func (s *statusDualWriter) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *v1.UpdateOptions) (runtime.Object, bool, error) { + requester, err := identity.GetRequester(ctx) + if err != nil { + return nil, false, err + } + + shortURL, err := s.legacy.service.GetShortURLByUID(ctx, requester, name) + if err != nil || shortURL == nil { + if errors.Is(err, shorturls.ErrShortURLNotFound) || err == nil { + err = k8serrors.NewNotFound(shorturl.ShortURLKind().GroupVersionResource().GroupResource(), name) + } + return nil, false, err + } + + // This ignores the incoming and updates it directly + err = s.legacy.service.UpdateLastSeenAt(ctx, shortURL) + if err != nil { + return nil, false, err + } + + getter := func(getter rest.Getter) (*shorturl.ShortURL, error) { + obj, err := getter.Get(ctx, name, &v1.GetOptions{}) + if err != nil { + return nil, err + } + val, ok := obj.(*shorturl.ShortURL) + if !ok { + return nil, fmt.Errorf("expected ShortURL but got %T", obj) + } + return val, nil + } + + legacy, err := getter(s.legacy) + if err != nil { + return nil, false, err // unable to get legacy object + } + + unified, err := getter(s.status) + if err != nil { + logging.FromContext(ctx).Warn("unable to read unified status", "error", err) + return legacy, false, nil + } + + // Use the same status from legacy in unified + unified.Status = legacy.Status + + _, _, err = s.status.Update(ctx, name, rest.DefaultUpdatedObjectInfo(unified), createValidation, updateValidation, false, options) + if err != nil { + logging.FromContext(ctx).Warn("error updating unified status", "error", err) + } + + return legacy, false, err +} + +// GetResetFields implements rest.ResetFieldsStrategy +func (s *statusDualWriter) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + fields := map[fieldpath.APIVersion]*fieldpath.Set{ + fieldpath.APIVersion(s.gv.String()): fieldpath.NewSet( + fieldpath.MakePathOrDie("spec"), + fieldpath.MakePathOrDie("metadata"), + ), + } + return fields +} diff --git a/pkg/server/wire.go b/pkg/server/wire.go index b06e7e995ad..179b371760d 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -449,6 +449,7 @@ var wireBasicSet = wire.NewSet( secretencryption.ProvideGlobalDataKeyStorage, secretencryption.ProvideEncryptedValueStorage, secretencryption.ProvideGlobalEncryptedValueStorage, + secretencryption.ProvideEncryptedValueMigrationExecutor, secretsecurevalueservice.ProvideSecureValueService, secretvalidator.ProvideKeeperValidator, secretvalidator.ProvideSecureValueValidator, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 3507dc75ff6..94a02ac291d 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -220,7 +220,7 @@ import ( kvstore2 "github.com/grafana/grafana/pkg/services/secrets/kvstore" migrations2 "github.com/grafana/grafana/pkg/services/secrets/kvstore/migrations" "github.com/grafana/grafana/pkg/services/secrets/manager" - "github.com/grafana/grafana/pkg/services/secrets/migrator" + migrator2 "github.com/grafana/grafana/pkg/services/secrets/migrator" "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/services/serviceaccounts/extsvcaccounts" manager3 "github.com/grafana/grafana/pkg/services/serviceaccounts/manager" @@ -258,7 +258,7 @@ import ( database4 "github.com/grafana/grafana/pkg/storage/secret/database" "github.com/grafana/grafana/pkg/storage/secret/encryption" "github.com/grafana/grafana/pkg/storage/secret/metadata" - migrator2 "github.com/grafana/grafana/pkg/storage/secret/migrator" + "github.com/grafana/grafana/pkg/storage/secret/migrator" "github.com/grafana/grafana/pkg/storage/unified" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/search" @@ -486,7 +486,20 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } - ossKeeperService, err := secretkeeper.ProvideService(tracer, encryptedValueStorage, encryptionManager, registerer) + globalEncryptedValueStorage, err := encryption.ProvideGlobalEncryptedValueStorage(databaseDatabase, tracer) + if err != nil { + return nil, err + } + encryptedValueMigrationExecutor, err := encryption.ProvideEncryptedValueMigrationExecutor(databaseDatabase, tracer, encryptedValueStorage, globalEncryptedValueStorage) + if err != nil { + return nil, err + } + secretDBMigrator := migrator.NewWithEngine(sqlStore) + dependencyRegisterer, err := secret.RegisterDependencies(featureToggles, cfg, secretDBMigrator, acimplService) + if err != nil { + return nil, err + } + ossKeeperService, err := secretkeeper.ProvideService(tracer, encryptedValueStorage, encryptionManager, encryptedValueMigrationExecutor, registerer, cfg, dependencyRegisterer) if err != nil { return nil, err } @@ -700,7 +713,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } csrfCSRF := csrf.ProvideCSRFFilter(cfg) playlistService := playlistimpl.ProvideService(sqlStore, tracingService) - secretsMigrator := migrator.ProvideSecretsMigrator(serviceService, secretsService, sqlStore, ossImpl, featureToggles) + secretsMigrator := migrator2.ProvideSecretsMigrator(serviceService, secretsService, sqlStore, ossImpl, featureToggles) dataSourceSecretMigrationService := migrations2.ProvideDataSourceMigrationService(service15, kvStore, featureToggles) secretMigrationProviderImpl := migrations2.ProvideSecretMigrationProvider(serverLockService, dataSourceSecretMigrationService) publicDashboardServiceImpl := service3.ProvideService(cfg, featureToggles, publicDashboardStoreImpl, queryServiceImpl, repositoryImpl, accessControl, publicDashboardServiceWrapperImpl, dashboardService, ossLicensingService) @@ -832,7 +845,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService) ldapImpl := service12.ProvideService(cfg, featureToggles, ssosettingsimplService) apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService) - dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService) + dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService) snapshotsAPIBuilder := dashboardsnapshot.RegisterAPIService(serviceImpl, apiserverService, cfg, featureToggles, sqlStore, registerer) dataSourceAPIBuilder, err := datasource.RegisterAPIService(configProvider, featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, accessControl, registerer) if err != nil { @@ -880,11 +893,6 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api if err != nil { return nil, err } - secretDBMigrator := migrator2.NewWithEngine(sqlStore) - dependencyRegisterer, err := secret.RegisterDependencies(featureToggles, cfg, secretDBMigrator, acimplService) - if err != nil { - return nil, err - } apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, provisioningAPIBuilder, ofrepAPIBuilder, dependencyRegisterer) teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService) if err != nil { @@ -1110,7 +1118,20 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - ossKeeperService, err := secretkeeper.ProvideService(tracer, encryptedValueStorage, encryptionManager, registerer) + globalEncryptedValueStorage, err := encryption.ProvideGlobalEncryptedValueStorage(databaseDatabase, tracer) + if err != nil { + return nil, err + } + encryptedValueMigrationExecutor, err := encryption.ProvideEncryptedValueMigrationExecutor(databaseDatabase, tracer, encryptedValueStorage, globalEncryptedValueStorage) + if err != nil { + return nil, err + } + secretDBMigrator := migrator.NewWithEngine(sqlStore) + dependencyRegisterer, err := secret.RegisterDependencies(featureToggles, cfg, secretDBMigrator, acimplService) + if err != nil { + return nil, err + } + ossKeeperService, err := secretkeeper.ProvideService(tracer, encryptedValueStorage, encryptionManager, encryptedValueMigrationExecutor, registerer, cfg, dependencyRegisterer) if err != nil { return nil, err } @@ -1326,7 +1347,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } csrfCSRF := csrf.ProvideCSRFFilter(cfg) playlistService := playlistimpl.ProvideService(sqlStore, tracingService) - secretsMigrator := migrator.ProvideSecretsMigrator(serviceService, secretsService, sqlStore, ossImpl, featureToggles) + secretsMigrator := migrator2.ProvideSecretsMigrator(serviceService, secretsService, sqlStore, ossImpl, featureToggles) dataSourceSecretMigrationService := migrations2.ProvideDataSourceMigrationService(service15, kvStore, featureToggles) secretMigrationProviderImpl := migrations2.ProvideSecretMigrationProvider(serverLockService, dataSourceSecretMigrationService) publicDashboardServiceImpl := service3.ProvideService(cfg, featureToggles, publicDashboardStoreImpl, queryServiceImpl, repositoryImpl, accessControl, publicDashboardServiceWrapperImpl, dashboardService, ossLicensingService) @@ -1458,7 +1479,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService) ldapImpl := service12.ProvideService(cfg, featureToggles, ssosettingsimplService) apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService) - dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService) + dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service15, dashboardServiceImpl, dashboardPermissionsService, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, libraryPanelService, eventualRestConfigProvider, userService, libraryElementService) snapshotsAPIBuilder := dashboardsnapshot.RegisterAPIService(serviceImpl, apiserverService, cfg, featureToggles, sqlStore, registerer) dataSourceAPIBuilder, err := datasource.RegisterAPIService(configProvider, featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, accessControl, registerer) if err != nil { @@ -1506,11 +1527,6 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac if err != nil { return nil, err } - secretDBMigrator := migrator2.NewWithEngine(sqlStore) - dependencyRegisterer, err := secret.RegisterDependencies(featureToggles, cfg, secretDBMigrator, acimplService) - if err != nil { - return nil, err - } apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, apiBuilder, provisioningAPIBuilder, ofrepAPIBuilder, dependencyRegisterer) teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService) if err != nil { @@ -1580,7 +1596,7 @@ func InitializeForCLI(ctx context.Context, cfg *setting.Cfg) (Runner, error) { if err != nil { return Runner{}, err } - secretsMigrator := migrator.ProvideSecretsMigrator(serviceService, secretsService, sqlStore, ossImpl, featureToggles) + secretsMigrator := migrator2.ProvideSecretsMigrator(serviceService, secretsService, sqlStore, ossImpl, featureToggles) configProvider, err := configprovider.ProvideService(cfg) if err != nil { return Runner{}, err @@ -1723,7 +1739,7 @@ var withOTelSet = wire.NewSet( otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator, ) -var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, wire.Bind(new(installsync.ServerLock), new(*serverlock.ServerLockService)), annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), garbagecollectionworker.ProvideWorker, grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), dsquerierclient.NewNullQSDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioningService, service7.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), wire.Bind(new(folder.LegacyService), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), service11.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service11.ImportDashboardService)), service8.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service8.Service)), service8.ProvideDashboardUpdater, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), promtypemigration.ProvideAzurePromMigrationService, promtypemigration.ProvideAmazonPromMigrationService, promtypemigration.ProvidePromTypeMigrationProvider, wire.Bind(new(promtypemigration.PromTypeMigrationProvider), new(*promtypemigration.PromTypeMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, accesscontrol.ProvideFixedRolesLoader, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, caching.ProvideCachingServiceClient, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, wire.Value([]decrypt.ExtraOwnerDecrypter(nil)), decrypt.ProvideDecryptService, inline.ProvideInlineSecureValueService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, service5.ProvideSecureValueService, validator.ProvideKeeperValidator, validator.ProvideSecureValueValidator, mutator.ProvideKeeperMutator, mutator.ProvideSecureValueMutator, migrator2.NewWithEngine, database4.ProvideDatabase, clock.ProvideClock, wire.Bind(new(contracts.Database), new(*database4.Database)), wire.Bind(new(contracts.Clock), new(*clock.Clock)), manager2.ProvideEncryptionManager, service4.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet, client.ProvideK8sClientWithFallback) +var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator3.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, wire.Bind(new(installsync.ServerLock), new(*serverlock.ServerLockService)), annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service12.ProvideService, wire.Bind(new(service12.LDAP), new(*service12.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service9.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service9.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption2.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), garbagecollectionworker.ProvideWorker, grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database5.DashboardSnapshotStore)), database5.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service10.ServiceImpl)), service10.ProvideService, service9.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service9.Service)), service9.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager3.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), dsquerierclient.NewNullQSDatasourceClientBuilder, expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, service7.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service7.DashboardServiceImpl)), service7.ProvideDashboardService, service7.ProvideDashboardProvisioningService, service7.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), wire.Bind(new(folder.LegacyService), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), service11.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service11.ImportDashboardService)), service8.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service8.Service)), service8.ProvideDashboardUpdater, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), promtypemigration.ProvideAzurePromMigrationService, promtypemigration.ProvideAmazonPromMigrationService, promtypemigration.ProvidePromTypeMigrationProvider, wire.Bind(new(promtypemigration.PromTypeMigrationProvider), new(*promtypemigration.PromTypeMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, accesscontrol.ProvideFixedRolesLoader, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, caching.ProvideCachingServiceClient, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideDecryptStorage, decrypt.ProvideDecryptAuthorizer, wire.Value([]decrypt.ExtraOwnerDecrypter(nil)), decrypt.ProvideDecryptService, inline.ProvideInlineSecureValueService, encryption.ProvideDataKeyStorage, encryption.ProvideGlobalDataKeyStorage, encryption.ProvideEncryptedValueStorage, encryption.ProvideGlobalEncryptedValueStorage, encryption.ProvideEncryptedValueMigrationExecutor, service5.ProvideSecureValueService, validator.ProvideKeeperValidator, validator.ProvideSecureValueValidator, mutator.ProvideKeeperMutator, mutator.ProvideSecureValueMutator, migrator.NewWithEngine, database4.ProvideDatabase, clock.ProvideClock, wire.Bind(new(contracts.Database), new(*database4.Database)), wire.Bind(new(contracts.Clock), new(*clock.Clock)), manager2.ProvideEncryptionManager, service4.ProvideAESGCMCipherService, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet, client.ProvideK8sClientWithFallback) var wireSet = wire.NewSet( wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)), diff --git a/pkg/services/apiserver/appinstaller/installer.go b/pkg/services/apiserver/appinstaller/installer.go index a89d2b84f9a..d7742eedf12 100644 --- a/pkg/services/apiserver/appinstaller/installer.go +++ b/pkg/services/apiserver/appinstaller/installer.go @@ -12,18 +12,18 @@ import ( "k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" serverstore "k8s.io/apiserver/pkg/server/storage" "k8s.io/kube-openapi/pkg/common" appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver" "github.com/grafana/grafana-app-sdk/logging" - "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" - grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" grafanaapiserveroptions "github.com/grafana/grafana/pkg/services/apiserver/options" + "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite" ) type LegacyStorageGetterFunc func(schema.GroupVersionResource) grafanarest.Storage @@ -32,6 +32,12 @@ type LegacyStorageProvider interface { GetLegacyStorage(schema.GroupVersionResource) grafanarest.Storage } +// In the rare case that that legacy needs to support the status subresource +// Unlike resource storage, dual writing must be managed explicitly +type LegacyStatusProvider interface { + GetLegacyStatus(schema.GroupVersionResource, *appsdkapiserver.StatusREST) rest.Storage +} + type AuthorizerProvider interface { GetAuthorizer() authorizer.Authorizer } diff --git a/pkg/services/apiserver/appinstaller/server.go b/pkg/services/apiserver/appinstaller/server.go index 2e5d46b2b3d..ab688a46fb9 100644 --- a/pkg/services/apiserver/appinstaller/server.go +++ b/pkg/services/apiserver/appinstaller/server.go @@ -60,25 +60,41 @@ func (s *serverWrapper) InstallAPIGroup(apiGroupInfo *genericapiserver.APIGroupI continue } storage := s.configureStorage(gr, dualWriteSupported, restStorage) - if unifiedStorage, ok := storage.(grafanarest.Storage); ok && dualWriteSupported { - log.Debug("Configuring dual writer for storage", "resource", gr.String(), "version", v, "storagePath", storagePath) - dw, err := NewDualWriter( - s.ctx, - gr, - s.storageOpts, - legacyProvider.GetLegacyStorage(gr.WithVersion(v)), - unifiedStorage, - s.kvStore, - s.lock, - s.namespaceMapper, - s.dualWriteService, - s.dualWriterMetrics, - s.builderMetrics, - ) - if err != nil { - return err + if dualWriteSupported { + if unifiedStorage, ok := storage.(grafanarest.Storage); ok { + log.Debug("Configuring dual writer for storage", "resource", gr.String(), "version", v, "storagePath", storagePath) + storage, err = NewDualWriter( + s.ctx, + gr, + s.storageOpts, + legacyProvider.GetLegacyStorage(gr.WithVersion(v)), + unifiedStorage, + s.kvStore, + s.lock, + s.namespaceMapper, + s.dualWriteService, + s.dualWriterMetrics, + s.builderMetrics, + ) + if err != nil { + return err + } + } else if statusRest, ok := storage.(*appsdkapiserver.StatusREST); ok { + parentPath := strings.TrimSuffix(storagePath, "/status") + parentStore, ok := apiGroupInfo.VersionedResourcesStorageMap[v][parentPath] + if ok { + if _, isMode4or5 := parentStore.(*genericregistry.Store); !isMode4or5 { + // When legacy resources have status, the dual writing must be handled explicitly + if statusProvider, ok := s.installer.(LegacyStatusProvider); ok { + storage = statusProvider.GetLegacyStatus(gr.WithVersion(v), statusRest) + } else { + log.Warn("skipped registering status sub-resource that does not support dual writing", + "resource", gr.String(), "version", v, "storagePath", storagePath) + continue + } + } + } } - storage = dw } apiGroupInfo.VersionedResourcesStorageMap[v][storagePath] = storage } diff --git a/pkg/services/apiserver/builder/helper.go b/pkg/services/apiserver/builder/helper.go index 222637401ee..cfcf3540b43 100644 --- a/pkg/services/apiserver/builder/helper.go +++ b/pkg/services/apiserver/builder/helper.go @@ -58,6 +58,12 @@ var PathRewriters = []filters.PathRewriter{ return matches[1] + "/name" // connector requires a name }, }, + { + Pattern: regexp.MustCompile(`(/apis/query.grafana.app/v0alpha1/namespaces/.*/sqlschemas$)`), + ReplaceFunc: func(matches []string) string { + return matches[1] + "/name" // connector requires a name + }, + }, { Pattern: regexp.MustCompile(`(/apis/.*/v0alpha1/namespaces/.*/queryconvert$)`), ReplaceFunc: func(matches []string) string { diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go index 449a0ee0817..2d393236801 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/cloudmigration_test.go @@ -40,6 +40,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/models" ngalertstore "github.com/grafana/grafana/pkg/services/ngalert/store" ngalertfakes "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/quota/quotatest" @@ -790,7 +791,15 @@ func TestGetPlugins(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) - user := &user.SignedInUser{OrgID: 1} + user := &user.SignedInUser{ + OrgID: 1, + Permissions: map[int64]map[string][]string{ + 1: { + pluginaccesscontrol.ActionInstall: {pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, + pluginaccesscontrol.ActionWrite: {pluginaccesscontrol.ScopeProvider.GetResourceAllScope()}, + }, + }, + } s.pluginStore = pluginstore.NewFakePluginStore([]pluginstore.Plugin{ { @@ -1001,7 +1010,7 @@ func setUpServiceTest(t *testing.T, cfgOverrides ...configOverrides) cloudmigrat mockFolder, &pluginstore.FakePluginStore{}, &pluginsettings.FakePluginSettings{}, - actest.FakeAccessControl{ExpectedEvaluate: true}, + accessControl, fakeAccessControlService, kvstore.ProvideService(sqlStore), &libraryelementsfake.LibraryElementService{}, diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go index a279c09d9db..ae9a1ef52ed 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts.go @@ -9,9 +9,12 @@ import ( "github.com/prometheus/common/model" "github.com/grafana/grafana/pkg/components/simplejson" + ac "github.com/grafana/grafana/pkg/services/accesscontrol" ngalertapi "github.com/grafana/grafana/pkg/services/ngalert/api/compat" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/provisioning" + "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" ) @@ -79,6 +82,13 @@ type contactPoint struct { } func (s *Service) getContactPoints(ctx context.Context, signedInUser *user.SignedInUser) ([]contactPoint, error) { + userIsOrgAdmin := signedInUser.HasRole(org.RoleAdmin) + hasAccess, _ := s.accessControl.Evaluate(ctx, signedInUser, ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets, models.ScopeReceiversAll)) + if !userIsOrgAdmin && !hasAccess { + msg := "user '%s' is not allowed to read contact point secrets, missing 'alert.notifications.receivers.secrets:read' permission, which can be granted through the 'Admin' or 'Alerting > Full admin access' roles" + return nil, fmt.Errorf(msg, signedInUser.UserUID) + } + query := provisioning.ContactPointQuery{ OrgID: signedInUser.GetOrgID(), Decrypt: true, // needed to recreate the settings in the target instance. diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go index ec50629c24b..611b10c317b 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/snapshot_mgmt_alerts_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "net/http" "testing" "time" @@ -13,7 +12,6 @@ import ( "github.com/prometheus/alertmanager/pkg/labels" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" @@ -127,10 +125,7 @@ func TestGetContactPoints(t *testing.T) { contactPoints, err := s.getContactPoints(ctx, user) require.Nil(t, contactPoints) - - gfErr := errutil.Error{} - require.ErrorAs(t, err, &gfErr) - require.Equal(t, http.StatusForbidden, gfErr.Reason.Status().HTTPStatus()) + require.Contains(t, err.Error(), "alert.notifications.receivers.secrets:read") }) } diff --git a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go index 2d84b257a5a..6abb63ce54e 100644 --- a/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go +++ b/pkg/services/cloudmigration/cloudmigrationimpl/xorm_store_test.go @@ -822,7 +822,7 @@ func TestEncodeDecode(t *testing.T) { Private: grafanaPrivateKey[:], }, crypto.NewNacl(), - "", + t.TempDir(), ) require.NoError(t, err) diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 3d26169edd7..f59416924de 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -1638,12 +1638,14 @@ func (dr *DashboardServiceImpl) DeleteInFolders(ctx context.Context, orgID int64 defer span.End() // We need a list of dashboard uids inside the folder to delete related public dashboards - dashes, err := dr.dashboardStore.FindDashboards(ctx, &dashboards.FindPersistedDashboardsQuery{ + dashes, err := dr.searchDashboardsThroughK8s(ctx, &dashboards.FindPersistedDashboardsQuery{ SignedInUser: u, - FolderUIDs: folderUIDs, - OrgId: orgID, Type: searchstore.TypeDashboard, + Limit: 100000, + OrgId: orgID, + FolderUIDs: folderUIDs, }) + if err != nil { return folder.ErrInternal.Errorf("failed to fetch dashboards: %w", err) } @@ -1658,7 +1660,14 @@ func (dr *DashboardServiceImpl) DeleteInFolders(ctx context.Context, orgID int64 return err } - return dr.dashboardStore.DeleteDashboardsInFolders(ctx, &dashboards.DeleteDashboardsInFolderRequest{FolderUIDs: folderUIDs, OrgID: orgID}) + for _, dash := range dashes { + errDel := dr.DeleteDashboard(ctx, dash.ID, dash.UID, orgID) + if errDel != nil { + dr.log.Error("failed to delete dashboard inside folder", "dashboardUID", dash.UID, "folderUIDs", folderUIDs, "error", errDel) + } + } + + return err } func (dr *DashboardServiceImpl) Kind() string { return entity.StandardKindDashboard } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index a511dc87c92..8eeb788036e 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1873,10 +1873,10 @@ var ( { Name: "sharingDashboardImage", Description: "Enables image sharing functionality for dashboards", - Stage: FeatureStageExperimental, + Stage: FeatureStageGeneralAvailability, Owner: grafanaSharingSquad, - HideFromDocs: true, FrontendOnly: true, + Expression: "true", }, { Name: "preferLibraryPanelTitle", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 9fd5ebcc1ab..a28888c6867 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -242,7 +242,7 @@ alertEnrichmentMultiStep,experimental,@grafana/alerting-squad,false,false,false alertEnrichmentConditional,experimental,@grafana/alerting-squad,false,false,false alertingImportAlertmanagerAPI,experimental,@grafana/alerting-squad,false,false,false alertingImportAlertmanagerUI,experimental,@grafana/alerting-squad,false,false,false -sharingDashboardImage,experimental,@grafana/sharing-squad,false,false,true +sharingDashboardImage,GA,@grafana/sharing-squad,false,false,true preferLibraryPanelTitle,privatePreview,@grafana/dashboards-squad,false,false,false tabularNumbers,GA,@grafana/grafana-frontend-platform,false,false,false newInfluxDSConfigPageDesign,privatePreview,@grafana/partner-datasources,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index e996a0afa7a..aa347659401 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -3626,15 +3626,18 @@ { "metadata": { "name": "sharingDashboardImage", - "resourceVersion": "1753448760331", - "creationTimestamp": "2025-07-15T21:07:39Z" + "resourceVersion": "1761782859498", + "creationTimestamp": "2025-07-15T21:07:39Z", + "annotations": { + "grafana.app/updatedTimestamp": "2025-10-30 00:07:39.498343 +0000 UTC" + } }, "spec": { "description": "Enables image sharing functionality for dashboards", - "stage": "experimental", + "stage": "GA", "codeowner": "@grafana/sharing-squad", "frontend": true, - "hideFromDocs": true + "expression": "true" } }, { diff --git a/pkg/services/libraryelements/api.go b/pkg/services/libraryelements/api.go index 791cef618a9..312905cf0c8 100644 --- a/pkg/services/libraryelements/api.go +++ b/pkg/services/libraryelements/api.go @@ -87,7 +87,7 @@ func (l *LibraryElementService) createHandler(c *contextmodel.ReqContext) respon } } - element, err := l.createLibraryElement(c.Req.Context(), c.SignedInUser, cmd) + element, err := l.CreateElement(c.Req.Context(), c.SignedInUser, cmd) if err != nil { return l.toLibraryElementError(err, "Failed to create library element") } @@ -124,7 +124,7 @@ func (l *LibraryElementService) createHandler(c *contextmodel.ReqContext) respon // 404: notFoundError // 500: internalServerError func (l *LibraryElementService) deleteHandler(c *contextmodel.ReqContext) response.Response { - id, err := l.deleteLibraryElement(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":uid"]) + id, err := l.DeleteLibraryElement(c.Req.Context(), c.SignedInUser, web.Params(c.Req)[":uid"]) if err != nil { return l.toLibraryElementError(err, "Failed to delete library element") } @@ -252,7 +252,7 @@ func (l *LibraryElementService) patchHandler(c *contextmodel.ReqContext) respons } } - element, err := l.patchLibraryElement(c.Req.Context(), c.SignedInUser, cmd, web.Params(c.Req)[":uid"]) + element, err := l.PatchLibraryElement(c.Req.Context(), c.SignedInUser, cmd, web.Params(c.Req)[":uid"]) if err != nil { return l.toLibraryElementError(err, "Failed to update library element") } diff --git a/pkg/services/libraryelements/conversions.go b/pkg/services/libraryelements/conversions.go new file mode 100644 index 00000000000..465830d1697 --- /dev/null +++ b/pkg/services/libraryelements/conversions.go @@ -0,0 +1,59 @@ +package libraryelements + +import ( + "encoding/json" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/libraryelements/model" + "github.com/grafana/grafana/pkg/util" +) + +func ToCreateLibraryElementCommand(raw runtime.Object) (*model.CreateLibraryElementCommand, error) { + obj, err := utils.MetaAccessor(raw) + if err != nil { + return nil, err + } + folder := obj.GetFolder() + cmd := &model.CreateLibraryElementCommand{ + UID: obj.GetName(), + FolderUID: &folder, + Kind: 1, // the only kind... LibraryPanel + Name: obj.FindTitle("library panel"), + } + if cmd.UID == "" { + if obj.GetGenerateName() == "" { + return nil, fmt.Errorf("expecting either name or generateName property") + } + cmd.UID = obj.GetGenerateName() + util.GenerateShortUID() + } + cmd.Model, err = toRawMessage(raw) + return cmd, err +} + +func ToPatchLibraryElementCommand(raw runtime.Object) (*model.PatchLibraryElementCommand, error) { + obj, err := utils.MetaAccessor(raw) + if err != nil { + return nil, err + } + folder := obj.GetFolder() + cmd := &model.PatchLibraryElementCommand{ + UID: obj.GetName(), + FolderUID: &folder, + Kind: 1, // the only kind... LibraryPanel + Name: obj.FindTitle("library panel"), + } + cmd.Model, err = toRawMessage(raw) + return cmd, err +} + +func toRawMessage(raw runtime.Object) (json.RawMessage, error) { + switch obj := raw.(type) { + case *v0alpha1.LibraryPanel: + return json.Marshal(obj.Spec) + } + return nil, fmt.Errorf("unsupported library panel type: %T", raw) +} diff --git a/pkg/services/libraryelements/conversions_test.go b/pkg/services/libraryelements/conversions_test.go new file mode 100644 index 00000000000..2ae76fb9fa4 --- /dev/null +++ b/pkg/services/libraryelements/conversions_test.go @@ -0,0 +1,101 @@ +package libraryelements + +import ( + "encoding/json" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" + + data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + "github.com/grafana/grafana/pkg/services/libraryelements/model" +) + +func TestConversionsCommands(t *testing.T) { + cases := []struct { + name string + input runtime.Object + expectedCreate *model.CreateLibraryElementCommand + expectedPatch *model.PatchLibraryElementCommand + }{ + { + name: "basic conversion", + input: &v0alpha1.LibraryPanel{ + ObjectMeta: metav1.ObjectMeta{ + Name: "uid", + Annotations: map[string]string{ + utils.AnnoKeyFolder: "aaa", + }, + }, + Spec: v0alpha1.LibraryPanelSpec{ + Type: "timeseries", + PluginVersion: "1.2.3", + Title: "title", + Description: "descr", + Options: common.Unstructured{ + Object: map[string]any{ + "hello": "options", + }, + }, + FieldConfig: common.Unstructured{ + Object: map[string]any{ + "hello": "fieldConfig", + }, + }, + PanelTitle: "panel title", + GridPos: v0alpha1.GridPos{ + W: 1, H: 2, X: 3, Y: 4, + }, + Transparent: true, + Links: []common.Unstructured{{ + Object: map[string]any{ + "link1": "hello", + }, + }}, + Datasource: &data.DataSourceRef{ + UID: "uid", + Type: "ttt", + APIVersion: "v0alpha1", + }, + }, + }, + expectedCreate: &model.CreateLibraryElementCommand{ + FolderUID: ptr.To("aaa"), + UID: "uid", + Name: "title", + Kind: 1, + Model: json.RawMessage(`{"type":"timeseries","pluginVersion":"1.2.3","title":"title","panelTitle":"panel title","description":"descr","options":{"hello":"options"},"fieldConfig":{"hello":"fieldConfig"},"datasource":{"type":"ttt","uid":"uid","apiVersion":"v0alpha1"},"gridPos":{"w":1,"h":2,"x":3,"y":4},"transparent":true,"links":[{"link1":"hello"}]}`), + }, + expectedPatch: &model.PatchLibraryElementCommand{ + FolderUID: ptr.To("aaa"), + UID: "uid", + Name: "title", + Kind: 1, + Version: 0, + Model: json.RawMessage(`{"type":"timeseries","pluginVersion":"1.2.3","title":"title","panelTitle":"panel title","description":"descr","options":{"hello":"options"},"fieldConfig":{"hello":"fieldConfig"},"datasource":{"type":"ttt","uid":"uid","apiVersion":"v0alpha1"},"gridPos":{"w":1,"h":2,"x":3,"y":4},"transparent":true,"links":[{"link1":"hello"}]}`), + }, + }, + } + + for _, tt := range cases { + t.Run(tt.name, func(t *testing.T) { + create, err := ToCreateLibraryElementCommand(tt.input) + require.NoError(t, err) + if diff := cmp.Diff(tt.expectedCreate, create); diff != "" { + require.FailNowf(t, "Create mismatch (-want +got):%s", diff) + } + + patch, err := ToPatchLibraryElementCommand(tt.input) + require.NoError(t, err) + if diff := cmp.Diff(tt.expectedPatch, patch); diff != "" { + require.FailNowf(t, "Path mismatch (-want +got):%s", diff) + } + }) + } +} diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index d4923ffa20a..3a47b3095ce 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -110,7 +110,7 @@ func (l *LibraryElementService) GetLibraryElement(c context.Context, signedInUse } // createLibraryElement adds a library element. -func (l *LibraryElementService) createLibraryElement(c context.Context, signedInUser identity.Requester, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { +func (l *LibraryElementService) CreateElement(c context.Context, signedInUser identity.Requester, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { if err := l.requireSupportedElementKind(cmd.Kind); err != nil { return model.LibraryElementDTO{}, err } @@ -169,7 +169,7 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn err = l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { allowed, err := l.AccessControl.Evaluate(c, signedInUser, ac.EvalPermission(ActionLibraryPanelsCreate, dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID))) if !allowed { - return fmt.Errorf("insufficient permissions for creating library panel in folder with UID %s", folderUID) + return fmt.Errorf("insufficient permissions for creating library panel in folder with UID: '%s'", folderUID) } if err != nil { return err @@ -216,7 +216,7 @@ func (l *LibraryElementService) createLibraryElement(c context.Context, signedIn } // deleteLibraryElement deletes a library element. -func (l *LibraryElementService) deleteLibraryElement(c context.Context, signedInUser identity.Requester, uid string) (int64, error) { +func (l *LibraryElementService) DeleteLibraryElement(c context.Context, signedInUser identity.Requester, uid string) (int64, error) { var elementID int64 err := l.SQLStore.WithTransactionalDbSession(c, func(session *db.Session) error { element, err := l.GetLibraryElement(c, signedInUser, session, uid) @@ -578,7 +578,7 @@ func (l *LibraryElementService) handleFolderIDPatches(ctx context.Context, eleme } // patchLibraryElement updates a Library Element. -func (l *LibraryElementService) patchLibraryElement(c context.Context, signedInUser identity.Requester, cmd model.PatchLibraryElementCommand, uid string) (model.LibraryElementDTO, error) { +func (l *LibraryElementService) PatchLibraryElement(c context.Context, signedInUser identity.Requester, cmd model.PatchLibraryElementCommand, uid string) (model.LibraryElementDTO, error) { var dto model.LibraryElementDTO if err := l.requireSupportedElementKind(cmd.Kind); err != nil { return model.LibraryElementDTO{}, err diff --git a/pkg/services/libraryelements/fake/libraryelements_service.go b/pkg/services/libraryelements/fake/libraryelements_service.go index e8f0b3615d5..13f7aef322d 100644 --- a/pkg/services/libraryelements/fake/libraryelements_service.go +++ b/pkg/services/libraryelements/fake/libraryelements_service.go @@ -17,6 +17,16 @@ type LibraryElementService struct { idCounter int64 } +// DeleteLibraryElement implements libraryelements.Service. +func (l *LibraryElementService) DeleteLibraryElement(c context.Context, signedInUser identity.Requester, uid string) (int64, error) { + panic("unimplemented") +} + +// PatchLibraryElement implements libraryelements.Service. +func (l *LibraryElementService) PatchLibraryElement(c context.Context, signedInUser identity.Requester, cmd model.PatchLibraryElementCommand, uid string) (model.LibraryElementDTO, error) { + panic("unimplemented") +} + var _ libraryelements.Service = (*LibraryElementService)(nil) func (l *LibraryElementService) CreateElement(c context.Context, signedInUser identity.Requester, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { @@ -68,6 +78,11 @@ func (l *LibraryElementService) CreateElement(c context.Context, signedInUser id return dto, nil } +// PatchElement implements libraryelements.Service. +func (l *LibraryElementService) PatchElement(c context.Context, signedInUser identity.Requester, cmd model.PatchLibraryElementCommand, uid string) (model.LibraryElementDTO, error) { + panic("unimplemented") +} + func (l *LibraryElementService) GetElement(c context.Context, signedInUser identity.Requester, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { l.mx.RLock() defer l.mx.RUnlock() diff --git a/pkg/services/libraryelements/k8s_conversion_test.go b/pkg/services/libraryelements/k8s_conversion_test.go index 0319e0ae76a..689ef1f0c1d 100644 --- a/pkg/services/libraryelements/k8s_conversion_test.go +++ b/pkg/services/libraryelements/k8s_conversion_test.go @@ -2,13 +2,13 @@ package libraryelements import ( "context" - "encoding/json" "net/http/httptest" "testing" "time" "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" "github.com/grafana/grafana/pkg/api/dtos" @@ -23,7 +23,6 @@ import ( "github.com/grafana/grafana/pkg/services/user/usertest" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestUnstructuredToLegacyLibraryPanelDTO(t *testing.T) { @@ -155,26 +154,22 @@ func TestUnstructuredToLegacyLibraryPanelDTO(t *testing.T) { require.Equal(t, testUser.Login, result.Meta.UpdatedBy.Name) require.Equal(t, dtos.GetGravatarUrl(cfg, testUser.Email), result.Meta.UpdatedBy.AvatarUrl) - var modelMap map[string]interface{} - err = json.Unmarshal(result.Model, &modelMap) - require.NoError(t, err) - require.Equal(t, "testdata", modelMap["datasource"].(map[string]interface{})["type"]) - require.Equal(t, "test-datasource", modelMap["datasource"].(map[string]interface{})["uid"]) - require.Equal(t, "Test description", modelMap["description"]) - require.Equal(t, float64(123), modelMap["id"]) - require.Equal(t, "text", modelMap["type"]) - require.Equal(t, "Test Panel Title", modelMap["title"]) - require.Equal(t, "Test content", modelMap["options"].(map[string]interface{})["content"]) - require.Equal(t, true, modelMap["transparent"]) - require.Equal(t, "Test Library Panel", modelMap["libraryPanel"].(map[string]interface{})["name"]) - require.Equal(t, "test-panel-uid", modelMap["libraryPanel"].(map[string]interface{})["uid"]) - links := modelMap["links"].([]interface{}) - require.Len(t, links, 1) - require.Equal(t, "Test Link", links[0].(map[string]interface{})["title"]) - - targets := modelMap["targets"].([]interface{}) - require.Len(t, targets, 1) - require.Equal(t, "A", targets[0].(map[string]interface{})["refId"]) + // fmt.Printf("%s\n", result.Model) + require.JSONEq(t, `{ + "datasource": { "type": "testdata", "uid": "test-datasource" }, + "description": "Test description", + "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" } } }, + "gridPos": { "w": 12, "h": 8, "x": 0, "y": 0 }, + "id": 123, + "libraryPanel": { "name": "Test Library Panel", "uid": "test-panel-uid" }, + "links": [{ "title": "Test Link", "url": "https://example.com" }], + "options": { "content": "Test content" }, + "pluginVersion": "1.0.0", + "targets": [{ "refId": "A", "expr": "test_query" }], + "title": "Test Panel Title", + "transparent": true, + "type": "text" + }`, string(result.Model)) dashboardsSvc.AssertExpectations(t) } diff --git a/pkg/services/libraryelements/libraryelements.go b/pkg/services/libraryelements/libraryelements.go index fe31dfcfc70..540e54fc817 100644 --- a/pkg/services/libraryelements/libraryelements.go +++ b/pkg/services/libraryelements/libraryelements.go @@ -40,6 +40,8 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.Rout // Service is a service for operating on library elements. type Service interface { CreateElement(c context.Context, signedInUser identity.Requester, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) + PatchLibraryElement(c context.Context, signedInUser identity.Requester, cmd model.PatchLibraryElementCommand, uid string) (model.LibraryElementDTO, error) + DeleteLibraryElement(c context.Context, signedInUser identity.Requester, uid string) (int64, error) GetElement(c context.Context, signedInUser identity.Requester, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) GetElementsForDashboard(c context.Context, dashboardID int64) (map[string]model.LibraryElementDTO, error) ConnectElementsToDashboard(c context.Context, signedInUser identity.Requester, elementUIDs []string, dashboardID int64) error @@ -63,11 +65,6 @@ type LibraryElementService struct { var _ Service = (*LibraryElementService)(nil) -// CreateElement creates a Library Element. -func (l *LibraryElementService) CreateElement(c context.Context, signedInUser identity.Requester, cmd model.CreateLibraryElementCommand) (model.LibraryElementDTO, error) { - return l.createLibraryElement(c, signedInUser, cmd) -} - // GetElement gets an element from a UID. func (l *LibraryElementService) GetElement(c context.Context, signedInUser identity.Requester, cmd model.GetLibraryElementCommand) (model.LibraryElementDTO, error) { return l.getLibraryElementByUid(c, signedInUser, cmd) diff --git a/pkg/services/libraryelements/libraryelements_get_test.go b/pkg/services/libraryelements/libraryelements_get_test.go index c85c22ee676..1d9912195dd 100644 --- a/pkg/services/libraryelements/libraryelements_get_test.go +++ b/pkg/services/libraryelements/libraryelements_get_test.go @@ -103,7 +103,7 @@ func TestIntegration_GetLibraryElement(t *testing.T) { newFolder := createFolder(t, sc, "NewFolder", sc.folderSvc) sc.reqContext.Permissions[sc.reqContext.OrgID][dashboards.ActionFoldersRead] = []string{dashboards.ScopeFoldersAll} sc.reqContext.Permissions[sc.reqContext.OrgID][dashboards.ActionFoldersDelete] = []string{dashboards.ScopeFoldersAll} - result, err := sc.service.createLibraryElement(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, model.CreateLibraryElementCommand{ + result, err := sc.service.CreateElement(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, model.CreateLibraryElementCommand{ FolderID: newFolder.ID, // nolint:staticcheck FolderUID: &newFolder.UID, Name: "Testing Library Panel With Deleted Folder", diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index e782c20ac03..9340c19409f 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -150,7 +150,7 @@ func TestIntegration_GetLibraryPanelConnections(t *testing.T) { newFolder := createFolder(t, sc, "NewFolder", sc.folderSvc) sc.reqContext.Permissions[sc.reqContext.OrgID][dashboards.ActionFoldersRead] = []string{dashboards.ScopeFoldersAll} sc.reqContext.Permissions[sc.reqContext.OrgID][dashboards.ActionFoldersDelete] = []string{dashboards.ScopeFoldersAll} - _, err = sc.service.createLibraryElement(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, model.CreateLibraryElementCommand{ + _, err = sc.service.CreateElement(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, model.CreateLibraryElementCommand{ FolderID: newFolder.ID, // nolint:staticcheck FolderUID: &newFolder.UID, Name: "Testing Library Panel With Deleted Folder", diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index 50ccfcb4cdd..8206a45d5ea 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -21,6 +21,7 @@ const ( WeightAlerting WeightAlertsAndIncidents WeightAIAndML + WeightAdaptiveTelemetry WeightTestingAndSynthetics WeightObservability WeightCloudServiceProviders diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index 7a2f15206dc..e3fea6da25e 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -344,7 +344,7 @@ func (s *ServiceImpl) addPluginToSection(c *contextmodel.ReqContext, treeRoot *n Id: navtree.NavIDAdaptiveTelemetry, SubTitle: "Reduce noise, cut costs, and accelerate troubleshooting by intelligently ingesting only the telemetry data that matters most.", Icon: "adaptive-telemetry", - SortWeight: navtree.WeightAIAndML + 1, // Place under "AI & Machine Learning" + SortWeight: navtree.WeightAdaptiveTelemetry, Children: sectionChildren, Url: "adaptive-telemetry", // Use the icon URL from the first "Adaptive Telemetry" plugin in the list (they will all be the same) diff --git a/pkg/services/ngalert/models/receivers.go b/pkg/services/ngalert/models/receivers.go index 1130711da90..1601ddc94e5 100644 --- a/pkg/services/ngalert/models/receivers.go +++ b/pkg/services/ngalert/models/receivers.go @@ -130,6 +130,9 @@ func (r *Receiver) WithExistingSecureFields(existing *Receiver, integrationSecur // Validate validates all integration settings, ensuring that the integrations are correctly configured. func (r *Receiver) Validate(decryptFn DecryptFn) error { var errs []error + if r.Name == "" { + errs = append(errs, fmt.Errorf("name should not be an empty string")) + } for _, integration := range r.Integrations { if err := integration.Validate(decryptFn); err != nil { errs = append(errs, err) diff --git a/pkg/services/ngalert/notifier/receiver_svc_test.go b/pkg/services/ngalert/notifier/receiver_svc_test.go index 85e7733f4bd..221f3e805d0 100644 --- a/pkg/services/ngalert/notifier/receiver_svc_test.go +++ b/pkg/services/ngalert/notifier/receiver_svc_test.go @@ -476,6 +476,12 @@ func TestReceiverService_Create(t *testing.T) { }, }, }, + { + name: "receiver with empty name fails", + user: writer, + receiver: models.CopyReceiverWith(baseReceiver, models.ReceiverMuts.WithName("")), + expectedErr: legacy_storage.ErrReceiverInvalid, + }, } { t.Run(tc.name, func(t *testing.T) { sut := createReceiverServiceSut(t, &secretsService) @@ -901,6 +907,16 @@ func TestReceiverService_UpdateReceiverName(t *testing.T) { assert.NotNil(t, ruleStore.Calls[0].Args[4]) assert.Falsef(t, ruleStore.Calls[0].Args[5].(bool), "dryrun expected to be false") }) + + t.Run("returns ErrReceiverInvalid if empty name", func(t *testing.T) { + ruleStore := &fakeAlertRuleNotificationStore{} + sut := createReceiverServiceSut(t, &secretsService) + sut.ruleNotificationsStore = ruleStore + baseReceiver.Name = "" + + _, err := sut.UpdateReceiver(context.Background(), &baseReceiver, nil, writer.GetOrgID(), writer) + require.ErrorIs(t, err, legacy_storage.ErrReceiverInvalid) + }) } func TestReceiverServiceAC_Read(t *testing.T) { diff --git a/pkg/services/ngalert/provisioning/contactpoints.go b/pkg/services/ngalert/provisioning/contactpoints.go index d25aa4e959f..14abb8ceaa5 100644 --- a/pkg/services/ngalert/provisioning/contactpoints.go +++ b/pkg/services/ngalert/provisioning/contactpoints.go @@ -309,13 +309,14 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in return err } - oldReceiverName, fullRemoval, newReceiverCreated := stitchReceiver(revision.Config, mergedReceiver) - if oldReceiverName == "" { - return fmt.Errorf("contact point with uid '%s' not found", mergedReceiver.UID) + oldReceiverNameRef, fullRemoval, newReceiverCreated := stitchReceiver(revision.Config, mergedReceiver) + if oldReceiverNameRef == nil { + return fmt.Errorf("%w: contact point with uid '%s' not found", ErrNotFound, mergedReceiver.UID) } + oldReceiverName := *oldReceiverNameRef err = ecp.xact.InTransaction(ctx, func(ctx context.Context) error { - if mergedReceiver.Name != oldReceiverName { + if mergedReceiver.Name != oldReceiverName && oldReceiverName != "" { if newReceiverCreated { // Copy receiver permissions permissionsUpdated, err := ecp.resourcePermissions.CopyPermissions(ctx, orgID, nil, legacy_storage.NameToUid(oldReceiverName), legacy_storage.NameToUid(mergedReceiver.Name)) @@ -373,12 +374,12 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in } } } - if fullRemoval && revision.ReceiverNameUsedByRoutes(name) { + if fullRemoval && name != "" && revision.ReceiverNameUsedByRoutes(name) { return ErrContactPointReferenced.Errorf("") } return ecp.xact.InTransaction(ctx, func(ctx context.Context) error { - if fullRemoval { + if fullRemoval && name != "" { used, err := ecp.notificationSettingsStore.ListNotificationSettings(ctx, models.ListNotificationSettingsQuery{OrgID: orgID, ReceiverName: name}) if err != nil { return fmt.Errorf("failed to query alert rules for reference to the contact point '%s': %w", name, err) @@ -443,7 +444,7 @@ func (ecp *ContactPointService) encryptValue(value string) (string, error) { // stitchReceiver modifies a receiver, target, in an alertmanager configStore. It modifies the given configStore in-place. // Returns true if the configStore was altered in any way, and false otherwise. // If integration was moved to another group and it was the last in the previous group, the second parameter contains the name of the old group that is gone -func stitchReceiver(cfg *apimodels.PostableUserConfig, target *apimodels.PostableGrafanaReceiver) (oldReceiverName string, fullRemoval bool, newReceiverCreated bool) { +func stitchReceiver(cfg *apimodels.PostableUserConfig, target *apimodels.PostableGrafanaReceiver) (oldReceiverName *string, fullRemoval bool, newReceiverCreated bool) { // Algorithm to fix up receivers. Receivers are very complex and depend heavily on internal consistency. // All receivers in a given receiver group have the same name. We must maintain this across renames. groupLoop: @@ -451,7 +452,8 @@ groupLoop: // Does the current group contain the grafana receiver we're interested in? for i, grafanaReceiver := range receiverGroup.GrafanaManagedReceivers { if grafanaReceiver.UID == target.UID { - oldReceiverName = receiverGroup.Name + name := receiverGroup.Name + oldReceiverName = &name // If it's a basic field change, simply replace it. Done! // // NOTE: @@ -519,6 +521,9 @@ groupLoop: } func ValidateContactPoint(ctx context.Context, e *apimodels.EmbeddedContactPoint, decryptFunc alertingNotify.GetDecryptedValueFn) error { + if e.Name == "" { + return errors.New("name is required") + } iType, err := alertingNotify.IntegrationTypeFromString(e.Type) if err != nil { return err diff --git a/pkg/services/ngalert/provisioning/contactpoints_test.go b/pkg/services/ngalert/provisioning/contactpoints_test.go index 34535c1a504..ddddadb1104 100644 --- a/pkg/services/ngalert/provisioning/contactpoints_test.go +++ b/pkg/services/ngalert/provisioning/contactpoints_test.go @@ -114,16 +114,6 @@ func TestIntegrationContactPointService(t *testing.T) { require.Equal(t, customUID, cps[0].UID) }) - t.Run("it's not possible to use invalid UID", func(t *testing.T) { - customUID := strings.Repeat("1", util.MaxUIDLength+1) - sut := createContactPointServiceSut(t, secretsService) - newCp := createTestContactPoint() - newCp.UID = customUID - - _, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) - require.ErrorIs(t, err, ErrValidation) - }) - t.Run("it's not possible to use the same uid twice", func(t *testing.T) { customUID := "1337" sut := createContactPointServiceSut(t, secretsService) @@ -137,14 +127,40 @@ func TestIntegrationContactPointService(t *testing.T) { require.Error(t, err) }) - t.Run("create rejects contact points that fail validation", func(t *testing.T) { + t.Run("create rejects invalid contact points", func(t *testing.T) { sut := createContactPointServiceSut(t, secretsService) - newCp := createTestContactPoint() - newCp.Type = "" + testCases := []struct { + name string + cp func(*definitions.EmbeddedContactPoint) + }{ + { + name: "empty type", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Type = "" + }, + }, + { + name: "empty name", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Name = "" + }, + }, + { + name: "invalid UID", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.UID = strings.Repeat("1", util.MaxUIDLength+1) + }, + }, + } - _, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) - - require.ErrorIs(t, err, ErrValidation) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + newCp := createTestContactPoint() + tc.cp(&newCp) + _, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) + require.ErrorIs(t, err, ErrValidation) + }) + } }) t.Run("create accepts contact point with type in different cases", func(t *testing.T) { @@ -162,40 +178,57 @@ func TestIntegrationContactPointService(t *testing.T) { assert.EqualValues(t, slack.Type, got[0].Type) }) - t.Run("update rejects contact points with no settings", func(t *testing.T) { + t.Run("update rejects invalid contact points", func(t *testing.T) { + testCases := []struct { + name string + cp func(*definitions.EmbeddedContactPoint) + }{ + { + name: "empty type", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Type = "" + }, + }, + { + name: "empty name", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Name = "" + }, + }, + { + name: "nil settings", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Settings = nil + }, + }, + { + name: "invalid settings after merge", + cp: func(cp *definitions.EmbeddedContactPoint) { + cp.Settings, _ = simplejson.NewJson([]byte(`{}`)) + }, + }, + } + sut := createContactPointServiceSut(t, secretsService) newCp := createTestContactPoint() newCp, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) require.NoError(t, err) - newCp.Settings = nil - err = sut.UpdateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI) - - require.ErrorIs(t, err, ErrValidation) - }) - - t.Run("update rejects contact points with no type", func(t *testing.T) { - sut := createContactPointServiceSut(t, secretsService) - newCp := createTestContactPoint() - newCp, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) - require.NoError(t, err) - newCp.Type = "" - - err = sut.UpdateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI) - - require.ErrorIs(t, err, ErrValidation) - }) - - t.Run("update rejects contact points which fail validation after merging", func(t *testing.T) { - sut := createContactPointServiceSut(t, secretsService) - newCp := createTestContactPoint() - newCp, err := sut.CreateContactPoint(context.Background(), 1, redactedUser, newCp, models.ProvenanceAPI) - require.NoError(t, err) - newCp.Settings, _ = simplejson.NewJson([]byte(`{}`)) - - err = sut.UpdateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI) - - require.ErrorIs(t, err, ErrValidation) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cp := definitions.EmbeddedContactPoint{ + UID: newCp.UID, + Name: newCp.Name, + Type: newCp.Type, + Settings: newCp.Settings.DeepCopy(), + DisableResolveMessage: newCp.DisableResolveMessage, + Provenance: newCp.Provenance, + } + tc.cp(&cp) + err = sut.UpdateContactPoint(context.Background(), 1, cp, models.ProvenanceAPI) + require.ErrorIs(t, err, ErrValidation) + }) + } }) t.Run("update accepts contact points with type in another case", func(t *testing.T) { @@ -592,7 +625,7 @@ func TestStitchReceivers(t *testing.T) { initial *definitions.PostableUserConfig new *definitions.PostableGrafanaReceiver expCfg definitions.PostableApiAlertingConfig - expOldReceiver string + expOldReceiver *string expCreatedReceiver bool expFullRemoval bool } @@ -603,7 +636,7 @@ func TestStitchReceivers(t *testing.T) { new: &definitions.PostableGrafanaReceiver{ UID: "does not exist", }, - expOldReceiver: "", + expOldReceiver: nil, expCfg: createTestConfigWithReceivers().AlertmanagerConfig, }, { @@ -613,7 +646,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-2", Type: "teams", }, - expOldReceiver: "receiver-2", + expOldReceiver: util.Pointer("receiver-2"), expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ Route: &definitions.Route{ @@ -674,7 +707,7 @@ func TestStitchReceivers(t *testing.T) { Name: "new-receiver", Type: "slack", }, - expOldReceiver: "receiver-1", + expOldReceiver: util.Pointer("receiver-1"), expCreatedReceiver: true, expFullRemoval: true, expCfg: definitions.PostableApiAlertingConfig{ @@ -737,7 +770,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-1", Type: "slack", }, - expOldReceiver: "receiver-2", + expOldReceiver: util.Pointer("receiver-2"), expCreatedReceiver: false, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -858,7 +891,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-2", Type: "slack", }, - expOldReceiver: "receiver-1", + expOldReceiver: util.Pointer("receiver-1"), expCreatedReceiver: false, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -1002,7 +1035,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-4", Type: "slack", }, - expOldReceiver: "receiver-1", + expOldReceiver: util.Pointer("receiver-1"), expCreatedReceiver: false, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -1087,7 +1120,7 @@ func TestStitchReceivers(t *testing.T) { Name: "brand-new-group", Type: "opsgenie", }, - expOldReceiver: "receiver-2", + expOldReceiver: util.Pointer("receiver-2"), expCreatedReceiver: true, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -1159,7 +1192,7 @@ func TestStitchReceivers(t *testing.T) { Name: "brand-new-group", Type: "opsgenie", }, - expOldReceiver: "receiver-2", // Not the inconsistent receiver-3? + expOldReceiver: util.Pointer("receiver-2"), // Not the inconsistent receiver-3? expCreatedReceiver: true, expCfg: definitions.PostableApiAlertingConfig{ Config: definitions.Config{ @@ -1282,7 +1315,7 @@ func TestStitchReceivers(t *testing.T) { Name: "receiver-1", Type: "slack", }, - expOldReceiver: "receiver-2", + expOldReceiver: util.Pointer("receiver-2"), expCreatedReceiver: false, expFullRemoval: true, expCfg: definitions.PostableApiAlertingConfig{ @@ -1337,7 +1370,7 @@ func TestStitchReceivers(t *testing.T) { } renamedReceiver, fullRemoval, createdReceiver := stitchReceiver(cfg, c.new) - assert.Equalf(t, c.expOldReceiver, renamedReceiver, "expected old receiver to be %s, got %s", c.expOldReceiver, renamedReceiver) + assert.EqualValuesf(t, c.expOldReceiver, renamedReceiver, "expected old receiver to be %v, got %v", c.expOldReceiver, renamedReceiver) assert.Equalf(t, c.expFullRemoval, fullRemoval, "expected full removal to be %t, got %t", c.expFullRemoval, fullRemoval) assert.Equalf(t, c.expCreatedReceiver, createdReceiver, "expected created receiver to be %t, got %t", c.expCreatedReceiver, createdReceiver) require.Equal(t, c.expCfg, cfg.AlertmanagerConfig) diff --git a/pkg/services/pluginsintegration/installsync/syncer.go b/pkg/services/pluginsintegration/installsync/syncer.go index ac6d6ba3d32..1f056a6e85b 100644 --- a/pkg/services/pluginsintegration/installsync/syncer.go +++ b/pkg/services/pluginsintegration/installsync/syncer.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/grafana/grafana-app-sdk/logging" "github.com/grafana/grafana-app-sdk/resource" "github.com/grafana/grafana/apps/plugins/pkg/app/install" @@ -90,6 +91,11 @@ func (s *syncer) Sync(ctx context.Context, source install.Source, installedPlugi return nil } + if !s.featureToggles.IsEnabled(ctx, featuremgmt.FlagPluginStoreServiceLoading) { + logging.DefaultLogger.Warn("pluginInstallAPISync is enabled, but pluginStoreServiceLoading is disabled. skipping plugin sync.") + return nil + } + if len(installedPlugins) == 0 { return nil } diff --git a/pkg/services/pluginsintegration/installsync/syncer_test.go b/pkg/services/pluginsintegration/installsync/syncer_test.go index a1ca40638b0..e8894436083 100644 --- a/pkg/services/pluginsintegration/installsync/syncer_test.go +++ b/pkg/services/pluginsintegration/installsync/syncer_test.go @@ -20,234 +20,68 @@ import ( "github.com/grafana/grafana/pkg/services/org/orgtest" ) -// Test helpers to avoid import cycles -type fakeServerLock struct { - lockFunc func(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error -} - -func (f *fakeServerLock) LockExecuteAndRelease(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error { - if f.lockFunc != nil { - return f.lockFunc(ctx, actionName, maxInterval, fn) - } - fn(ctx) - return nil -} - -type fakePluginInstallClient struct { - listAllFunc func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) - getFunc func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.PluginInstall, error) - createFunc func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) - updateFunc func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.UpdateOptions) (*pluginsv0alpha1.PluginInstall, error) - deleteFunc func(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error -} - -func (f *fakePluginInstallClient) Get(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.PluginInstall, error) { - if f.getFunc != nil { - return f.getFunc(ctx, identifier) - } - // Return a proper k8s NotFound error - return nil, errorsK8s.NewNotFound(schema.GroupResource{ - Group: pluginsv0alpha1.APIGroup, - Resource: "plugininstalls", - }, identifier.Name) -} - -func (f *fakePluginInstallClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { - if f.listAllFunc != nil { - return f.listAllFunc(ctx, namespace, opts) - } - return &pluginsv0alpha1.PluginInstallList{}, nil -} - -func (f *fakePluginInstallClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { - return f.ListAll(ctx, namespace, opts) -} - -func (f *fakePluginInstallClient) Create(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) { - if f.createFunc != nil { - return f.createFunc(ctx, obj, opts) - } - return obj, nil -} - -func (f *fakePluginInstallClient) Update(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.UpdateOptions) (*pluginsv0alpha1.PluginInstall, error) { - if f.updateFunc != nil { - return f.updateFunc(ctx, obj, opts) - } - return obj, nil -} - -func (f *fakePluginInstallClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus pluginsv0alpha1.PluginInstallStatus, opts resource.UpdateOptions) (*pluginsv0alpha1.PluginInstall, error) { - return nil, nil -} - -func (f *fakePluginInstallClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*pluginsv0alpha1.PluginInstall, error) { - return nil, nil -} - -func (f *fakePluginInstallClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { - if f.deleteFunc != nil { - return f.deleteFunc(ctx, identifier, opts) - } - return nil -} - -type fakeClientGenerator struct { - client *fakePluginInstallClient -} - -func (f *fakeClientGenerator) ClientFor(kind resource.Kind) (resource.Client, error) { - return &fakeResourceClient{client: f.client}, nil -} - -type fakeResourceClient struct { - client *fakePluginInstallClient -} - -func (f *fakeResourceClient) Get(ctx context.Context, identifier resource.Identifier) (resource.Object, error) { - return f.client.Get(ctx, identifier) -} - -func (f *fakeResourceClient) GetInto(ctx context.Context, identifier resource.Identifier, into resource.Object) error { - obj, err := f.client.Get(ctx, identifier) - if err != nil { - return err - } - // Copy the object data into the provided 'into' object - if target, ok := into.(*pluginsv0alpha1.PluginInstall); ok { - *target = *obj - } - return nil -} - -func (f *fakeResourceClient) List(ctx context.Context, namespace string, options resource.ListOptions) (resource.ListObject, error) { - return f.client.ListAll(ctx, namespace, options) -} - -func (f *fakeResourceClient) ListInto(ctx context.Context, namespace string, options resource.ListOptions, into resource.ListObject) error { - list, err := f.client.ListAll(ctx, namespace, options) - if err != nil { - return err - } - // Copy the list data into the provided 'into' object - if target, ok := into.(*pluginsv0alpha1.PluginInstallList); ok { - *target = *list - } - return nil -} - -func (f *fakeResourceClient) Create(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions) (resource.Object, error) { - plugin := obj.(*pluginsv0alpha1.PluginInstall) - return f.client.Create(ctx, plugin, options) -} - -func (f *fakeResourceClient) CreateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions, into resource.Object) error { - created, err := f.Create(ctx, identifier, obj, options) - if err != nil { - return err - } - // Copy the created object data into the provided 'into' object - if plugin, ok := created.(*pluginsv0alpha1.PluginInstall); ok { - if target, ok := into.(*pluginsv0alpha1.PluginInstall); ok { - *target = *plugin - } - } - return nil -} - -func (f *fakeResourceClient) Update(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions) (resource.Object, error) { - plugin := obj.(*pluginsv0alpha1.PluginInstall) - return f.client.Update(ctx, plugin, options) -} - -func (f *fakeResourceClient) UpdateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions, into resource.Object) error { - updated, err := f.Update(ctx, identifier, obj, options) - if err != nil { - return err - } - // Copy the updated object data into the provided 'into' object - if plugin, ok := updated.(*pluginsv0alpha1.PluginInstall); ok { - if target, ok := into.(*pluginsv0alpha1.PluginInstall); ok { - *target = *plugin - } - } - return nil -} - -func (f *fakeResourceClient) Patch(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions) (resource.Object, error) { - return f.client.Patch(ctx, identifier, patch, options) -} - -func (f *fakeResourceClient) PatchInto(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions, into resource.Object) error { - patched, err := f.Patch(ctx, identifier, patch, options) - if err != nil { - return err - } - // Copy the patched object data into the provided 'into' object - if plugin, ok := patched.(*pluginsv0alpha1.PluginInstall); ok { - if target, ok := into.(*pluginsv0alpha1.PluginInstall); ok { - *target = *plugin - } - } - return nil -} - -func (f *fakeResourceClient) Delete(ctx context.Context, identifier resource.Identifier, options resource.DeleteOptions) error { - return f.client.Delete(ctx, identifier, options) -} - -func (f *fakeResourceClient) SubresourceRequest(ctx context.Context, identifier resource.Identifier, req resource.CustomRouteRequestOptions) ([]byte, error) { - return []byte{}, nil -} - -func (f *fakeResourceClient) Watch(ctx context.Context, namespace string, options resource.WatchOptions) (resource.WatchResponse, error) { - return &fakeWatchResponse{}, nil -} - -type fakeWatchResponse struct{} - -func (f *fakeWatchResponse) Stop() {} - -func (f *fakeWatchResponse) WatchEvents() <-chan resource.WatchEvent { - ch := make(chan resource.WatchEvent) - close(ch) - return ch -} - func TestSyncer_Sync(t *testing.T) { tests := []struct { - name string - featureToggleEnabled bool - orgs []*org.OrgDTO - orgServiceError error - serverLockError error - expectedError error - expectSyncCalls int + name string + pluginInstallAPISyncEnabled bool + pluginStoreServiceEnabled bool + installedPlugins []*plugins.Plugin + orgs []*org.OrgDTO + orgServiceError error + serverLockError error + expectedError error + expectSyncCalls int }{ { - name: "feature toggle disabled", - featureToggleEnabled: false, - orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, - expectedError: nil, - expectSyncCalls: 0, + name: "plugin install API sync feature toggle disabled", + pluginInstallAPISyncEnabled: false, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + expectedError: nil, + expectSyncCalls: 0, }, { - name: "feature toggle enabled, no orgs", - featureToggleEnabled: true, - orgs: []*org.OrgDTO{}, - expectedError: nil, - expectSyncCalls: 0, + name: "plugin store service feature toggle disabled", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: false, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + expectedError: nil, + expectSyncCalls: 0, }, { - name: "feature toggle enabled, single org", - featureToggleEnabled: true, - orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, - expectedError: nil, - expectSyncCalls: 1, + name: "both feature toggles enabled, no orgs", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{}, + expectedError: nil, + expectSyncCalls: 0, }, { - name: "feature toggle enabled, multiple orgs", - featureToggleEnabled: true, + name: "both feature toggles enabled, empty installed plugins", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + expectedError: nil, + expectSyncCalls: 0, + }, + { + name: "both feature toggles enabled, single org", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + expectedError: nil, + expectSyncCalls: 1, + }, + { + name: "both feature toggles enabled, multiple orgs", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, orgs: []*org.OrgDTO{ {ID: 1, Name: "Org 1"}, {ID: 2, Name: "Org 2"}, @@ -257,20 +91,24 @@ func TestSyncer_Sync(t *testing.T) { expectSyncCalls: 3, }, { - name: "org service error", - featureToggleEnabled: true, - orgs: nil, - orgServiceError: errors.New("org service error"), - expectedError: errors.New("org service error"), - expectSyncCalls: 0, + name: "org service error", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: nil, + orgServiceError: errors.New("org service error"), + expectedError: errors.New("org service error"), + expectSyncCalls: 0, }, { - name: "server lock error", - featureToggleEnabled: true, - orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, - serverLockError: errors.New("lock error"), - expectedError: errors.New("lock error"), - expectSyncCalls: 0, + name: "server lock error", + pluginInstallAPISyncEnabled: true, + pluginStoreServiceEnabled: true, + installedPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}}, + orgs: []*org.OrgDTO{{ID: 1, Name: "Org 1"}}, + serverLockError: errors.New("lock error"), + expectedError: errors.New("lock error"), + expectSyncCalls: 0, }, } @@ -280,7 +118,8 @@ func TestSyncer_Sync(t *testing.T) { // Setup feature toggles ft := featuremgmt.NewMockFeatureToggles(t) - ft.EXPECT().IsEnabled(ctx, featuremgmt.FlagPluginInstallAPISync).Return(tt.featureToggleEnabled).Maybe() + ft.EXPECT().IsEnabled(ctx, featuremgmt.FlagPluginInstallAPISync).Return(tt.pluginInstallAPISyncEnabled).Maybe() + ft.EXPECT().IsEnabled(ctx, featuremgmt.FlagPluginStoreServiceLoading).Return(tt.pluginStoreServiceEnabled).Maybe() // Setup org service orgService := orgtest.NewOrgServiceFake() @@ -298,12 +137,12 @@ func TestSyncer_Sync(t *testing.T) { // Setup fake client and registrar syncCalls := 0 fakeClient := &fakePluginInstallClient{ - createFunc: func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) { + createFunc: func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) { syncCalls++ return obj, nil }, - listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { - return &pluginsv0alpha1.PluginInstallList{}, nil + listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { + return &pluginsv0alpha1.PluginList{}, nil }, } clientGen := &fakeClientGenerator{client: fakeClient} @@ -320,10 +159,7 @@ func TestSyncer_Sync(t *testing.T) { ) // Execute - installedPlugins := []*plugins.Plugin{ - {JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}}, - } - err := s.Sync(ctx, install.SourcePluginStore, installedPlugins) + err := s.Sync(ctx, install.SourcePluginStore, tt.installedPlugins) // Verify if tt.expectedError != nil { @@ -342,7 +178,7 @@ func TestSyncer_syncNamespace(t *testing.T) { tests := []struct { name string installedPlugins []*plugins.Plugin - apiPlugins []pluginsv0alpha1.PluginInstall + apiPlugins []pluginsv0alpha1.Plugin clientListError error expectedError error expectedRegCalls int @@ -353,7 +189,7 @@ func TestSyncer_syncNamespace(t *testing.T) { { name: "no installed plugins, no API plugins", installedPlugins: []*plugins.Plugin{}, - apiPlugins: []pluginsv0alpha1.PluginInstall{}, + apiPlugins: []pluginsv0alpha1.Plugin{}, expectedError: nil, expectedRegCalls: 0, expectedUnregCalls: 0, @@ -364,7 +200,7 @@ func TestSyncer_syncNamespace(t *testing.T) { {JSONData: plugins.JSONData{ID: "plugin-1", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}, {JSONData: plugins.JSONData{ID: "plugin-2", Info: plugins.Info{Version: "2.0.0"}}, Class: plugins.ClassExternal}, }, - apiPlugins: []pluginsv0alpha1.PluginInstall{}, + apiPlugins: []pluginsv0alpha1.Plugin{}, expectedError: nil, expectedRegCalls: 2, expectedUnregCalls: 0, @@ -373,7 +209,7 @@ func TestSyncer_syncNamespace(t *testing.T) { { name: "API plugins only", installedPlugins: []*plugins.Plugin{}, - apiPlugins: []pluginsv0alpha1.PluginInstall{ + apiPlugins: []pluginsv0alpha1.Plugin{ { ObjectMeta: metav1.ObjectMeta{ Name: "plugin-1", @@ -381,7 +217,7 @@ func TestSyncer_syncNamespace(t *testing.T) { install.PluginInstallSourceAnnotation: install.SourcePluginStore, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{Id: "plugin-1"}, + Spec: pluginsv0alpha1.PluginSpec{Id: "plugin-1"}, }, { ObjectMeta: metav1.ObjectMeta{ @@ -390,7 +226,7 @@ func TestSyncer_syncNamespace(t *testing.T) { install.PluginInstallSourceAnnotation: install.SourcePluginStore, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{Id: "plugin-2"}, + Spec: pluginsv0alpha1.PluginSpec{Id: "plugin-2"}, }, }, expectedError: nil, @@ -405,7 +241,7 @@ func TestSyncer_syncNamespace(t *testing.T) { {JSONData: plugins.JSONData{ID: "plugin-2", Info: plugins.Info{Version: "2.0.0"}}, Class: plugins.ClassExternal}, {JSONData: plugins.JSONData{ID: "plugin-3", Info: plugins.Info{Version: "3.0.0"}}, Class: plugins.ClassExternal}, }, - apiPlugins: []pluginsv0alpha1.PluginInstall{ + apiPlugins: []pluginsv0alpha1.Plugin{ { ObjectMeta: metav1.ObjectMeta{ Name: "plugin-2", @@ -413,7 +249,7 @@ func TestSyncer_syncNamespace(t *testing.T) { install.PluginInstallSourceAnnotation: install.SourcePluginStore, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{Id: "plugin-2", Version: "2.0.0"}, + Spec: pluginsv0alpha1.PluginSpec{Id: "plugin-2", Version: "2.0.0"}, }, { ObjectMeta: metav1.ObjectMeta{ @@ -422,7 +258,7 @@ func TestSyncer_syncNamespace(t *testing.T) { install.PluginInstallSourceAnnotation: install.SourcePluginStore, }, }, - Spec: pluginsv0alpha1.PluginInstallSpec{Id: "plugin-4"}, + Spec: pluginsv0alpha1.PluginSpec{Id: "plugin-4"}, }, }, expectedError: nil, @@ -434,7 +270,7 @@ func TestSyncer_syncNamespace(t *testing.T) { { name: "list error", installedPlugins: []*plugins.Plugin{}, - apiPlugins: []pluginsv0alpha1.PluginInstall{}, + apiPlugins: []pluginsv0alpha1.Plugin{}, clientListError: errors.New("list error"), expectedError: errors.New("list error"), }, @@ -450,15 +286,15 @@ func TestSyncer_syncNamespace(t *testing.T) { // Setup fake client fakeClient := &fakePluginInstallClient{ - listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { + listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { if tt.clientListError != nil { return nil, tt.clientListError } - return &pluginsv0alpha1.PluginInstallList{ + return &pluginsv0alpha1.PluginList{ Items: tt.apiPlugins, }, nil }, - createFunc: func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) { + createFunc: func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) { registeredIDs = append(registeredIDs, obj.Spec.Id) return obj, nil }, @@ -466,7 +302,7 @@ func TestSyncer_syncNamespace(t *testing.T) { unregisteredIDs = append(unregisteredIDs, identifier.Name) return nil }, - getFunc: func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.PluginInstall, error) { + getFunc: func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error) { // Check if plugin exists in apiPlugins for i := range tt.apiPlugins { if tt.apiPlugins[i].Name == identifier.Name { @@ -521,7 +357,7 @@ func TestSyncer_syncNamespace(t *testing.T) { } } -func TestSyncer_getClient(t *testing.T) { +func TestInstallRegistrar_GetClient(t *testing.T) { tests := []struct { name string }{ @@ -559,92 +395,196 @@ func TestSyncer_getClient(t *testing.T) { } } -func TestSyncer_syncAllNamespaces(t *testing.T) { - tests := []struct { - name string - orgs []*org.OrgDTO - orgServiceError error - expectedError error - expectedCalls int - }{ - { - name: "no orgs", - orgs: []*org.OrgDTO{}, - expectedError: nil, - expectedCalls: 0, - }, - { - name: "single org", - orgs: []*org.OrgDTO{ - {ID: 1, Name: "Org 1"}, - }, - expectedError: nil, - expectedCalls: 1, - }, - { - name: "multiple orgs", - orgs: []*org.OrgDTO{ - {ID: 1, Name: "Org 1"}, - {ID: 2, Name: "Org 2"}, - {ID: 3, Name: "Org 3"}, - }, - expectedError: nil, - expectedCalls: 3, - }, - { - name: "org service error", - orgs: nil, - orgServiceError: errors.New("org service error"), - expectedError: errors.New("org service error"), - expectedCalls: 0, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() - - orgService := orgtest.NewOrgServiceFake() - orgService.ExpectedOrgs = tt.orgs - orgService.ExpectedError = tt.orgServiceError - - // Track namespace sync calls - syncCalls := 0 - fakeClient := &fakePluginInstallClient{ - createFunc: func(ctx context.Context, obj *pluginsv0alpha1.PluginInstall, opts resource.CreateOptions) (*pluginsv0alpha1.PluginInstall, error) { - syncCalls++ - return obj, nil - }, - listAllFunc: func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginInstallList, error) { - return &pluginsv0alpha1.PluginInstallList{}, nil - }, - } - - clientGen := &fakeClientGenerator{client: fakeClient} - - s := newSyncer( - featuremgmt.NewMockFeatureToggles(t), - clientGen, - install.NewInstallRegistrar(clientGen), - orgService, - func(orgID int64) string { return "org-1" }, - &fakeServerLock{}, - ) - - installedPlugins := []*plugins.Plugin{ - {JSONData: plugins.JSONData{ID: "test-plugin", Info: plugins.Info{Version: "1.0.0"}}, Class: plugins.ClassCore}, - } - - err := s.syncAllNamespaces(ctx, install.SourcePluginStore, installedPlugins) - - if tt.expectedError != nil { - require.Error(t, err) - require.Equal(t, tt.expectedError.Error(), err.Error()) - } else { - require.NoError(t, err) - } - - require.Equal(t, tt.expectedCalls, syncCalls) - }) +// Test helpers to avoid import cycles +type fakeServerLock struct { + lockFunc func(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error +} + +func (f *fakeServerLock) LockExecuteAndRelease(ctx context.Context, actionName string, maxInterval time.Duration, fn func(ctx context.Context)) error { + if f.lockFunc != nil { + return f.lockFunc(ctx, actionName, maxInterval, fn) + } + fn(ctx) + return nil +} + +type fakePluginInstallClient struct { + listAllFunc func(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) + getFunc func(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error) + createFunc func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) + updateFunc func(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) + deleteFunc func(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error +} + +func (f *fakePluginInstallClient) Get(ctx context.Context, identifier resource.Identifier) (*pluginsv0alpha1.Plugin, error) { + if f.getFunc != nil { + return f.getFunc(ctx, identifier) + } + // Return a proper k8s NotFound error + return nil, errorsK8s.NewNotFound(schema.GroupResource{ + Group: pluginsv0alpha1.APIGroup, + Resource: "plugininstalls", + }, identifier.Name) +} + +func (f *fakePluginInstallClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { + if f.listAllFunc != nil { + return f.listAllFunc(ctx, namespace, opts) + } + return &pluginsv0alpha1.PluginList{}, nil +} + +func (f *fakePluginInstallClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*pluginsv0alpha1.PluginList, error) { + return f.ListAll(ctx, namespace, opts) +} + +func (f *fakePluginInstallClient) Create(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.CreateOptions) (*pluginsv0alpha1.Plugin, error) { + if f.createFunc != nil { + return f.createFunc(ctx, obj, opts) + } + return obj, nil +} + +func (f *fakePluginInstallClient) Update(ctx context.Context, obj *pluginsv0alpha1.Plugin, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) { + if f.updateFunc != nil { + return f.updateFunc(ctx, obj, opts) + } + return obj, nil +} + +func (f *fakePluginInstallClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus pluginsv0alpha1.PluginStatus, opts resource.UpdateOptions) (*pluginsv0alpha1.Plugin, error) { + return nil, nil +} + +func (f *fakePluginInstallClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*pluginsv0alpha1.Plugin, error) { + return nil, nil +} + +func (f *fakePluginInstallClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + if f.deleteFunc != nil { + return f.deleteFunc(ctx, identifier, opts) + } + return nil +} + +type fakeClientGenerator struct { + client *fakePluginInstallClient +} + +func (f *fakeClientGenerator) ClientFor(kind resource.Kind) (resource.Client, error) { + return &fakeResourceClient{client: f.client}, nil +} + +type fakeResourceClient struct { + client *fakePluginInstallClient +} + +func (f *fakeResourceClient) Get(ctx context.Context, identifier resource.Identifier) (resource.Object, error) { + return f.client.Get(ctx, identifier) +} + +func (f *fakeResourceClient) GetInto(ctx context.Context, identifier resource.Identifier, into resource.Object) error { + obj, err := f.client.Get(ctx, identifier) + if err != nil { + return err + } + // Copy the object data into the provided 'into' object + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *obj } + return nil +} + +func (f *fakeResourceClient) List(ctx context.Context, namespace string, options resource.ListOptions) (resource.ListObject, error) { + return f.client.ListAll(ctx, namespace, options) +} + +func (f *fakeResourceClient) ListInto(ctx context.Context, namespace string, options resource.ListOptions, into resource.ListObject) error { + list, err := f.client.ListAll(ctx, namespace, options) + if err != nil { + return err + } + // Copy the list data into the provided 'into' object + if target, ok := into.(*pluginsv0alpha1.PluginList); ok { + *target = *list + } + return nil +} + +func (f *fakeResourceClient) Create(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions) (resource.Object, error) { + plugin := obj.(*pluginsv0alpha1.Plugin) + return f.client.Create(ctx, plugin, options) +} + +func (f *fakeResourceClient) CreateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.CreateOptions, into resource.Object) error { + created, err := f.Create(ctx, identifier, obj, options) + if err != nil { + return err + } + // Copy the created object data into the provided 'into' object + if plugin, ok := created.(*pluginsv0alpha1.Plugin); ok { + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *plugin + } + } + return nil +} + +func (f *fakeResourceClient) Update(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions) (resource.Object, error) { + plugin := obj.(*pluginsv0alpha1.Plugin) + return f.client.Update(ctx, plugin, options) +} + +func (f *fakeResourceClient) UpdateInto(ctx context.Context, identifier resource.Identifier, obj resource.Object, options resource.UpdateOptions, into resource.Object) error { + updated, err := f.Update(ctx, identifier, obj, options) + if err != nil { + return err + } + // Copy the updated object data into the provided 'into' object + if plugin, ok := updated.(*pluginsv0alpha1.Plugin); ok { + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *plugin + } + } + return nil +} + +func (f *fakeResourceClient) Patch(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions) (resource.Object, error) { + return f.client.Patch(ctx, identifier, patch, options) +} + +func (f *fakeResourceClient) PatchInto(ctx context.Context, identifier resource.Identifier, patch resource.PatchRequest, options resource.PatchOptions, into resource.Object) error { + patched, err := f.Patch(ctx, identifier, patch, options) + if err != nil { + return err + } + // Copy the patched object data into the provided 'into' object + if plugin, ok := patched.(*pluginsv0alpha1.Plugin); ok { + if target, ok := into.(*pluginsv0alpha1.Plugin); ok { + *target = *plugin + } + } + return nil +} + +func (f *fakeResourceClient) Delete(ctx context.Context, identifier resource.Identifier, options resource.DeleteOptions) error { + return f.client.Delete(ctx, identifier, options) +} + +func (f *fakeResourceClient) SubresourceRequest(ctx context.Context, identifier resource.Identifier, req resource.CustomRouteRequestOptions) ([]byte, error) { + return []byte{}, nil +} + +func (f *fakeResourceClient) Watch(ctx context.Context, namespace string, options resource.WatchOptions) (resource.WatchResponse, error) { + return &fakeWatchResponse{}, nil +} + +type fakeWatchResponse struct{} + +func (f *fakeWatchResponse) Stop() {} + +func (f *fakeWatchResponse) WatchEvents() <-chan resource.WatchEvent { + ch := make(chan resource.WatchEvent) + close(ch) + return ch } diff --git a/pkg/services/publicdashboards/public_dashboard_service_mock.go b/pkg/services/publicdashboards/public_dashboard_service_mock.go index b54ef3ec6db..c2b39355737 100644 --- a/pkg/services/publicdashboards/public_dashboard_service_mock.go +++ b/pkg/services/publicdashboards/public_dashboard_service_mock.go @@ -4,9 +4,12 @@ package publicdashboards import ( context "context" + "fmt" backend "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/expr" dashboards "github.com/grafana/grafana/pkg/services/dashboards" dtos "github.com/grafana/grafana/pkg/api/dtos" @@ -587,6 +590,10 @@ func (_m *FakePublicDashboardService) Update(ctx context.Context, u *user.Signed return r0, r1 } +func (_m *FakePublicDashboardService) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) { + return nil, fmt.Errorf("not implemented in public dashboards") +} + // NewFakePublicDashboardService creates a new instance of FakePublicDashboardService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFakePublicDashboardService(t interface { diff --git a/pkg/services/publicdashboards/publicdashboard.go b/pkg/services/publicdashboards/publicdashboard.go index c3c2eb83496..b31657ef267 100644 --- a/pkg/services/publicdashboards/publicdashboard.go +++ b/pkg/services/publicdashboards/publicdashboard.go @@ -5,6 +5,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/expr" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboards" . "github.com/grafana/grafana/pkg/services/publicdashboards/models" @@ -37,6 +39,8 @@ type Service interface { ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error) ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) + + GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) } // ServiceWrapper these methods have different behavior between OSS and Enterprise. The latter would call the OSS service first diff --git a/pkg/services/publicdashboards/service/service.go b/pkg/services/publicdashboards/service/service.go index 239ce31829c..bb3c70dcef4 100644 --- a/pkg/services/publicdashboards/service/service.go +++ b/pkg/services/publicdashboards/service/service.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -512,6 +513,10 @@ func (pd *PublicDashboardServiceImpl) logIsEnabledChanged(existingPubdash *Publi } } +func (pd *PublicDashboardServiceImpl) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) { + return nil, fmt.Errorf("sql schema endpoint not supported with public dashboards") +} + // Checks to see if PublicDashboard.ExistsEnabledByDashboardUid is true on create or changed on update func publicDashboardIsEnabledChanged(existingPubdash *PublicDashboard, newPubdash *PublicDashboard) bool { // creating dashboard, enabled true diff --git a/pkg/services/query/expr_sql_schema.go b/pkg/services/query/expr_sql_schema.go new file mode 100644 index 00000000000..db29da98625 --- /dev/null +++ b/pkg/services/query/expr_sql_schema.go @@ -0,0 +1,75 @@ +package query + +import ( + "context" + + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/apimachinery/errutil" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/expr" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/dsquerierclient" + "github.com/grafana/grafana/pkg/services/validations" +) + +func (s *ServiceImpl) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) { + //TODO DEdupe code + + parsedReq, err := s.parseMetricRequest(ctx, user, false, reqDTO, false) + if err != nil { + return expr.SQLSchemas{}, err + } + exprReq := expr.Request{ + Queries: []expr.Query{}, + } + + if user != nil { // for passthrough authentication, SSE does not authenticate + exprReq.User = user + exprReq.OrgId = user.GetOrgID() + } + + for _, pq := range parsedReq.getFlattenedQueries() { + if pq.datasource == nil { + return nil, ErrMissingDataSourceInfo.Build(errutil.TemplateData{ + Public: map[string]any{ + "RefId": pq.query.RefID, + }, + }) + } + + exprReq.Queries = append(exprReq.Queries, expr.Query{ + JSON: pq.query.JSON, + Interval: pq.query.Interval, + RefID: pq.query.RefID, + MaxDataPoints: pq.query.MaxDataPoints, + QueryType: pq.query.QueryType, + DataSource: pq.datasource, + TimeRange: expr.AbsoluteTimeRange{ + From: pq.query.TimeRange.From, + To: pq.query.TimeRange.To, + }, + }) + } + + return s.expressionService.GetSQLSchemas(ctx, exprReq) +} + +func GetSQLSchemas(ctx context.Context, log log.Logger, dscache datasources.CacheService, exprService *expr.Service, reqDTO dtos.MetricRequest, qsDatasourceClientBuilder dsquerierclient.QSDatasourceClientBuilder, headers map[string]string) (expr.SQLSchemas, error) { + s := &ServiceImpl{ + log: log, + dataSourceCache: dscache, + expressionService: exprService, + dataSourceRequestValidator: validations.ProvideValidator(), + qsDatasourceClientBuilder: qsDatasourceClientBuilder, + headers: headers, + concurrentQueryLimit: 16, // TODO: make it configurable + } + + user, err := identity.GetRequester(ctx) + if err != nil { + return nil, err + } + + return s.GetSQLSchemas(ctx, user, reqDTO) +} diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index d69c69af134..138812ebd03 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -73,6 +73,8 @@ type Service interface { // this is more "forward compatible", for example supports per-query time ranges QueryDataNew(ctx context.Context, user identity.Requester, skipDSCache bool, reqDTO dtos.MetricRequest) (*backend.QueryDataResponse, error) + + GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) } // Gives us compile time error if the service does not adhere to the contract of the interface diff --git a/pkg/services/query/query_service_mock.go b/pkg/services/query/query_service_mock.go index 22c08118941..c391b86061f 100644 --- a/pkg/services/query/query_service_mock.go +++ b/pkg/services/query/query_service_mock.go @@ -4,10 +4,12 @@ package query import ( context "context" + "fmt" backend "github.com/grafana/grafana-plugin-sdk-go/backend" dtos "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/expr" identity "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -97,6 +99,10 @@ func (_m *FakeQueryService) Run(ctx context.Context) error { return r0 } +func (_m *FakeQueryService) GetSQLSchemas(ctx context.Context, user identity.Requester, reqDTO dtos.MetricRequest) (expr.SQLSchemas, error) { + return nil, fmt.Errorf("sql schema endpoint not supported with public dashboards") +} + // NewFakeQueryService creates a new instance of FakeQueryService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewFakeQueryService(t interface { diff --git a/pkg/services/shorturls/shorturl.go b/pkg/services/shorturls/shorturl.go index d62f469f85c..99040011f50 100644 --- a/pkg/services/shorturls/shorturl.go +++ b/pkg/services/shorturls/shorturl.go @@ -4,12 +4,12 @@ import ( "context" "github.com/grafana/grafana/pkg/api/dtos" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/apimachinery/identity" ) type Service interface { - GetShortURLByUID(ctx context.Context, user *user.SignedInUser, uid string) (*ShortUrl, error) - CreateShortURL(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*ShortUrl, error) + GetShortURLByUID(ctx context.Context, user identity.Requester, uid string) (*ShortUrl, error) + CreateShortURL(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*ShortUrl, error) UpdateLastSeenAt(ctx context.Context, shortURL *ShortUrl) error DeleteStaleShortURLs(ctx context.Context, cmd *DeleteShortUrlCommand) error ConvertShortURLToDTO(shortURL *ShortUrl, appURL string) *dtos.ShortURL diff --git a/pkg/services/shorturls/shorturlimpl/shorturl.go b/pkg/services/shorturls/shorturlimpl/shorturl.go index 51753b74c6e..a363a4d7627 100644 --- a/pkg/services/shorturls/shorturlimpl/shorturl.go +++ b/pkg/services/shorturls/shorturlimpl/shorturl.go @@ -8,9 +8,9 @@ import ( "time" "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/shorturls" - "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/util" ) @@ -28,7 +28,7 @@ func ProvideService(db db.DB) *ShortURLService { } } -func (s ShortURLService) GetShortURLByUID(ctx context.Context, user *user.SignedInUser, uid string) (*shorturls.ShortUrl, error) { +func (s ShortURLService) GetShortURLByUID(ctx context.Context, user identity.Requester, uid string) (*shorturls.ShortUrl, error) { return s.SQLStore.Get(ctx, user, uid) } @@ -40,7 +40,7 @@ func (s ShortURLService) List(ctx context.Context, orgID int64) ([]*shorturls.Sh return s.SQLStore.List(ctx, orgID) } -func (s ShortURLService) CreateShortURL(ctx context.Context, user *user.SignedInUser, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { +func (s ShortURLService) CreateShortURL(ctx context.Context, user identity.Requester, cmd *dtos.CreateShortURLCmd) (*shorturls.ShortUrl, error) { relPath := strings.TrimSpace(cmd.Path) if path.IsAbs(relPath) { @@ -74,12 +74,12 @@ func (s ShortURLService) CreateShortURL(ctx context.Context, user *user.SignedIn now := time.Now().Unix() shortURL := shorturls.ShortUrl{ - OrgId: user.OrgID, + OrgId: user.GetOrgID(), Uid: uid, Path: relPath, - CreatedBy: user.UserID, CreatedAt: now, } + shortURL.CreatedBy, _ = user.GetInternalID() if err := s.SQLStore.Insert(ctx, &shortURL); err != nil { return nil, shorturls.ErrShortURLInternal.Errorf("failed to insert shorturl: %w", err) diff --git a/pkg/services/shorturls/shorturlimpl/store.go b/pkg/services/shorturls/shorturlimpl/store.go index 6e0a072d23c..ad74b269d45 100644 --- a/pkg/services/shorturls/shorturlimpl/store.go +++ b/pkg/services/shorturls/shorturlimpl/store.go @@ -3,13 +3,13 @@ package shorturlimpl import ( "context" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/shorturls" - "github.com/grafana/grafana/pkg/services/user" ) type store interface { - Get(ctx context.Context, user *user.SignedInUser, uid string) (*shorturls.ShortUrl, error) + Get(ctx context.Context, user identity.Requester, uid string) (*shorturls.ShortUrl, error) Update(ctx context.Context, shortURL *shorturls.ShortUrl) error Insert(ctx context.Context, shortURL *shorturls.ShortUrl) error Delete(ctx context.Context, cmd *shorturls.DeleteShortUrlCommand) error @@ -20,10 +20,10 @@ type sqlStore struct { db db.DB } -func (s sqlStore) Get(ctx context.Context, user *user.SignedInUser, uid string) (*shorturls.ShortUrl, error) { +func (s sqlStore) Get(ctx context.Context, user identity.Requester, uid string) (*shorturls.ShortUrl, error) { var shortURL shorturls.ShortUrl err := s.db.WithDbSession(ctx, func(dbSession *db.Session) error { - exists, err := dbSession.Where("org_id=? AND uid=?", user.OrgID, uid).Get(&shortURL) + exists, err := dbSession.Where("org_id=? AND uid=?", user.GetOrgID(), uid).Get(&shortURL) if err != nil { return err } diff --git a/pkg/setting/setting_secrets_manager.go b/pkg/setting/setting_secrets_manager.go index 926a0c4b962..d05e6f40f85 100644 --- a/pkg/setting/setting_secrets_manager.go +++ b/pkg/setting/setting_secrets_manager.go @@ -35,8 +35,13 @@ type SecretsManagerSettings struct { GCWorkerPollInterval time.Duration // How long to wait for the process to clean up a secure value to complete. GCWorkerPerSecureValueCleanupTimeout time.Duration - // Whether the secrets management is running in developer mode. - IsDeveloperMode bool + + // Whether to register the MT CRUD API + RegisterAPIServer bool + // Whether to create the MT secrets management database + RunSecretsDBMigrations bool + // Whether to run the data key id migration. Requires that RunSecretsDBMigrations is also true. + RunDataKeyMigration bool } func (cfg *Cfg) readSecretsManagerSettings() { @@ -57,7 +62,9 @@ func (cfg *Cfg) readSecretsManagerSettings() { cfg.SecretsManagement.GCWorkerPollInterval = secretsMgmt.Key("gc_worker_poll_interval").MustDuration(1 * time.Minute) cfg.SecretsManagement.GCWorkerPerSecureValueCleanupTimeout = secretsMgmt.Key("gc_worker_per_request_timeout").MustDuration(5 * time.Second) - cfg.SecretsManagement.IsDeveloperMode = secretsMgmt.Key("developer_mode").MustBool(false) + cfg.SecretsManagement.RegisterAPIServer = secretsMgmt.Key("register_api_server").MustBool(true) + cfg.SecretsManagement.RunSecretsDBMigrations = secretsMgmt.Key("run_secrets_db_migrations").MustBool(true) + cfg.SecretsManagement.RunDataKeyMigration = secretsMgmt.Key("run_data_key_migration").MustBool(true) // Extract available KMS providers from configuration sections providers := make(map[string]map[string]string) diff --git a/pkg/setting/setting_secrets_manager_test.go b/pkg/setting/setting_secrets_manager_test.go index a71a36e0155..c326c250821 100644 --- a/pkg/setting/setting_secrets_manager_test.go +++ b/pkg/setting/setting_secrets_manager_test.go @@ -171,18 +171,18 @@ domain = example.com assert.Empty(t, cfg.SecretsManagement.ConfiguredKMSProviders) }) - t.Run("should handle configuration with developer mode on", func(t *testing.T) { + t.Run("should handle configuration with register_api_server disabled", func(t *testing.T) { iniContent := ` [secrets_manager] -developer_mode = true +register_api_server = false ` cfg, err := NewCfgFromBytes([]byte(iniContent)) require.NoError(t, err) - assert.True(t, cfg.SecretsManagement.IsDeveloperMode) + assert.False(t, cfg.SecretsManagement.RegisterAPIServer) }) - t.Run("should handle configuration without developer mode set", func(t *testing.T) { + t.Run("should handle configuration without register_api_server set", func(t *testing.T) { iniContent := ` [secrets_manager] encryption_provider = aws_kms @@ -190,6 +190,50 @@ encryption_provider = aws_kms cfg, err := NewCfgFromBytes([]byte(iniContent)) require.NoError(t, err) - assert.False(t, cfg.SecretsManagement.IsDeveloperMode) + assert.True(t, cfg.SecretsManagement.RegisterAPIServer) + }) + + t.Run("should handle configuration with run_secrets_db_migrations disabled", func(t *testing.T) { + iniContent := ` +[secrets_manager] +run_secrets_db_migrations = false +` + cfg, err := NewCfgFromBytes([]byte(iniContent)) + require.NoError(t, err) + + assert.False(t, cfg.SecretsManagement.RunSecretsDBMigrations) + }) + + t.Run("should handle configuration without run_secrets_db_migrations set", func(t *testing.T) { + iniContent := ` +[secrets_manager] +encryption_provider = aws_kms +` + cfg, err := NewCfgFromBytes([]byte(iniContent)) + require.NoError(t, err) + + assert.True(t, cfg.SecretsManagement.RunSecretsDBMigrations) + }) + + t.Run("should handle configuration with run_data_key_migration disabled", func(t *testing.T) { + iniContent := ` +[secrets_manager] +run_data_key_migration = false +` + cfg, err := NewCfgFromBytes([]byte(iniContent)) + require.NoError(t, err) + + assert.False(t, cfg.SecretsManagement.RunDataKeyMigration) + }) + + t.Run("should handle configuration without run_data_key_migration set", func(t *testing.T) { + iniContent := ` +[secrets_manager] +encryption_provider = aws_kms +` + cfg, err := NewCfgFromBytes([]byte(iniContent)) + require.NoError(t, err) + + assert.True(t, cfg.SecretsManagement.RunDataKeyMigration) }) } diff --git a/pkg/storage/legacysql/dualwrite/dualwriter.go b/pkg/storage/legacysql/dualwrite/dualwriter.go index 72c05d5ed4d..cefaf923efc 100644 --- a/pkg/storage/legacysql/dualwrite/dualwriter.go +++ b/pkg/storage/legacysql/dualwrite/dualwriter.go @@ -37,6 +37,7 @@ type dualWriter struct { } func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + log := logging.FromContext(ctx).With("method", "Get", "name", name) // If we read from unified, we can just do that and return. if d.readUnified { return d.unified.Get(ctx, name, options) @@ -44,6 +45,7 @@ func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOp // If legacy is still our main store, lets first read from it. legacyGet, err := d.legacy.Get(ctx, name, options) if err != nil { + log.Error("failed to get object from legacy storage", "err", err) return nil, err } // Once we have successfully read from legacy, we can check if we want to fail on a unified read. @@ -52,7 +54,6 @@ func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOp go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.Get(ctxBg, name, options); err != nil { - log := logging.FromContext(ctxBg).With("method", "Get") log.Error("failed background GET to unified", "err", err) } }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) @@ -61,6 +62,7 @@ func (d *dualWriter) Get(ctx context.Context, name string, options *metav1.GetOp // If it's not okay to fail, we have to check it in the foreground. _, unifiedErr := d.unified.Get(ctx, name, options) if unifiedErr != nil && !apierrors.IsNotFound(unifiedErr) { + log.Error("failed to get object from unified storage", "err", unifiedErr) return nil, unifiedErr } return legacyGet, nil @@ -71,7 +73,7 @@ func (d *dualWriter) List(ctx context.Context, options *metainternalversion.List var ( legacyOptions = options.DeepCopy() unifiedOptions = options.DeepCopy() - log = logging.FromContext(ctx).With("method", "List") + log = logging.FromContext(ctx).With("method", "List", "options", options) ) legacyToken, unifiedToken, err := parseContinueTokens(options.Continue) @@ -86,6 +88,7 @@ func (d *dualWriter) List(ctx context.Context, options *metainternalversion.List if d.readUnified { unifiedList, err := d.unified.List(ctx, unifiedOptions) if err != nil { + log.Error("failed to list objects from unified storage", "err", err) return nil, err } unifiedMeta, err := meta.ListAccessor(unifiedList) @@ -115,6 +118,7 @@ func (d *dualWriter) List(ctx context.Context, options *metainternalversion.List // If legacy is still the main store, lets first read from it. legacyList, err := d.legacy.List(ctx, legacyOptions) if err != nil { + log.Error("failed to list objects from legacy storage", "err", err) return nil, err } legacyMeta, err := meta.ListAccessor(legacyList) @@ -158,6 +162,7 @@ func (d *dualWriter) List(ctx context.Context, options *metainternalversion.List // If it's not okay to fail, we have to check it in the foreground. unifiedList, err := d.unified.List(ctx, unifiedOptions) if err != nil { + log.Error("failed to list objects from unified storage", "err", err) return nil, err } unifiedMeta, err := meta.ListAccessor(unifiedList) @@ -207,7 +212,7 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida // will try to cleanup the object in legacy. createdFromLegacy, err := d.legacy.Create(ctx, in, createValidation, options) if err != nil { - log.Error("unable to create object in legacy storage", "err", err) + log.With("object", in).Error("failed to CREATE object in legacy storage", "err", err) return nil, err } @@ -234,11 +239,11 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida if d.readUnified { storageObj, errObjectSt := d.unified.Create(ctx, createdCopy, createValidation, options) if errObjectSt != nil { - log.Error("unable to create object in unified storage", "err", errObjectSt) + log.With("object", createdCopy).Error("failed to CREATE object in unified storage", "err", errObjectSt) // If we cannot create in unified storage, attempt to clean up legacy. _, _, err = d.legacy.Delete(ctx, accCreated.GetName(), nil, &metav1.DeleteOptions{}) if err != nil { - log.Error("unable to cleanup object in legacy storage", "err", err) + log.With("name", accCreated.GetName()).Error("failed to CLEANUP object in legacy storage", "err", err) } return nil, errObjectSt } @@ -248,13 +253,13 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.Create(ctxBg, createdCopy, createValidation, options); err != nil { - log.Error("unable to create object in unified storage", "err", err) + log.With("object", createdCopy).Error("failed to CREATE object in unified storage", "err", err) } }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) } else { // Otherwise let's create it in the foreground and return any error. if _, err := d.unified.Create(ctx, createdCopy, createValidation, options); err != nil { - log.Error("unable to create object in unified storage", "err", err) + log.With("object", createdCopy).Error("failed to CREATE object in unified storage", "err", err) if d.errorIsOK { return createdFromLegacy, nil } @@ -262,7 +267,7 @@ func (d *dualWriter) Create(ctx context.Context, in runtime.Object, createValida // If we cannot create in unified storage, attempt to clean up legacy. _, _, errLegacy := d.legacy.Delete(ctx, accCreated.GetName(), nil, &metav1.DeleteOptions{}) if errLegacy != nil { - log.Error("unable to cleanup object in legacy storage", "err", errLegacy) + log.With("name", accCreated.GetName()).Error("failed to CLEANUP object in legacy storage", "err", errLegacy) } return nil, err } @@ -281,10 +286,12 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r // By setting RemovePermissions to false in the context, we will skip the deletion of permissions // in the legacy store. This is needed as otherwise the permissions would be missing when executing // the delete operation in the unified storage store. + log := logging.FromContext(ctx).With("method", "Delete", "name", name) ctx = utils.SetFolderRemovePermissions(ctx, false) objFromLegacy, asyncLegacy, err := d.legacy.Delete(ctx, name, deleteValidation, options) if err != nil && (!d.readUnified || !d.errorIsOK && !apierrors.IsNotFound(err)) { + log.Error("failed to DELETE object in legacy storage", "err", err) return nil, false, err } @@ -295,6 +302,7 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r if d.readUnified { objFromStorage, asyncStorage, err := d.unified.Delete(ctx, name, deleteValidation, options) if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { + log.Error("failed to DELETE object in unified storage", "err", err) return nil, false, err } return objFromStorage, asyncStorage, nil @@ -304,7 +312,6 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r defer cancel() _, _, err := d.unified.Delete(ctxBg, name, deleteValidation, options) if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { - log := logging.FromContext(ctxBg).With("method", "Delete") log.Error("failed background DELETE in unified storage", "err", err) } }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) @@ -312,6 +319,7 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r // Otherwise we just run it in the foreground and return an error if any might happen. _, _, err = d.unified.Delete(ctx, name, deleteValidation, options) if err != nil && !apierrors.IsNotFound(err) && !d.errorIsOK { + log.Error("failed to DELETE object in unified storage", "err", err) return nil, false, err } return objFromLegacy, asyncLegacy, nil @@ -319,7 +327,7 @@ func (d *dualWriter) Delete(ctx context.Context, name string, deleteValidation r // Update overrides the behavior of the generic DualWriter and writes first to Storage and then to LegacyStorage. func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { - log := logging.FromContext(ctx).With("method", "Update") + log := logging.FromContext(ctx).With("method", "Update", "name", name, "objInfo", objInfo) // update in legacy first, and then unistore. Will return a failure if either fails. // @@ -341,7 +349,7 @@ func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.Updat objFromLegacy, createdLegacy, err := d.legacy.Update(ctx, name, legacyInfo, createValidation, updateValidation, legacyForceCreate, options) if err != nil { - log.With("object", objFromLegacy).Error("could not update in legacy storage", "err", err) + log.Error("failed to UPDATE in legacy storage", "err", err) return nil, false, err } @@ -350,7 +358,7 @@ func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.Updat if createdLegacy { legacyMeta, err := utils.MetaAccessor(objFromLegacy) if err != nil { - log.With("object", objFromLegacy).Error("could not get meta accessor for legacy object", "err", err) + log.Error("failed to get meta accessor for legacy object", "err", err) return nil, false, err } unifiedInfo = &wrappedUpdateInfo{ @@ -374,6 +382,7 @@ func (d *dualWriter) Update(ctx context.Context, name string, objInfo rest.Updat } // If we want to check unified errors just run it in foreground. if _, _, err := d.unified.Update(ctx, name, unifiedInfo, createValidation, updateValidation, unifiedForceCreate, options); err != nil { + log.Error("failed to UPDATE in unified storage", "err", err) return nil, false, err } return objFromLegacy, createdLegacy, nil @@ -391,7 +400,7 @@ func (d *dualWriter) DeleteCollection(ctx context.Context, deleteValidation rest deletedLegacy, err := d.legacy.DeleteCollection(ctx, deleteValidation, options, listOptions) if err != nil { - log.With("deleted", deletedLegacy).Error("failed to delete collection successfully from legacy storage", "err", err) + log.With("options", options).Error("failed to DELETE collection successfully from legacy storage", "err", err) return nil, err } @@ -403,14 +412,14 @@ func (d *dualWriter) DeleteCollection(ctx context.Context, deleteValidation rest go func(ctxBg context.Context, cancel context.CancelFunc) { defer cancel() if _, err := d.unified.DeleteCollection(ctxBg, deleteValidation, options, listOptions); err != nil { - log.Error("failed background DELETE collection to unified storage", "err", err) + log.With("object", deletedLegacy).Error("failed background DELETE collection to unified storage", "err", err) } }(context.WithTimeout(context.WithoutCancel(ctx), backgroundReqTimeout)) return deletedLegacy, nil } // Otherwise we have to check the error and run it in the foreground. - if deletedStorage, err := d.unified.DeleteCollection(ctx, deleteValidation, options, listOptions); err != nil { - log.With("deleted", deletedStorage).Error("failed to delete collection successfully from Storage", "err", err) + if _, err := d.unified.DeleteCollection(ctx, deleteValidation, options, listOptions); err != nil { + log.With("object", deletedLegacy).Error("failed to DELETE collection successfully from Storage", "err", err) return nil, err } return deletedLegacy, nil diff --git a/pkg/storage/secret/encryption/data/encrypted_value_create.sql b/pkg/storage/secret/encryption/data/encrypted_value_create.sql index 7bce6a19bd4..7aa749e01a3 100644 --- a/pkg/storage/secret/encryption/data/encrypted_value_create.sql +++ b/pkg/storage/secret/encryption/data/encrypted_value_create.sql @@ -3,6 +3,7 @@ INSERT INTO {{ .Ident "secret_encrypted_value" }} ( {{ .Ident "name" }}, {{ .Ident "version" }}, {{ .Ident "encrypted_data" }}, + {{ .Ident "data_key_id" }}, {{ .Ident "created" }}, {{ .Ident "updated" }} ) VALUES ( @@ -10,6 +11,7 @@ INSERT INTO {{ .Ident "secret_encrypted_value" }} ( {{ .Arg .Row.Name }}, {{ .Arg .Row.Version }}, {{ .Arg .Row.EncryptedData }}, + {{ .Arg .Row.DataKeyID }}, {{ .Arg .Row.Created }}, {{ .Arg .Row.Updated }} ); diff --git a/pkg/storage/secret/encryption/data/encrypted_value_list_all.sql b/pkg/storage/secret/encryption/data/encrypted_value_list_all.sql index d318517346d..2a0446a3a9d 100644 --- a/pkg/storage/secret/encryption/data/encrypted_value_list_all.sql +++ b/pkg/storage/secret/encryption/data/encrypted_value_list_all.sql @@ -3,6 +3,7 @@ SELECT {{ .Ident "name" }}, {{ .Ident "version" }}, {{ .Ident "encrypted_data" }}, + {{ .Ident "data_key_id" }}, {{ .Ident "created" }}, {{ .Ident "updated" }} FROM diff --git a/pkg/storage/secret/encryption/data/encrypted_value_read.sql b/pkg/storage/secret/encryption/data/encrypted_value_read.sql index 6a672554efb..3ff20a641f0 100644 --- a/pkg/storage/secret/encryption/data/encrypted_value_read.sql +++ b/pkg/storage/secret/encryption/data/encrypted_value_read.sql @@ -3,6 +3,7 @@ SELECT {{ .Ident "name" }}, {{ .Ident "version" }}, {{ .Ident "encrypted_data" }}, + {{ .Ident "data_key_id" }}, {{ .Ident "created" }}, {{ .Ident "updated" }} FROM diff --git a/pkg/storage/secret/encryption/data/encrypted_value_update.sql b/pkg/storage/secret/encryption/data/encrypted_value_update.sql index 08e985297a1..c939c092c1c 100644 --- a/pkg/storage/secret/encryption/data/encrypted_value_update.sql +++ b/pkg/storage/secret/encryption/data/encrypted_value_update.sql @@ -2,6 +2,7 @@ UPDATE {{ .Ident "secret_encrypted_value" }} SET {{ .Ident "encrypted_data" }} = {{ .Arg .EncryptedData }}, + {{ .Ident "data_key_id" }} = {{ .Arg .DataKeyID }}, {{ .Ident "updated" }} = {{ .Arg .Updated }} WHERE {{ .Ident "namespace" }} = {{ .Arg .Namespace }} AND diff --git a/pkg/storage/secret/encryption/encrypted_value_model.go b/pkg/storage/secret/encryption/encrypted_value_model.go index eb66247c021..3c4ba7be905 100644 --- a/pkg/storage/secret/encryption/encrypted_value_model.go +++ b/pkg/storage/secret/encryption/encrypted_value_model.go @@ -6,6 +6,7 @@ type EncryptedValue struct { Namespace string Name string Version int64 + DataKeyID string EncryptedData []byte Created int64 Updated int64 diff --git a/pkg/storage/secret/encryption/encrypted_value_store.go b/pkg/storage/secret/encryption/encrypted_value_store.go index 3a10d01b957..af48a651868 100644 --- a/pkg/storage/secret/encryption/encrypted_value_store.go +++ b/pkg/storage/secret/encryption/encrypted_value_store.go @@ -1,7 +1,9 @@ package encryption import ( + "bytes" "context" + "encoding/base64" "errors" "fmt" "time" @@ -10,6 +12,7 @@ import ( "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/storage/unified/sql" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) @@ -37,9 +40,9 @@ type encryptedValStorage struct { tracer trace.Tracer } -func (s *encryptedValStorage) Create(ctx context.Context, namespace, name string, version int64, encryptedData []byte) (ev *contracts.EncryptedValue, err error) { +func (s *encryptedValStorage) Create(ctx context.Context, namespace xkube.Namespace, name string, version int64, encryptedData contracts.EncryptedPayload) (ev *contracts.EncryptedValue, err error) { ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Create", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), )) defer span.End() @@ -56,10 +59,11 @@ func (s *encryptedValStorage) Create(ctx context.Context, namespace, name string createdTime := time.Now().Unix() encryptedValue := &EncryptedValue{ - Namespace: namespace, + Namespace: namespace.String(), Name: name, Version: version, - EncryptedData: encryptedData, + EncryptedData: encryptedData.EncryptedData, + DataKeyID: encryptedData.DataKeyID, Created: createdTime, Updated: createdTime, } @@ -88,18 +92,21 @@ func (s *encryptedValStorage) Create(ctx context.Context, namespace, name string } return &contracts.EncryptedValue{ - Namespace: encryptedValue.Namespace, - Name: encryptedValue.Name, - Version: encryptedValue.Version, - EncryptedData: encryptedValue.EncryptedData, - Created: encryptedValue.Created, - Updated: encryptedValue.Updated, + Namespace: encryptedValue.Namespace, + Name: encryptedValue.Name, + Version: encryptedValue.Version, + EncryptedPayload: contracts.EncryptedPayload{ + DataKeyID: encryptedValue.DataKeyID, + EncryptedData: encryptedValue.EncryptedData, + }, + Created: encryptedValue.Created, + Updated: encryptedValue.Updated, }, nil } -func (s *encryptedValStorage) Update(ctx context.Context, namespace, name string, version int64, encryptedData []byte) error { +func (s *encryptedValStorage) Update(ctx context.Context, namespace xkube.Namespace, name string, version int64, encryptedData contracts.EncryptedPayload) error { ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Update", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), attribute.String("name", name), attribute.Int64("version", version), )) @@ -107,10 +114,11 @@ func (s *encryptedValStorage) Update(ctx context.Context, namespace, name string req := updateEncryptedValue{ SQLTemplate: sqltemplate.New(s.dialect), - Namespace: namespace, + Namespace: namespace.String(), Name: name, Version: version, - EncryptedData: encryptedData, + EncryptedData: encryptedData.EncryptedData, + DataKeyID: encryptedData.DataKeyID, Updated: time.Now().Unix(), } @@ -133,9 +141,9 @@ func (s *encryptedValStorage) Update(ctx context.Context, namespace, name string return nil } -func (s *encryptedValStorage) Get(ctx context.Context, namespace, name string, version int64) (*contracts.EncryptedValue, error) { +func (s *encryptedValStorage) Get(ctx context.Context, namespace xkube.Namespace, name string, version int64) (*contracts.EncryptedValue, error) { ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Get", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), attribute.String("name", name), attribute.Int64("version", version), )) @@ -143,7 +151,7 @@ func (s *encryptedValStorage) Get(ctx context.Context, namespace, name string, v req := &readEncryptedValue{ SQLTemplate: sqltemplate.New(s.dialect), - Namespace: namespace, + Namespace: namespace.String(), Name: name, Version: version, } @@ -163,7 +171,7 @@ func (s *encryptedValStorage) Get(ctx context.Context, namespace, name string, v } var encryptedValue EncryptedValue - err = rows.Scan(&encryptedValue.Namespace, &encryptedValue.Name, &encryptedValue.Version, &encryptedValue.EncryptedData, &encryptedValue.Created, &encryptedValue.Updated) + err = rows.Scan(&encryptedValue.Namespace, &encryptedValue.Name, &encryptedValue.Version, &encryptedValue.EncryptedData, &encryptedValue.DataKeyID, &encryptedValue.Created, &encryptedValue.Updated) if err != nil { return nil, fmt.Errorf("failed to scan encrypted value row: %w", err) } @@ -172,18 +180,21 @@ func (s *encryptedValStorage) Get(ctx context.Context, namespace, name string, v } return &contracts.EncryptedValue{ - Namespace: encryptedValue.Namespace, - Name: encryptedValue.Name, - Version: encryptedValue.Version, - EncryptedData: encryptedValue.EncryptedData, - Created: encryptedValue.Created, - Updated: encryptedValue.Updated, + Namespace: encryptedValue.Namespace, + Name: encryptedValue.Name, + Version: encryptedValue.Version, + EncryptedPayload: contracts.EncryptedPayload{ + DataKeyID: encryptedValue.DataKeyID, + EncryptedData: encryptedValue.EncryptedData, + }, + Created: encryptedValue.Created, + Updated: encryptedValue.Updated, }, nil } -func (s *encryptedValStorage) Delete(ctx context.Context, namespace, name string, version int64) error { +func (s *encryptedValStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string, version int64) error { ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Delete", trace.WithAttributes( - attribute.String("namespace", namespace), + attribute.String("namespace", namespace.String()), attribute.String("name", name), attribute.Int64("version", version), )) @@ -191,7 +202,7 @@ func (s *encryptedValStorage) Delete(ctx context.Context, namespace, name string req := deleteEncryptedValue{ SQLTemplate: sqltemplate.New(s.dialect), - Namespace: namespace, + Namespace: namespace.String(), Name: name, Version: version, } @@ -264,6 +275,7 @@ func (s *globalEncryptedValStorage) ListAll(ctx context.Context, opts contracts. &row.Name, &row.Version, &row.EncryptedData, + &row.DataKeyID, &row.Created, &row.Updated, ) @@ -272,12 +284,15 @@ func (s *globalEncryptedValStorage) ListAll(ctx context.Context, opts contracts. } encryptedValues = append(encryptedValues, &contracts.EncryptedValue{ - Namespace: row.Namespace, - Name: row.Name, - Version: row.Version, - EncryptedData: row.EncryptedData, - Created: row.Created, - Updated: row.Updated, + Namespace: row.Namespace, + Name: row.Name, + Version: row.Version, + EncryptedPayload: contracts.EncryptedPayload{ + DataKeyID: row.DataKeyID, + EncryptedData: row.EncryptedData, + }, + Created: row.Created, + Updated: row.Updated, }) } if err := rows.Err(); err != nil { @@ -329,3 +344,77 @@ func (s *globalEncryptedValStorage) CountAll(ctx context.Context, untilTime *int return count, nil } + +type encryptedValMigrationExecutor struct { + db contracts.Database + dialect sqltemplate.Dialect + tracer trace.Tracer + encryptedValueStore contracts.EncryptedValueStorage + globalStore contracts.GlobalEncryptedValueStorage +} + +func ProvideEncryptedValueMigrationExecutor( + db contracts.Database, + tracer trace.Tracer, + encryptedValueStore contracts.EncryptedValueStorage, + globalStore contracts.GlobalEncryptedValueStorage, +) (contracts.EncryptedValueMigrationExecutor, error) { + return &encryptedValMigrationExecutor{ + db: db, + dialect: sqltemplate.DialectForDriver(db.DriverName()), + tracer: tracer, + encryptedValueStore: encryptedValueStore, + globalStore: globalStore, + }, nil +} + +func (s *encryptedValMigrationExecutor) Execute(ctx context.Context) (int, error) { + ctx, span := s.tracer.Start(ctx, "EncryptedValueMigrationExecutor.Execute") + defer span.End() + + // 1. Retrieve all encrypted values + encryptedValues, err := s.globalStore.ListAll(ctx, contracts.ListOpts{}, nil) + if err != nil { + return 0, fmt.Errorf("listing all encrypted values: %w", err) + } + + // This doesn't need to be done in a single transaction because there's no risk to successful rows if other rows fail + rowsAffected := 0 + for _, encryptedValue := range encryptedValues { + // 2. If the value already has the data key id broken out, skip it + if encryptedValue.DataKeyID != "" { + continue + } + + // 3. Split the data key id and the encrypted data out from the encoded payload + payload := encryptedValue.EncryptedData + const keyIdDelimiter = '#' + payload = payload[1:] + endOfKey := bytes.Index(payload, []byte{keyIdDelimiter}) + if endOfKey == -1 { + return rowsAffected, fmt.Errorf("could not find valid key id in encrypted payload with namespace %s and name %s and version %d", encryptedValue.Namespace, encryptedValue.Name, encryptedValue.Version) + } + b64Key := payload[:endOfKey] + encryptedData := payload[endOfKey+1:] + if len(encryptedData) == 0 { + return rowsAffected, fmt.Errorf("encrypted data is empty with namespace %s and name %s and version %d", encryptedValue.Namespace, encryptedValue.Name, encryptedValue.Version) + } + keyId := make([]byte, base64.RawStdEncoding.DecodedLen(len(b64Key))) + _, err := base64.RawStdEncoding.Decode(keyId, b64Key) + if err != nil { + return rowsAffected, fmt.Errorf("decoding key id with namespace %s and name %s and version %d: %w", encryptedValue.Namespace, encryptedValue.Name, encryptedValue.Version, err) + } + + // 4. Update the encrypted value with the data key id and the encrypted data + err = s.encryptedValueStore.Update(ctx, xkube.Namespace(encryptedValue.Namespace), encryptedValue.Name, encryptedValue.Version, contracts.EncryptedPayload{ + DataKeyID: string(keyId), + EncryptedData: encryptedData, + }) + if err != nil { + return rowsAffected, fmt.Errorf("updating encrypted value with namespace %s and name %s and version %d: %w", encryptedValue.Namespace, encryptedValue.Name, encryptedValue.Version, err) + } + rowsAffected++ + } + + return rowsAffected, nil +} diff --git a/pkg/storage/secret/encryption/encrypted_value_store_test.go b/pkg/storage/secret/encryption/encrypted_value_store_test.go index 620a034f22c..2f84e1f8575 100644 --- a/pkg/storage/secret/encryption/encrypted_value_store_test.go +++ b/pkg/storage/secret/encryption/encrypted_value_store_test.go @@ -2,15 +2,25 @@ package encryption_test import ( "bytes" + "context" + "encoding/base64" "errors" + "fmt" "slices" "testing" + "text/template" "time" + "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" + "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher" + cipherService "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service" "github.com/grafana/grafana/pkg/registry/apis/secret/testutils" + "github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/storage/secret/encryption" + "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" "pgregory.net/rapid" ) @@ -21,7 +31,10 @@ func TestEncryptedValueStoreImpl(t *testing.T) { t.Parallel() sut := testutils.Setup(t) - createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace", "test-name", 1, []byte("test-data")) + createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace", "test-name", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("test-data"), + }) require.NoError(t, err) require.NotEmpty(t, createdEV.Namespace) require.NotEmpty(t, createdEV.Name) @@ -36,10 +49,13 @@ func TestEncryptedValueStoreImpl(t *testing.T) { t.Parallel() sut := testutils.Setup(t) - createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace", "test-name", 1, []byte("test-data")) + createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace", "test-name", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("test-data"), + }) require.NoError(t, err) - obtainedEV, err := sut.EncryptedValueStorage.Get(t.Context(), createdEV.Namespace, createdEV.Name, createdEV.Version) + obtainedEV, err := sut.EncryptedValueStorage.Get(t.Context(), xkube.Namespace(createdEV.Namespace), createdEV.Name, createdEV.Version) require.NoError(t, err) require.Equal(t, createdEV.Namespace, obtainedEV.Namespace) @@ -47,6 +63,7 @@ func TestEncryptedValueStoreImpl(t *testing.T) { require.Equal(t, createdEV.Created, obtainedEV.Created) require.Equal(t, createdEV.Updated, obtainedEV.Updated) require.Equal(t, createdEV.EncryptedData, obtainedEV.EncryptedData) + require.Equal(t, createdEV.DataKeyID, obtainedEV.DataKeyID) require.Equal(t, createdEV.Namespace, obtainedEV.Namespace) }) @@ -54,7 +71,10 @@ func TestEncryptedValueStoreImpl(t *testing.T) { t.Parallel() sut := testutils.Setup(t) - createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "ns1", "test-name", 1, []byte("test-data")) + createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "ns1", "test-name", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("test-data"), + }) require.NoError(t, err) obtainedEV, err := sut.EncryptedValueStorage.Get(t.Context(), "ns2", createdEV.Name, createdEV.Version) @@ -78,16 +98,23 @@ func TestEncryptedValueStoreImpl(t *testing.T) { t.Parallel() sut := testutils.Setup(t) - createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace", "test-name", 1, []byte("test-data")) + createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace", "test-name", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("test-data"), + }) require.NoError(t, err) - err = sut.EncryptedValueStorage.Update(t.Context(), createdEV.Namespace, createdEV.Name, createdEV.Version, []byte("test-data-updated")) + err = sut.EncryptedValueStorage.Update(t.Context(), xkube.Namespace(createdEV.Namespace), createdEV.Name, createdEV.Version, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id-updated", + EncryptedData: []byte("test-data-updated"), + }) require.NoError(t, err) - updatedEV, err := sut.EncryptedValueStorage.Get(t.Context(), createdEV.Namespace, createdEV.Name, createdEV.Version) + updatedEV, err := sut.EncryptedValueStorage.Get(t.Context(), xkube.Namespace(createdEV.Namespace), createdEV.Name, createdEV.Version) require.NoError(t, err) require.Equal(t, []byte("test-data-updated"), updatedEV.EncryptedData) + require.Equal(t, "test-data-key-id-updated", updatedEV.DataKeyID) require.Equal(t, createdEV.Created, updatedEV.Created) require.Equal(t, createdEV.Namespace, updatedEV.Namespace) }) @@ -96,7 +123,10 @@ func TestEncryptedValueStoreImpl(t *testing.T) { t.Parallel() sut := testutils.Setup(t) - err := sut.EncryptedValueStorage.Update(t.Context(), "test-namespace", "test-uid", 1, []byte("test-data")) + err := sut.EncryptedValueStorage.Update(t.Context(), "test-namespace", "test-uid", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("test-data"), + }) require.Error(t, err) }) @@ -104,16 +134,19 @@ func TestEncryptedValueStoreImpl(t *testing.T) { t.Parallel() sut := testutils.Setup(t) - createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace", "test-name", 1, []byte("ttttest-data")) + createdEV, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace", "test-name", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("ttttest-data"), + }) require.NoError(t, err) - _, err = sut.EncryptedValueStorage.Get(t.Context(), createdEV.Namespace, createdEV.Name, createdEV.Version) + _, err = sut.EncryptedValueStorage.Get(t.Context(), xkube.Namespace(createdEV.Namespace), createdEV.Name, createdEV.Version) require.NoError(t, err) - err = sut.EncryptedValueStorage.Delete(t.Context(), createdEV.Namespace, createdEV.Name, createdEV.Version) + err = sut.EncryptedValueStorage.Delete(t.Context(), xkube.Namespace(createdEV.Namespace), createdEV.Name, createdEV.Version) require.NoError(t, err) - obtainedEV, err := sut.EncryptedValueStorage.Get(t.Context(), createdEV.Namespace, createdEV.Name, createdEV.Version) + obtainedEV, err := sut.EncryptedValueStorage.Get(t.Context(), xkube.Namespace(createdEV.Namespace), createdEV.Name, createdEV.Version) require.Error(t, err) require.Nil(t, obtainedEV) }) @@ -130,10 +163,16 @@ func TestEncryptedValueStoreImpl(t *testing.T) { t.Parallel() sut := testutils.Setup(t) - createdEvA, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace-a", "test-name", 1, []byte("test-data")) + createdEvA, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace-a", "test-name", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("test-data"), + }) require.NoError(t, err) - createdEvB, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace-b", "test-name", 1, []byte("test-data")) + createdEvB, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace-b", "test-name", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("test-data"), + }) require.NoError(t, err) // List all encrypted values, without pagination @@ -180,10 +219,16 @@ func TestEncryptedValueStoreImpl(t *testing.T) { t.Parallel() sut := testutils.Setup(t) - _, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace-a", "test-name", 1, []byte("test-data")) + _, err := sut.EncryptedValueStorage.Create(t.Context(), "test-namespace-a", "test-name", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("test-data"), + }) require.NoError(t, err) - _, err = sut.EncryptedValueStorage.Create(t.Context(), "test-namespace-b", "test-name", 1, []byte("test-data")) + _, err = sut.EncryptedValueStorage.Create(t.Context(), "test-namespace-b", "test-name", 1, contracts.EncryptedPayload{ + DataKeyID: "test-data-key-id", + EncryptedData: []byte("test-data"), + }) require.NoError(t, err) count, err := sut.GlobalEncryptedValueStorage.CountAll(t.Context(), nil) @@ -198,6 +243,281 @@ func TestEncryptedValueStoreImpl(t *testing.T) { }) } +func TestEncryptedValueMigration(t *testing.T) { + t.Parallel() + + t.Run("golden path - successful migration of legacy format", func(t *testing.T) { + t.Parallel() + + sut := testutils.Setup(t) + tracer := noop.NewTracerProvider().Tracer("test") + usageStats := &usagestats.UsageStatsMock{T: t} + enc, err := cipherService.ProvideAESGCMCipherService(tracer, usageStats) + require.NoError(t, err) + + testCases := []struct { + namespace string + name string + version int64 + plaintext string + dataKeyId string + }{ + { + namespace: "test-namespace-1", + name: "test-name-1", + version: 1, + plaintext: "test-plaintext-1", + dataKeyId: "test-data-key-id-1", + }, + { + namespace: "test-namespace-1", + name: "test-name-2", + version: 1, + plaintext: "test-plaintext-2", + dataKeyId: "test-data-key-id-1", + }, + { + namespace: "test-namespace-2", + name: "test-name-3", + version: 1, + plaintext: "test-plaintext-3", + dataKeyId: "test-data-key-id-2", + }, + } + + // Seed with data in the legacy format + for _, tc := range testCases { + err := createLegacyEncryptedData(t, sut, enc, tc.namespace, tc.name, tc.version, tc.plaintext, tc.dataKeyId) + require.NoError(t, err) + } + + // Run the migration and blindy trust it + rowsAffected, err := sut.EncryptedValueMigrationExecutor.Execute(t.Context()) + require.NoError(t, err) + require.Equal(t, len(testCases), rowsAffected) + + // Now validate that the data is in the new format + encryptedValues, err := sut.GlobalEncryptedValueStorage.ListAll(t.Context(), contracts.ListOpts{}, nil) + require.NoError(t, err) + require.Len(t, encryptedValues, 3) + + for _, tc := range testCases { + ev, err := sut.EncryptedValueStorage.Get(t.Context(), xkube.Namespace(tc.namespace), tc.name, tc.version) + require.NoError(t, err) + + // Decrypt the encrypted data and check for equality + decrypted, err := enc.Decrypt(t.Context(), ev.EncryptedData, tc.dataKeyId) + require.NoError(t, err) + require.Equal(t, tc.dataKeyId, ev.DataKeyID) + require.Equal(t, tc.plaintext, string(decrypted)) + } + }) + + t.Run("error conditions - handles corrupt data gracefully", func(t *testing.T) { + t.Parallel() + + tracer := noop.NewTracerProvider().Tracer("test") + sut := testutils.Setup(t) + + t.Run("global store list error", func(t *testing.T) { + mockGlobalStore := &mockGlobalEncryptedValueStorage{ + listAllError: errors.New("database connection failed"), + } + + migrationExecutor, err := encryption.ProvideEncryptedValueMigrationExecutor( + sut.Database, + tracer, + sut.EncryptedValueStorage, + mockGlobalStore, + ) + require.NoError(t, err) + + rowsAffected, err := migrationExecutor.Execute(t.Context()) + require.Error(t, err) + require.Contains(t, err.Error(), "listing all encrypted values") + require.Equal(t, 0, rowsAffected) + }) + + t.Run("corrupt data - missing key delimiter", func(t *testing.T) { + mockGlobalStore := &mockGlobalEncryptedValueStorage{ + encryptedValues: []*contracts.EncryptedValue{ + { + Namespace: "test-ns", + Name: "test-name", + Version: 1, + EncryptedPayload: contracts.EncryptedPayload{ + EncryptedData: []byte("corrupt-data-without-delimiter"), + DataKeyID: "", // Empty to trigger migration + }, + }, + }, + } + + migrationExecutor, err := encryption.ProvideEncryptedValueMigrationExecutor( + sut.Database, + tracer, + sut.EncryptedValueStorage, + mockGlobalStore, + ) + require.NoError(t, err) + + rowsAffected, err := migrationExecutor.Execute(t.Context()) + require.Error(t, err) + require.Contains(t, err.Error(), "could not find valid key id in encrypted payload") + require.Equal(t, 0, rowsAffected) + }) + + t.Run("corrupt data - empty encrypted data", func(t *testing.T) { + mockGlobalStore := &mockGlobalEncryptedValueStorage{ + encryptedValues: []*contracts.EncryptedValue{ + { + Namespace: "test-ns", + Name: "test-name", + Version: 1, + EncryptedPayload: contracts.EncryptedPayload{ + EncryptedData: []byte("#dGVzdA#"), // Valid key but no encrypted data after delimiter + DataKeyID: "", // Empty to trigger migration + }, + }, + }, + } + + migrationExecutor, err := encryption.ProvideEncryptedValueMigrationExecutor( + sut.Database, + tracer, + sut.EncryptedValueStorage, + mockGlobalStore, + ) + require.NoError(t, err) + + rowsAffected, err := migrationExecutor.Execute(t.Context()) + require.Error(t, err) + require.Contains(t, err.Error(), "encrypted data is empty") + require.Equal(t, 0, rowsAffected) + }) + + t.Run("corrupt data - invalid base64 key", func(t *testing.T) { + mockGlobalStore := &mockGlobalEncryptedValueStorage{ + encryptedValues: []*contracts.EncryptedValue{ + { + Namespace: "test-ns", + Name: "test-name", + Version: 1, + EncryptedPayload: contracts.EncryptedPayload{ + EncryptedData: []byte("#invalid-base64!@#$%^&*()#somedata"), + DataKeyID: "", // Empty to trigger migration + }, + }, + }, + } + + migrationExecutor, err := encryption.ProvideEncryptedValueMigrationExecutor( + sut.Database, + tracer, + sut.EncryptedValueStorage, + mockGlobalStore, + ) + require.NoError(t, err) + + rowsAffected, err := migrationExecutor.Execute(t.Context()) + require.Error(t, err) + require.Contains(t, err.Error(), "decoding key id") + require.Equal(t, 0, rowsAffected) + }) + + t.Run("update failure", func(t *testing.T) { + mockGlobalStore := &mockGlobalEncryptedValueStorage{ + encryptedValues: []*contracts.EncryptedValue{ + { + Namespace: "nonexistent-ns", + Name: "nonexistent-name", + Version: 999, + EncryptedPayload: contracts.EncryptedPayload{ + EncryptedData: []byte("#dGVzdA#someencrypteddata"), + DataKeyID: "", // Empty to trigger migration + }, + }, + }, + } + + migrationExecutor, err := encryption.ProvideEncryptedValueMigrationExecutor( + sut.Database, + tracer, + sut.EncryptedValueStorage, + mockGlobalStore, + ) + require.NoError(t, err) + + rowsAffected, err := migrationExecutor.Execute(t.Context()) + require.Error(t, err) + require.Contains(t, err.Error(), "updating encrypted value") + require.Equal(t, 0, rowsAffected) + }) + }) +} + +// Helper function that bypasses interfaces and creates data in the legacy format directly in the database. +// The format is "#{encoded_key_id}#{encrypted_data}". +func createLegacyEncryptedData(t *testing.T, sut testutils.Sut, enc cipher.Cipher, namespace, name string, version int64, plaintext string, dataKeyId string) error { + t.Helper() + + encryptedData, err := enc.Encrypt(t.Context(), []byte(plaintext), dataKeyId) + require.NoError(t, err) + + // Encode using the legacy format + const keyIdDelimiter = '#' + prefix := make([]byte, base64.RawStdEncoding.EncodedLen(len(dataKeyId))+2) + base64.RawStdEncoding.Encode(prefix[1:], []byte(dataKeyId)) + prefix[0] = keyIdDelimiter + prefix[len(prefix)-1] = keyIdDelimiter + + blob := make([]byte, len(prefix)+len(encryptedData)) + copy(blob, prefix) + copy(blob[len(prefix):], encryptedData) + + createdTime := time.Now().Unix() + + encryptedValue := &encryption.EncryptedValue{ + Namespace: namespace, + Name: name, + Version: version, + EncryptedData: blob, + DataKeyID: "", + Created: createdTime, + Updated: createdTime, + } + + req := struct { + sqltemplate.SQLTemplate + Row *encryption.EncryptedValue + }{ + SQLTemplate: sqltemplate.New(sqltemplate.DialectForDriver(sut.Database.DriverName())), + Row: encryptedValue, + } + tmpl, err := template.ParseFiles("data/encrypted_value_create.sql") + if err != nil { + return fmt.Errorf("parsing template: %w", err) + } + + query, err := sqltemplate.Execute(tmpl, req) + if err != nil { + return fmt.Errorf("executing template: %w", err) + } + + res, err := sut.Database.ExecContext(t.Context(), query, req.GetArgs()...) + if err != nil { + return fmt.Errorf("inserting row: %w", err) + } + + if rowsAffected, err := res.RowsAffected(); err != nil { + return fmt.Errorf("getting rows affected: %w", err) + } else if rowsAffected != 1 { + return fmt.Errorf("expected 1 row affected, got %d", rowsAffected) + } + + return nil +} + func TestStateMachine(t *testing.T) { t.Parallel() @@ -212,10 +532,14 @@ func TestStateMachine(t *testing.T) { ns := namespaceGen.Draw(t, "ns") name := nameGen.Draw(t, "name") version := versionGen.Draw(t, "version") + dataKeyId := rapid.String().Draw(t, "dataKeyId") plaintext := rapid.String().Draw(t, "plaintext") - _, modelErr := m.create(ns, name, version, []byte(plaintext)) - _, err := sut.EncryptedValueStorage.Create(t.Context(), ns, name, version, []byte(plaintext)) + _, modelErr := m.create(ns, name, version, []byte(plaintext), dataKeyId) + _, err := sut.EncryptedValueStorage.Create(t.Context(), xkube.Namespace(ns), name, version, contracts.EncryptedPayload{ + DataKeyID: dataKeyId, + EncryptedData: []byte(plaintext), + }) if modelErr != nil || err != nil { require.ErrorIs(t, err, modelErr) return @@ -225,10 +549,14 @@ func TestStateMachine(t *testing.T) { ns := namespaceGen.Draw(t, "ns") name := nameGen.Draw(t, "name") version := versionGen.Draw(t, "version") + dataKeyId := rapid.String().Draw(t, "dataKeyId") plaintext := rapid.String().Draw(t, "plaintext") - modelErr := m.update(ns, name, version, []byte(plaintext)) - err := sut.EncryptedValueStorage.Update(t.Context(), ns, name, version, []byte(plaintext)) + modelErr := m.update(ns, name, version, []byte(plaintext), dataKeyId) + err := sut.EncryptedValueStorage.Update(t.Context(), xkube.Namespace(ns), name, version, contracts.EncryptedPayload{ + DataKeyID: dataKeyId, + EncryptedData: []byte(plaintext), + }) if modelErr != nil || err != nil { require.ErrorIs(t, err, modelErr) return @@ -240,7 +568,7 @@ func TestStateMachine(t *testing.T) { version := versionGen.Draw(t, "version") modelValue, modelErr := m.get(ns, name, version) - value, err := sut.EncryptedValueStorage.Get(t.Context(), ns, name, version) + value, err := sut.EncryptedValueStorage.Get(t.Context(), xkube.Namespace(ns), name, version) if modelErr != nil || err != nil { require.ErrorIs(t, err, modelErr) return @@ -258,7 +586,7 @@ func TestStateMachine(t *testing.T) { version := versionGen.Draw(t, "version") modelErr := m.delete(ns, name, version) - err := sut.EncryptedValueStorage.Delete(t.Context(), ns, name, version) + err := sut.EncryptedValueStorage.Delete(t.Context(), xkube.Namespace(ns), name, version) if modelErr != nil || err != nil { require.ErrorIs(t, err, modelErr) return @@ -290,18 +618,19 @@ type entry struct { name string version int64 encryptedData []byte + dataKeyId string } func newModel() *model { return &model{} } -func (m *model) create(namespace, name string, version int64, encryptedData []byte) (*contracts.EncryptedValue, error) { +func (m *model) create(namespace, name string, version int64, encryptedData []byte, dataKeyId string) (*contracts.EncryptedValue, error) { v, err := m.get(namespace, name, version) if err != nil && !errors.Is(err, encryption.ErrEncryptedValueNotFound) { return nil, err } - // The entry being creted already exists + // The entry being created already exists if v != nil { return nil, encryption.ErrEncryptedValueAlreadyExists } @@ -311,20 +640,25 @@ func (m *model) create(namespace, name string, version int64, encryptedData []by name: name, version: version, encryptedData: encryptedData, + dataKeyId: dataKeyId, }) return &contracts.EncryptedValue{ - Namespace: namespace, - Name: name, - Version: version, - EncryptedData: encryptedData, - Created: 1, - Updated: 1, + Namespace: namespace, + Name: name, + Version: version, + EncryptedPayload: contracts.EncryptedPayload{ + DataKeyID: dataKeyId, + EncryptedData: encryptedData, + }, + Created: 1, + Updated: 1, }, nil } -func (m *model) update(namespace, name string, version int64, encryptedData []byte) error { +func (m *model) update(namespace, name string, version int64, encryptedData []byte, dataKeyId string) error { for _, v := range m.entries { if v.namespace == namespace && v.name == name && v.version == version { v.encryptedData = encryptedData + v.dataKeyId = dataKeyId return nil } } @@ -336,12 +670,15 @@ func (m *model) get(namespace, name string, version int64) (*contracts.Encrypted for _, v := range m.entries { if v.namespace == namespace && v.name == name && v.version == version { return &contracts.EncryptedValue{ - Namespace: namespace, - Name: name, - Version: version, - EncryptedData: v.encryptedData, - Created: 1, - Updated: 1, + Namespace: namespace, + Name: name, + Version: version, + EncryptedPayload: contracts.EncryptedPayload{ + DataKeyID: v.dataKeyId, + EncryptedData: v.encryptedData, + }, + Created: 1, + Updated: 1, }, nil } } @@ -354,3 +691,25 @@ func (m *model) delete(namespace, name string, version int64) error { }) return nil } + +// mockGlobalEncryptedValueStorage is a mock implementation of contracts.GlobalEncryptedValueStorage +// used for testing error conditions in the migration executor +type mockGlobalEncryptedValueStorage struct { + encryptedValues []*contracts.EncryptedValue + listAllError error + countAllError error +} + +func (m *mockGlobalEncryptedValueStorage) ListAll(ctx context.Context, opts contracts.ListOpts, untilTime *int64) ([]*contracts.EncryptedValue, error) { + if m.listAllError != nil { + return nil, m.listAllError + } + return m.encryptedValues, nil +} + +func (m *mockGlobalEncryptedValueStorage) CountAll(ctx context.Context, untilTime *int64) (int64, error) { + if m.countAllError != nil { + return 0, m.countAllError + } + return int64(len(m.encryptedValues)), nil +} diff --git a/pkg/storage/secret/encryption/query.go b/pkg/storage/secret/encryption/query.go index 47ea83ce0cd..21e6081a7c7 100644 --- a/pkg/storage/secret/encryption/query.go +++ b/pkg/storage/secret/encryption/query.go @@ -74,6 +74,7 @@ type updateEncryptedValue struct { Name string Version int64 EncryptedData []byte + DataKeyID string Updated int64 } diff --git a/pkg/storage/secret/encryption/query_test.go b/pkg/storage/secret/encryption/query_test.go index a93ff0b77e6..4cbb830f0af 100644 --- a/pkg/storage/secret/encryption/query_test.go +++ b/pkg/storage/secret/encryption/query_test.go @@ -24,6 +24,7 @@ func TestEncryptedValueQueries(t *testing.T) { Name: "n1", Version: 1, EncryptedData: []byte("secret"), + DataKeyID: "test-data-key-id", Created: 1234, Updated: 5678, }, @@ -50,6 +51,7 @@ func TestEncryptedValueQueries(t *testing.T) { Name: "n1", Version: 1, EncryptedData: []byte("secret"), + DataKeyID: "test-data-key-id", Updated: 5679, }, }, diff --git a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_create-create.sql b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_create-create.sql index 7a886b1c6ee..952c04730a8 100755 --- a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_create-create.sql +++ b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_create-create.sql @@ -3,6 +3,7 @@ INSERT INTO `secret_encrypted_value` ( `name`, `version`, `encrypted_data`, + `data_key_id`, `created`, `updated` ) VALUES ( @@ -10,6 +11,7 @@ INSERT INTO `secret_encrypted_value` ( 'n1', 1, '[115 101 99 114 101 116]', + 'test-data-key-id', 1234, 5678 ); diff --git a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_all.sql b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_all.sql index 74b699d45f1..e41d58588d4 100755 --- a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_all.sql +++ b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_all.sql @@ -3,6 +3,7 @@ SELECT `name`, `version`, `encrypted_data`, + `data_key_id`, `created`, `updated` FROM diff --git a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_all_until_time.sql b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_all_until_time.sql index b34496aaf93..c51a40a9a5a 100755 --- a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_all_until_time.sql +++ b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_all_until_time.sql @@ -3,6 +3,7 @@ SELECT `name`, `version`, `encrypted_data`, + `data_key_id`, `created`, `updated` FROM diff --git a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_limit_10_offset_0.sql b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_limit_10_offset_0.sql index 9c33fd6bdbf..de82acacbbd 100755 --- a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_limit_10_offset_0.sql +++ b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_limit_10_offset_0.sql @@ -3,6 +3,7 @@ SELECT `name`, `version`, `encrypted_data`, + `data_key_id`, `created`, `updated` FROM diff --git a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_limit_10_offset_2.sql b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_limit_10_offset_2.sql index d7066395a78..f21f7122646 100755 --- a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_limit_10_offset_2.sql +++ b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_list_all-list_limit_10_offset_2.sql @@ -3,6 +3,7 @@ SELECT `name`, `version`, `encrypted_data`, + `data_key_id`, `created`, `updated` FROM diff --git a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_read-read.sql b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_read-read.sql index bb9faddb8ff..bca020a26d3 100755 --- a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_read-read.sql +++ b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_read-read.sql @@ -3,6 +3,7 @@ SELECT `name`, `version`, `encrypted_data`, + `data_key_id`, `created`, `updated` FROM diff --git a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_update-update.sql b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_update-update.sql index cf3d6897a64..b454cb5165a 100755 --- a/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_update-update.sql +++ b/pkg/storage/secret/encryption/testdata/mysql--encrypted_value_update-update.sql @@ -2,6 +2,7 @@ UPDATE `secret_encrypted_value` SET `encrypted_data` = '[115 101 99 114 101 116]', + `data_key_id` = 'test-data-key-id', `updated` = 5679 WHERE `namespace` = 'ns' AND diff --git a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_create-create.sql b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_create-create.sql index 23d9a0f2331..17576f07232 100755 --- a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_create-create.sql +++ b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_create-create.sql @@ -3,6 +3,7 @@ INSERT INTO "secret_encrypted_value" ( "name", "version", "encrypted_data", + "data_key_id", "created", "updated" ) VALUES ( @@ -10,6 +11,7 @@ INSERT INTO "secret_encrypted_value" ( 'n1', 1, '[115 101 99 114 101 116]', + 'test-data-key-id', 1234, 5678 ); diff --git a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_all.sql b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_all.sql index 74432ebbf69..ee8b9c0a399 100755 --- a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_all.sql +++ b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_all.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_all_until_time.sql b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_all_until_time.sql index 1d7089f751e..661d7f9ea0a 100755 --- a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_all_until_time.sql +++ b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_all_until_time.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_limit_10_offset_0.sql b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_limit_10_offset_0.sql index 6f2bbd0b90f..06782d7e40d 100755 --- a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_limit_10_offset_0.sql +++ b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_limit_10_offset_0.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_limit_10_offset_2.sql b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_limit_10_offset_2.sql index b9f326c8bf0..a64d45afaec 100755 --- a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_limit_10_offset_2.sql +++ b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_list_all-list_limit_10_offset_2.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_read-read.sql b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_read-read.sql index 4bbe1000ce6..437685ae4e7 100755 --- a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_read-read.sql +++ b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_read-read.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_update-update.sql b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_update-update.sql index b2b5e30ecac..0f710f7456d 100755 --- a/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_update-update.sql +++ b/pkg/storage/secret/encryption/testdata/postgres--encrypted_value_update-update.sql @@ -2,6 +2,7 @@ UPDATE "secret_encrypted_value" SET "encrypted_data" = '[115 101 99 114 101 116]', + "data_key_id" = 'test-data-key-id', "updated" = 5679 WHERE "namespace" = 'ns' AND diff --git a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_create-create.sql b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_create-create.sql index 23d9a0f2331..17576f07232 100755 --- a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_create-create.sql +++ b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_create-create.sql @@ -3,6 +3,7 @@ INSERT INTO "secret_encrypted_value" ( "name", "version", "encrypted_data", + "data_key_id", "created", "updated" ) VALUES ( @@ -10,6 +11,7 @@ INSERT INTO "secret_encrypted_value" ( 'n1', 1, '[115 101 99 114 101 116]', + 'test-data-key-id', 1234, 5678 ); diff --git a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_all.sql b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_all.sql index 74432ebbf69..ee8b9c0a399 100755 --- a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_all.sql +++ b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_all.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_all_until_time.sql b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_all_until_time.sql index 1d7089f751e..661d7f9ea0a 100755 --- a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_all_until_time.sql +++ b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_all_until_time.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_limit_10_offset_0.sql b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_limit_10_offset_0.sql index 6f2bbd0b90f..06782d7e40d 100755 --- a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_limit_10_offset_0.sql +++ b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_limit_10_offset_0.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_limit_10_offset_2.sql b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_limit_10_offset_2.sql index b9f326c8bf0..a64d45afaec 100755 --- a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_limit_10_offset_2.sql +++ b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_list_all-list_limit_10_offset_2.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_read-read.sql b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_read-read.sql index 4bbe1000ce6..437685ae4e7 100755 --- a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_read-read.sql +++ b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_read-read.sql @@ -3,6 +3,7 @@ SELECT "name", "version", "encrypted_data", + "data_key_id", "created", "updated" FROM diff --git a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_update-update.sql b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_update-update.sql index b2b5e30ecac..0f710f7456d 100755 --- a/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_update-update.sql +++ b/pkg/storage/secret/encryption/testdata/sqlite--encrypted_value_update-update.sql @@ -2,6 +2,7 @@ UPDATE "secret_encrypted_value" SET "encrypted_data" = '[115 101 99 114 101 116]', + "data_key_id" = 'test-data-key-id', "updated" = 5679 WHERE "namespace" = 'ns' AND diff --git a/pkg/storage/secret/metadata/decrypt_store.go b/pkg/storage/secret/metadata/decrypt_store.go index 413c4a48b2b..ea9d851999d 100644 --- a/pkg/storage/secret/metadata/decrypt_store.go +++ b/pkg/storage/secret/metadata/decrypt_store.go @@ -145,7 +145,7 @@ func (s *decryptStorage) Decrypt(ctx context.Context, namespace xkube.Namespace, return "", fmt.Errorf("failed to get keeper for config: %v (%w)", err, contracts.ErrDecryptFailed) } - exposedValue, err := keeper.Expose(ctx, keeperConfig, namespace.String(), name, sv.Status.Version) + exposedValue, err := keeper.Expose(ctx, keeperConfig, namespace, name, sv.Status.Version) if err != nil { return "", fmt.Errorf("failed to expose secret: %v (%w)", err, contracts.ErrDecryptFailed) } diff --git a/pkg/storage/secret/migrator/migrator.go b/pkg/storage/secret/migrator/migrator.go index 34b56a905df..b08fb766e96 100644 --- a/pkg/storage/secret/migrator/migrator.go +++ b/pkg/storage/secret/migrator/migrator.go @@ -200,4 +200,15 @@ func (*SecretDB) AddMigration(mg *migrator.Migrator) { mg.AddMigration("add lease_created index to "+TableNameSecureValue, migrator.NewAddIndexMigration(secureValueTable, &migrator.Index{ Cols: []string{"lease_created"}, })) + + mg.AddMigration("add data_key_id column to "+TableNameEncryptedValue, migrator.NewAddColumnMigration(encryptedValueTable, &migrator.Column{ + Name: "data_key_id", + Type: migrator.DB_NVarchar, + Length: 100, + Nullable: false, + Default: "''", + })) + mg.AddMigration("add data_key_id index to "+TableNameEncryptedValue, migrator.NewAddIndexMigration(encryptedValueTable, &migrator.Index{ + Cols: []string{"data_key_id"}, + })) } diff --git a/pkg/tests/apis/dashboard/testdata/panel-generate.json b/pkg/tests/apis/dashboard/testdata/panel-generate.json new file mode 100644 index 00000000000..93c7f1d2241 --- /dev/null +++ b/pkg/tests/apis/dashboard/testdata/panel-generate.json @@ -0,0 +1,101 @@ +{ + "kind": "LibraryPanel", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "generateName": "x" + }, + "spec": { + "type": "timeseries", + "pluginVersion": "12.3.0-pre", + "title": "example library panel", + "panelTitle": "simple", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "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", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "cejobd88i85j4d" + }, + "gridPos": { + "w": 12, + "h": 8, + "x": 0, + "y": 0 + }, + "targets": [ + { + "refId": "A", + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "cejobd88i85j4d" + } + } + ] + } +} \ No newline at end of file diff --git a/pkg/tests/apis/dashboard/testdata/panel-test.json b/pkg/tests/apis/dashboard/testdata/panel-test.json new file mode 100644 index 00000000000..d7e13d18653 --- /dev/null +++ b/pkg/tests/apis/dashboard/testdata/panel-test.json @@ -0,0 +1,101 @@ +{ + "kind": "LibraryPanel", + "apiVersion": "dashboard.grafana.app/v0alpha1", + "metadata": { + "name": "test1" + }, + "spec": { + "type": "timeseries", + "pluginVersion": "12.3.0-pre", + "title": "example library panel (test1)", + "panelTitle": "simple", + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "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", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "cejobd88i85j4d" + }, + "gridPos": { + "w": 12, + "h": 8, + "x": 0, + "y": 0 + }, + "targets": [ + { + "refId": "A", + "datasource": { + "type": "grafana-testdata-datasource", + "uid": "cejobd88i85j4d" + } + } + ] + } +} \ No newline at end of file diff --git a/pkg/tests/apis/iam/team_integration_test.go b/pkg/tests/apis/iam/team_integration_test.go index e8bb115dfb7..16d27456e03 100644 --- a/pkg/tests/apis/iam/team_integration_test.go +++ b/pkg/tests/apis/iam/team_integration_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "testing" + "time" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/api/errors" @@ -222,6 +223,36 @@ func doTeamCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHelper) { require.Equal(t, createdUID, fetched.GetName()) require.Equal(t, "default", fetched.GetNamespace()) + + // Cleanup + err = teamClient.Resource.Delete(ctx, createdUID, metav1.DeleteOptions{}) + require.NoError(t, err) + }) + + t.Run("should list teams correctly", func(t *testing.T) { + ctx := context.Background() + + teamClient := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()), + GVR: gvrTeams, + }) + + // For ensuring that it is able to list a team which has external_uid = null and is_provisioned = null + // only matters when legacy storage is involved + env := helper.GetEnv() + res, err := env.SQLStore.GetSqlxSession().Exec(ctx, "INSERT INTO team (org_id, uid, name, email, is_provisioned, external_uid, created, updated) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", + helper.Org1.Admin.Identity.GetOrgID(), "t000000001", "List Team 1", "list-team-1@example.com", nil, nil, time.Now(), time.Now()) + require.NoError(t, err) + require.NotNil(t, res) + + list, err := teamClient.Resource.List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.NotNil(t, list) + + // Cleanup + _, err = env.SQLStore.GetSqlxSession().Exec(ctx, "DELETE FROM team WHERE uid = ?", "t000000001") + require.NoError(t, err) }) } diff --git a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v0alpha1.json index e68f94dda3e..b9c3ee36a91 100644 --- a/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/dashboard.grafana.app-v0alpha1.json @@ -2694,7 +2694,7 @@ "items": { "$ref": "#/components/schemas/com.github.grafana.grafana-plugin-sdk-go.experimental.apis.data.v0alpha1.DataQuery" }, - "x-kubernetes-list-type": "set" + "x-kubernetes-list-type": "atomic" }, "title": { "description": "The title of the library panel", diff --git a/pkg/tests/apis/openapi_snapshots/notifications.alerting.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/notifications.alerting.grafana.app-v0alpha1.json index 7973627042e..2061616fc9a 100644 --- a/pkg/tests/apis/openapi_snapshots/notifications.alerting.grafana.app-v0alpha1.json +++ b/pkg/tests/apis/openapi_snapshots/notifications.alerting.grafana.app-v0alpha1.json @@ -865,296 +865,6 @@ } ] }, - "/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/{namespace}/receivers/{name}/status": { - "get": { - "tags": [ - "Receiver" - ], - "description": "read status of the specified Receiver", - "operationId": "getReceiverStatus", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - } - } - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "Receiver" - } - }, - "put": { - "tags": [ - "Receiver" - ], - "description": "replace status of the specified Receiver", - "operationId": "replaceReceiverStatus", - "parameters": [ - { - "name": "dryRun", - "in": "query", - "description": "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", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldManager", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldValidation", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - } - } - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "Receiver" - } - }, - "patch": { - "tags": [ - "Receiver" - ], - "description": "partially update status of the specified Receiver", - "operationId": "updateReceiverStatus", - "parameters": [ - { - "name": "dryRun", - "in": "query", - "description": "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", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldManager", - "in": "query", - "description": "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).", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldValidation", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "force", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "application/apply-patch+yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/json-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/strategic-merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.Receiver" - } - } - } - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "Receiver" - } - }, - "parameters": [ - { - "name": "name", - "in": "path", - "description": "name of the Receiver", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "namespace", - "in": "path", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "pretty", - "in": "query", - "description": "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).", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ] - }, "/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/{namespace}/routingtrees": { "get": { "tags": [ @@ -1985,296 +1695,6 @@ } ] }, - "/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/{namespace}/routingtrees/{name}/status": { - "get": { - "tags": [ - "RoutingTree" - ], - "description": "read status of the specified RoutingTree", - "operationId": "getRoutingTreeStatus", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - } - } - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "RoutingTree" - } - }, - "put": { - "tags": [ - "RoutingTree" - ], - "description": "replace status of the specified RoutingTree", - "operationId": "replaceRoutingTreeStatus", - "parameters": [ - { - "name": "dryRun", - "in": "query", - "description": "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", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldManager", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldValidation", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - } - } - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "RoutingTree" - } - }, - "patch": { - "tags": [ - "RoutingTree" - ], - "description": "partially update status of the specified RoutingTree", - "operationId": "updateRoutingTreeStatus", - "parameters": [ - { - "name": "dryRun", - "in": "query", - "description": "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", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldManager", - "in": "query", - "description": "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).", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldValidation", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "force", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "application/apply-patch+yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/json-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/strategic-merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree" - } - } - } - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "RoutingTree" - } - }, - "parameters": [ - { - "name": "name", - "in": "path", - "description": "name of the RoutingTree", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "namespace", - "in": "path", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "pretty", - "in": "query", - "description": "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).", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ] - }, "/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/{namespace}/templategroups": { "get": { "tags": [ @@ -3105,296 +2525,6 @@ } ] }, - "/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/{namespace}/templategroups/{name}/status": { - "get": { - "tags": [ - "TemplateGroup" - ], - "description": "read status of the specified TemplateGroup", - "operationId": "getTemplateGroupStatus", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - } - } - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "TemplateGroup" - } - }, - "put": { - "tags": [ - "TemplateGroup" - ], - "description": "replace status of the specified TemplateGroup", - "operationId": "replaceTemplateGroupStatus", - "parameters": [ - { - "name": "dryRun", - "in": "query", - "description": "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", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldManager", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldValidation", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - } - } - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "TemplateGroup" - } - }, - "patch": { - "tags": [ - "TemplateGroup" - ], - "description": "partially update status of the specified TemplateGroup", - "operationId": "updateTemplateGroupStatus", - "parameters": [ - { - "name": "dryRun", - "in": "query", - "description": "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", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldManager", - "in": "query", - "description": "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).", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldValidation", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "force", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "application/apply-patch+yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/json-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/strategic-merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup" - } - } - } - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "TemplateGroup" - } - }, - "parameters": [ - { - "name": "name", - "in": "path", - "description": "name of the TemplateGroup", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "namespace", - "in": "path", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "pretty", - "in": "query", - "description": "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).", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ] - }, "/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/{namespace}/timeintervals": { "get": { "tags": [ @@ -4224,296 +3354,6 @@ } } ] - }, - "/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/{namespace}/timeintervals/{name}/status": { - "get": { - "tags": [ - "TimeInterval" - ], - "description": "read status of the specified TimeInterval", - "operationId": "getTimeIntervalStatus", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - } - } - } - }, - "x-kubernetes-action": "get", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "TimeInterval" - } - }, - "put": { - "tags": [ - "TimeInterval" - ], - "description": "replace status of the specified TimeInterval", - "operationId": "replaceTimeIntervalStatus", - "parameters": [ - { - "name": "dryRun", - "in": "query", - "description": "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", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldManager", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldValidation", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - } - } - } - }, - "x-kubernetes-action": "put", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "TimeInterval" - } - }, - "patch": { - "tags": [ - "TimeInterval" - ], - "description": "partially update status of the specified TimeInterval", - "operationId": "updateTimeIntervalStatus", - "parameters": [ - { - "name": "dryRun", - "in": "query", - "description": "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", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldManager", - "in": "query", - "description": "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).", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "fieldValidation", - "in": "query", - "description": "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.", - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "force", - "in": "query", - "description": "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.", - "schema": { - "type": "boolean", - "uniqueItems": true - } - } - ], - "requestBody": { - "content": { - "application/apply-patch+yaml": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/json-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - }, - "application/strategic-merge-patch+json": { - "schema": { - "$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.Patch" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - } - } - }, - "201": { - "description": "Created", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/vnd.kubernetes.protobuf": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - }, - "application/yaml": { - "schema": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval" - } - } - } - } - }, - "x-kubernetes-action": "patch", - "x-kubernetes-group-version-kind": { - "group": "notifications.alerting.grafana.app", - "version": "v0alpha1", - "kind": "TimeInterval" - } - }, - "parameters": [ - { - "name": "name", - "in": "path", - "description": "name of the TimeInterval", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "namespace", - "in": "path", - "description": "object name and auth scope, such as for teams and projects", - "required": true, - "schema": { - "type": "string", - "uniqueItems": true - } - }, - { - "name": "pretty", - "in": "query", - "description": "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).", - "schema": { - "type": "string", - "uniqueItems": true - } - } - ] } }, "components": { @@ -4545,9 +3385,6 @@ }, "spec": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.ReceiverSpec" - }, - "status": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.ReceiverStatus" } }, "x-kubernetes-group-version-kind": [ @@ -4637,41 +3474,6 @@ } ] }, - "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.ReceiverOperatorState": { - "type": "object", - "required": [ - "lastEvaluation", - "state" - ], - "properties": { - "descriptiveState": { - "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", - "type": "string" - }, - "details": { - "description": "details contains any extra information that is operator-specific", - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": {} - } - }, - "lastEvaluation": { - "description": "lastEvaluation is the ResourceVersion last evaluated", - "type": "string" - }, - "state": { - "description": "state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.", - "type": "string", - "enum": [ - "success", - "in_progress", - "failed" - ] - } - }, - "additionalProperties": false - }, "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.ReceiverSpec": { "type": "object", "required": [ @@ -4691,27 +3493,6 @@ }, "additionalProperties": false }, - "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.ReceiverStatus": { - "type": "object", - "properties": { - "additionalFields": { - "description": "additionalFields is reserved for future use", - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": {} - } - }, - "operatorStates": { - "description": "operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.ReceiverOperatorState" - } - } - }, - "additionalProperties": false - }, "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTree": { "type": "object", "required": [ @@ -4739,9 +3520,6 @@ }, "spec": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTreeSpec" - }, - "status": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTreeStatus" } }, "x-kubernetes-group-version-kind": [ @@ -4821,41 +3599,6 @@ }, "additionalProperties": false }, - "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTreeOperatorState": { - "type": "object", - "required": [ - "lastEvaluation", - "state" - ], - "properties": { - "descriptiveState": { - "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", - "type": "string" - }, - "details": { - "description": "details contains any extra information that is operator-specific", - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": {} - } - }, - "lastEvaluation": { - "description": "lastEvaluation is the ResourceVersion last evaluated", - "type": "string" - }, - "state": { - "description": "state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.", - "type": "string", - "enum": [ - "success", - "in_progress", - "failed" - ] - } - }, - "additionalProperties": false - }, "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTreeRoute": { "type": "object", "required": [ @@ -4956,27 +3699,6 @@ }, "additionalProperties": false }, - "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTreeStatus": { - "type": "object", - "properties": { - "additionalFields": { - "description": "additionalFields is reserved for future use", - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": {} - } - }, - "operatorStates": { - "description": "operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.RoutingTreeOperatorState" - } - } - }, - "additionalProperties": false - }, "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroup": { "type": "object", "required": [ @@ -5004,9 +3726,6 @@ }, "spec": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupSpec" - }, - "status": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupStatus" } }, "x-kubernetes-group-version-kind": [ @@ -5060,41 +3779,6 @@ } ] }, - "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupOperatorState": { - "type": "object", - "required": [ - "lastEvaluation", - "state" - ], - "properties": { - "descriptiveState": { - "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", - "type": "string" - }, - "details": { - "description": "details contains any extra information that is operator-specific", - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": {} - } - }, - "lastEvaluation": { - "description": "lastEvaluation is the ResourceVersion last evaluated", - "type": "string" - }, - "state": { - "description": "state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.", - "type": "string", - "enum": [ - "success", - "in_progress", - "failed" - ] - } - }, - "additionalProperties": false - }, "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupSpec": { "type": "object", "required": [ @@ -5111,27 +3795,6 @@ }, "additionalProperties": false }, - "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupStatus": { - "type": "object", - "properties": { - "additionalFields": { - "description": "additionalFields is reserved for future use", - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": {} - } - }, - "operatorStates": { - "description": "operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupOperatorState" - } - } - }, - "additionalProperties": false - }, "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval": { "type": "object", "required": [ @@ -5159,9 +3822,6 @@ }, "spec": { "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeIntervalSpec" - }, - "status": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeIntervalStatus" } }, "x-kubernetes-group-version-kind": [ @@ -5254,41 +3914,6 @@ } ] }, - "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeIntervalOperatorState": { - "type": "object", - "required": [ - "lastEvaluation", - "state" - ], - "properties": { - "descriptiveState": { - "description": "descriptiveState is an optional more descriptive state field which has no requirements on format", - "type": "string" - }, - "details": { - "description": "details contains any extra information that is operator-specific", - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": {} - } - }, - "lastEvaluation": { - "description": "lastEvaluation is the ResourceVersion last evaluated", - "type": "string" - }, - "state": { - "description": "state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.", - "type": "string", - "enum": [ - "success", - "in_progress", - "failed" - ] - } - }, - "additionalProperties": false - }, "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeIntervalSpec": { "type": "object", "required": [ @@ -5308,27 +3933,6 @@ }, "additionalProperties": false }, - "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeIntervalStatus": { - "type": "object", - "properties": { - "additionalFields": { - "description": "additionalFields is reserved for future use", - "type": "object", - "additionalProperties": { - "type": "object", - "additionalProperties": {} - } - }, - "operatorStates": { - "description": "operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.", - "type": "object", - "additionalProperties": { - "$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeIntervalOperatorState" - } - } - }, - "additionalProperties": false - }, "com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeIntervalTimeRange": { "type": "object", "required": [ diff --git a/pkg/tests/apis/plugins/discovery_test.go b/pkg/tests/apis/plugins/discovery_test.go index 545f2f3bf7d..168aa259dad 100644 --- a/pkg/tests/apis/plugins/discovery_test.go +++ b/pkg/tests/apis/plugins/discovery_test.go @@ -20,19 +20,30 @@ func TestIntegrationPluginsIntegrationDiscovery(t *testing.T) { "freshness": "Current", "resources": [ { - "resource": "plugininstalls", + "resource": "plugins", "responseKind": { "group": "", - "kind": "PluginInstall", + "kind": "Plugin", "version": "" }, "scope": "Namespaced", - "singularResource": "plugininstalls", + "singularResource": "plugins", "subresources": [ { "responseKind": { "group": "", - "kind": "PluginInstall", + "kind": "ResourceCallOptions", + "version": "" + }, + "subresource": "meta", + "verbs": [ + "get" + ] + }, + { + "responseKind": { + "group": "", + "kind": "Plugin", "version": "" }, "subresource": "status", @@ -53,35 +64,6 @@ func TestIntegrationPluginsIntegrationDiscovery(t *testing.T) { "update", "watch" ] - }, - { - "resource": "pluginmetas", - "responseKind": { - "group": "", - "kind": "PluginMeta", - "version": "" - }, - "scope": "Namespaced", - "singularResource": "pluginmeta", - "subresources": [ - { - "responseKind": { - "group": "", - "kind": "PluginMeta", - "version": "" - }, - "subresource": "status", - "verbs": [ - "get", - "patch", - "update" - ] - } - ], - "verbs": [ - "get", - "list" - ] } ] } diff --git a/pkg/tests/apis/plugins/plugininstalls_test.go b/pkg/tests/apis/plugins/plugininstalls_test.go index a9ceecfd7aa..d8c45713337 100644 --- a/pkg/tests/apis/plugins/plugininstalls_test.go +++ b/pkg/tests/apis/plugins/plugininstalls_test.go @@ -16,88 +16,54 @@ import ( "github.com/grafana/grafana/pkg/util/testutil" ) -var gvrPluginInstalls = schema.GroupVersionResource{ +var gvrPlugins = schema.GroupVersionResource{ Group: "plugins.grafana.app", Version: "v0alpha1", - Resource: "plugininstalls", + Resource: "plugins", } func TestMain(m *testing.M) { testsuite.Run(m) } -func TestIntegrationPluginInstalls(t *testing.T) { +func TestIntegrationPlugins(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) - t.Run("create plugin install", func(t *testing.T) { + t.Run("create plugin", func(t *testing.T) { helper := setupHelper(t) ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-create" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + created, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) require.NotNil(t, created) require.Equal(t, pluginName, created.GetName()) }) - t.Run("create plugin install with status is ignored", func(t *testing.T) { - t.Skip("status is not ignored on create. this might require a change in the SDK. skipping for now") - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginInstalls, - }) - pluginName := "test-plugin-create-with-status" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ - "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", - "metadata": {"name": "%s"}, - "spec": {"version": "1.0.0"}, - "status": { - "operatorStates": { - "test-operator": { - "lastEvaluation": "1", - "state": "success" - } - } - } - }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) - require.NoError(t, err) - require.NotNil(t, created) - require.Equal(t, pluginName, created.GetName()) - // Status should be empty as it's ignored on create - status, found, err := unstructured.NestedMap(created.Object, "status") - require.NoError(t, err) - require.True(t, found) // status field should exist - require.Empty(t, status) // but it should be empty - }) - t.Run("get plugin install", func(t *testing.T) { helper := setupHelper(t) ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-get" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + created, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) fetched, err := client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) require.NoError(t, err) @@ -111,16 +77,16 @@ func TestIntegrationPluginInstalls(t *testing.T) { ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-update" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + created, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) updatedSpec := created.DeepCopy() updatedSpec.Object["spec"] = map[string]interface{}{ @@ -132,116 +98,21 @@ func TestIntegrationPluginInstalls(t *testing.T) { require.Equal(t, "2.0.0", updated.Object["spec"].(map[string]interface{})["version"]) }) - t.Run("update plugin install with status is ignored", func(t *testing.T) { - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginInstalls, - }) - pluginName := "test-plugin-update-with-status" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ - "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", - "metadata": {"name": "%s"}, - "spec": {"version": "1.0.0"} - }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) - require.NoError(t, err) - - // Try to update the status via a normal update - withStatus := created.DeepCopy() - withStatus.Object["status"] = map[string]interface{}{ - "operatorStates": map[string]interface{}{ - "test-operator": map[string]interface{}{ - "lastEvaluation": "1", - "state": "success", - }, - }, - } - updated, err := client.Resource.Update(ctx, withStatus, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, updated) - - // The status should not have been updated - status, found, err := unstructured.NestedMap(updated.Object, "status") - require.NoError(t, err) - require.True(t, found) - require.Empty(t, status) - - // also check with get - fetched, err := client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, fetched) - status, found, err = unstructured.NestedMap(fetched.Object, "status") - require.NoError(t, err) - require.True(t, found) - require.Empty(t, status) - }) - - t.Run("update plugin install status", func(t *testing.T) { - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginInstalls, - }) - pluginName := "test-plugin-status" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ - "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", - "metadata": {"name": "%s"}, - "spec": {"version": "1.0.0"} - }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) - require.NoError(t, err) - - // Update the status - status := created.DeepCopy() - statusPayload := map[string]interface{}{ - "operatorStates": map[string]interface{}{ - "test-operator": map[string]interface{}{ - "lastEvaluation": "1", - "state": "success", - }, - }, - } - status.Object["status"] = statusPayload - updated, err := client.Resource.UpdateStatus(ctx, status, metav1.UpdateOptions{}) - require.NoError(t, err) - require.NotNil(t, updated) - - // Check the status on the returned object - actualStatus, found, err := unstructured.NestedMap(updated.Object, "status") - require.NoError(t, err) - require.True(t, found) - require.Equal(t, statusPayload, actualStatus) - - // Get the status to ensure it persisted - fetched, err := client.Resource.Get(ctx, pluginName, metav1.GetOptions{}) - require.NoError(t, err) - require.NotNil(t, fetched) - actualStatus, found, err = unstructured.NestedMap(fetched.Object, "status") - require.NoError(t, err) - require.True(t, found) - require.Equal(t, statusPayload, actualStatus) - }) - t.Run("list plugin installs", func(t *testing.T) { helper := setupHelper(t) ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-list" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - created, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + created, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) list, err := client.Resource.List(ctx, metav1.ListOptions{}) require.NoError(t, err) @@ -254,16 +125,16 @@ func TestIntegrationPluginInstalls(t *testing.T) { ctx := context.Background() client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) pluginName := "test-plugin-delete" - pluginInstall := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ + plugin := helper.LoadYAMLOrJSON(fmt.Sprintf(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "%s"}, "spec": {"version": "1.0.0"} }`, pluginName)) - _, err := client.Resource.Create(ctx, pluginInstall, metav1.CreateOptions{}) + _, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{}) require.NoError(t, err) err = client.Resource.Delete(ctx, pluginName, metav1.DeleteOptions{}) require.NoError(t, err) @@ -281,15 +152,15 @@ func TestIntegrationPluginInstalls(t *testing.T) { t.Run(fmt.Sprintf("with basic role: %s", user.Identity.GetOrgRole()), func(t *testing.T) { client := helper.GetResourceClient(apis.ResourceClientArgs{ User: user, - GVR: gvrPluginInstalls, + GVR: gvrPlugins, }) - pluginInstall := helper.LoadYAMLOrJSON(`{ + plugin := helper.LoadYAMLOrJSON(`{ "apiVersion": "plugins.grafana.app/v0alpha1", - "kind": "PluginInstall", + "kind": "Plugin", "metadata": {"name": "test-plugin"}, "spec": {"version": "1.0.0"} }`) - _, err := client.Resource.Create(context.Background(), pluginInstall, metav1.CreateOptions{}) + _, err := client.Resource.Create(context.Background(), plugin, metav1.CreateOptions{}) statusError := helper.AsStatusError(err) require.Equal(t, metav1.StatusReasonForbidden, statusError.Status().Reason) err = client.Resource.Delete(context.Background(), "test-plugin", metav1.DeleteOptions{}) diff --git a/pkg/tests/apis/plugins/pluginsmeta_test.go b/pkg/tests/apis/plugins/pluginsmeta_test.go deleted file mode 100644 index 742dca3effc..00000000000 --- a/pkg/tests/apis/plugins/pluginsmeta_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package plugins - -import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/grafana/grafana/pkg/tests/apis" - "github.com/grafana/grafana/pkg/util/testutil" -) - -var gvrPluginMeta = schema.GroupVersionResource{ - Group: "plugins.grafana.app", - Version: "v0alpha1", - Resource: "pluginmetas", -} - -func TestIntegrationPluginMeta(t *testing.T) { - testutil.SkipIntegrationTestInShortMode(t) - - t.Run("list plugin metas", func(t *testing.T) { - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginMeta, - }) - list, err := client.Resource.List(ctx, metav1.ListOptions{}) - require.NoError(t, err) - require.NotNil(t, list) - require.Empty(t, list.Items) - }) - - t.Run("get plugin meta", func(t *testing.T) { - helper := setupHelper(t) - ctx := context.Background() - client := helper.GetResourceClient(apis.ResourceClientArgs{ - User: helper.Org1.Admin, - GVR: gvrPluginMeta, - }) - _, err := client.Resource.Get(ctx, "example", metav1.GetOptions{}) - require.Error(t, err) - }) -} diff --git a/pkg/tests/apis/provisioning/secrets_test.go b/pkg/tests/apis/provisioning/secrets_test.go index 6f0399e91f8..e4b55f5fa5f 100644 --- a/pkg/tests/apis/provisioning/secrets_test.go +++ b/pkg/tests/apis/provisioning/secrets_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/tests/testinfra" "github.com/grafana/grafana/pkg/util/testutil" "github.com/stretchr/testify/require" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -16,7 +17,9 @@ import ( func TestIntegrationProvisioning_InlineSecrets(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) - helper := runGrafana(t) + helper := runGrafana(t, func(opts *testinfra.GrafanaOpts) { + opts.SecretsManagerEnableDBMigrations = true + }) createOptions := metav1.CreateOptions{FieldValidation: "Strict"} ctx := context.Background() diff --git a/pkg/tests/apis/shorturl/shorturl_test.go b/pkg/tests/apis/shorturl/shorturl_test.go index 86445846acf..1b6f44dd7e4 100644 --- a/pkg/tests/apis/shorturl/shorturl_test.go +++ b/pkg/tests/apis/shorturl/shorturl_test.go @@ -5,13 +5,14 @@ import ( "fmt" "net/http" "testing" + "time" "github.com/stretchr/testify/assert" "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/schema" + shorturlV1 "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1alpha1" "github.com/grafana/grafana/pkg/api/dtos" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/options" @@ -28,11 +29,7 @@ func TestMain(m *testing.M) { testsuite.Run(m) } -var gvr = schema.GroupVersionResource{ - Group: "shorturl.grafana.app", - Version: "v1alpha1", - Resource: "shorturls", -} +var gvr = shorturlV1.ShortURLKind().GroupVersionResource() var RESOURCEGROUP = gvr.GroupResource().String() @@ -78,29 +75,31 @@ func TestIntegrationShortURL(t *testing.T) { doLegacyOnlyTests(t, helper) }) - for _, mode := range []grafanarest.DualWriterMode{ - grafanarest.Mode1, - grafanarest.Mode2, - // grafanarest.Mode3, TODO: the /goto function needs to use an UpdateStatus client - // grafanarest.Mode4, - } { - t.Run(fmt.Sprintf("with dual write (unified storage, mode %d)", mode), func(t *testing.T) { - helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: false, - DisableAnonymous: true, - APIServerStorageType: options.StorageTypeUnified, - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesShortURLs, - }, - UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ - RESOURCEGROUP: { - DualWriterMode: mode, + t.Run("modes", func(t *testing.T) { + for _, mode := range []grafanarest.DualWriterMode{ + grafanarest.Mode1, + grafanarest.Mode2, + grafanarest.Mode3, + grafanarest.Mode4, + } { + t.Run(fmt.Sprintf("dual write (unified storage, mode %d)", mode), func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: false, + DisableAnonymous: true, + APIServerStorageType: options.StorageTypeUnified, + EnableFeatureToggles: []string{ + featuremgmt.FlagKubernetesShortURLs, }, - }, + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + RESOURCEGROUP: { + DualWriterMode: mode, + }, + }, + }) + doDualWriteTests(t, helper, mode) }) - doDualWriteTests(t, helper, mode) - }) - } + } + }) t.Run("with dual write (unified storage, mode 5)", func(t *testing.T) { helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ @@ -173,7 +172,7 @@ func doLegacyOnlyTests(t *testing.T, helper *apis.K8sTestHelper) { User: client.Args.User, Method: http.MethodGet, Path: "/goto/" + uid + "?orgId=default", - }, (*interface{})(nil)) + }, (*any)(nil)) assert.Equal(t, 302, redirectResponse.Response.StatusCode) }) } @@ -278,21 +277,23 @@ func doDualWriteTests(t *testing.T, helper *apis.K8sTestHelper, mode grafanarest User: client.Args.User, Method: http.MethodGet, Path: "/goto/" + uid + "?orgId=default", - }, (*interface{})(nil)) + }, (*any)(nil)) assert.Equal(t, 302, redirectResponse.Response.StatusCode) - // Verify lastSeenAt was updated (should be > 0 now) - found, err := client.Resource.Get(context.Background(), uid, metav1.GetOptions{}) - require.NoError(t, err) - status, exists := found.Object["status"].(map[string]interface{}) - assert.True(t, exists) - lastSeenAt, exists := status["lastSeenAt"].(int64) - assert.True(t, exists) + require.EventuallyWithT(t, func(t *assert.CollectT) { + // Verify lastSeenAt was updated (should be > 0 now) + found, err := client.Resource.Get(context.Background(), uid, metav1.GetOptions{}) + require.NoError(t, err) - assert.Greater(t, lastSeenAt, int64(0)) + lastSeenAt, exists, err := unstructured.NestedInt64(found.Object, "status", "lastSeenAt") + require.NoError(t, err) + require.True(t, exists) + + require.Greater(t, lastSeenAt, int64(1), "lastSeenAt should be greater than 1 after redirect") + }, time.Second*5, time.Millisecond*75, "lastSeenAt should be updated after redirect") // Clean up - err = client.Resource.Delete(context.Background(), uid, metav1.DeleteOptions{}) + err := client.Resource.Delete(context.Background(), uid, metav1.DeleteOptions{}) require.NoError(t, err) }) } @@ -458,7 +459,7 @@ func doUnifiedOnlyTests(t *testing.T, helper *apis.K8sTestHelper) { User: client.Args.User, Method: http.MethodGet, Path: "/goto/" + uid + "?orgId=default", - }, (*interface{})(nil)) + }, (*any)(nil)) assert.Equal(t, 302, redirectResponse.Response.StatusCode) // Clean up @@ -505,9 +506,9 @@ func getFromBothAPIs(t *testing.T, if legacyShortURL != nil { // If legacy API returns data, verify consistency - spec, ok := k8sResource.Object["spec"].(map[string]interface{}) + spec, ok := k8sResource.Object["spec"].(map[string]any) require.True(t, ok) - status, ok := k8sResource.Object["status"].(map[string]interface{}) + status, ok := k8sResource.Object["status"].(map[string]any) require.True(t, ok) assert.Equal(t, legacyShortURL.Uid, k8sResource.GetName()) assert.Equal(t, legacyShortURL.Path, spec["path"].(string)) diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index b680cae8060..8f00449ac4b 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -552,6 +552,13 @@ func CreateGrafDir(t *testing.T, opts GrafanaOpts) (string, string) { require.NoError(t, err) } + if opts.SecretsManagerEnableDBMigrations { + apiserverSection, err := getOrCreateSection("secrets_manager") + require.NoError(t, err) + _, err = apiserverSection.NewKey("run_secrets_db_migrations", "true") + require.NoError(t, err) + } + dashboardsSection, err := getOrCreateSection("dashboards") require.NoError(t, err) _, err = dashboardsSection.NewKey("min_refresh_interval", "10s") @@ -623,6 +630,7 @@ type GrafanaOpts struct { EnableSCIM bool APIServerRuntimeConfig string DisableControllers bool + SecretsManagerEnableDBMigrations bool // When "unified-grpc" is selected it will also start the grpc server APIServerStorageType options.StorageType diff --git a/pkg/tsdb/cloudwatch/cloudwatch.go b/pkg/tsdb/cloudwatch/cloudwatch.go index 1015393477b..3ebe95515c0 100644 --- a/pkg/tsdb/cloudwatch/cloudwatch.go +++ b/pkg/tsdb/cloudwatch/cloudwatch.go @@ -36,7 +36,7 @@ const ( headerFromAlert = "FromAlert" defaultRegion = "default" - logsQueryMode = "Logs" + queryModeLogs = "Logs" // QueryTypes annotationQuery = "annotationQuery" logAction = "logAction" @@ -45,7 +45,8 @@ const ( type DataQueryJson struct { dataquery.CloudWatchAnnotationQuery - Type string `json:"type,omitempty"` + Type string `json:"type,omitempty"` + LogsMode dataquery.LogsMode `json:"logsMode,omitempty"` } type DataSource struct { @@ -147,12 +148,22 @@ func (ds *DataSource) QueryData(ctx context.Context, req *backend.QueryDataReque if model.QueryMode != "" { queryMode = string(model.QueryMode) } - fromPublicDashboard := model.Type == "" && queryMode == logsQueryMode - isSyncLogQuery := ((fromAlert || fromExpression) && queryMode == logsQueryMode) || fromPublicDashboard + + fromPublicDashboard := model.Type == "" + + isLogInsightsQuery := queryMode == queryModeLogs && (model.LogsMode == "" || model.LogsMode == dataquery.LogsModeInsights) + + isSyncLogQuery := isLogInsightsQuery && ((fromAlert || fromExpression) || fromPublicDashboard) + if isSyncLogQuery { return executeSyncLogQuery(ctx, ds, req) } + isLogsAnomaliesQuery := model.QueryMode == dataquery.CloudWatchQueryModeLogs && model.LogsMode == dataquery.LogsModeAnomalies + if isLogsAnomaliesQuery { + return executeLogAnomaliesQuery(ctx, ds, req) + } + var result *backend.QueryDataResponse switch model.Type { case annotationQuery: diff --git a/pkg/tsdb/cloudwatch/constants/metrics.go b/pkg/tsdb/cloudwatch/constants/metrics.go index f52207001ec..02da8f26a7a 100644 --- a/pkg/tsdb/cloudwatch/constants/metrics.go +++ b/pkg/tsdb/cloudwatch/constants/metrics.go @@ -17,6 +17,7 @@ func Regions() RegionsSet { "ap-southeast-3": {}, "ap-southeast-4": {}, "ap-southeast-5": {}, + "ap-southeast-6": {}, "ap-southeast-7": {}, "ca-central-1": {}, "ca-west-1": {}, @@ -24,6 +25,7 @@ func Regions() RegionsSet { "cn-northwest-1": {}, "eu-central-1": {}, "eu-central-2": {}, + "eu-isoe-west-1": {}, "eu-north-1": {}, "eu-south-1": {}, "eu-south-2": {}, @@ -40,7 +42,11 @@ func Regions() RegionsSet { "us-gov-east-1": {}, "us-gov-west-1": {}, "us-iso-east-1": {}, + "us-iso-west-1": {}, "us-isob-east-1": {}, + "us-isob-west-1": {}, + "us-isof-east-1": {}, + "us-northeast-1": {}, "us-west-1": {}, "us-west-2": {}, } diff --git a/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go b/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go index 76c706094a6..ce8bc54c6c5 100644 --- a/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go +++ b/pkg/tsdb/cloudwatch/kinds/dataquery/types_dataquery_gen.go @@ -285,6 +285,13 @@ func NewQueryEditorOperatorValueType() *QueryEditorOperatorValueType { return NewStringOrBoolOrInt64OrArrayOfQueryEditorOperatorType() } +type LogsMode string + +const ( + LogsModeInsights LogsMode = "Insights" + LogsModeAnomalies LogsMode = "Anomalies" +) + type LogsQueryLanguage string const ( @@ -297,7 +304,9 @@ const ( type CloudWatchLogsQuery struct { // Whether a query is a Metrics, Logs, or Annotations query QueryMode CloudWatchQueryMode `json:"queryMode"` - Id string `json:"id"` + // Whether a query is a Logs Insights or Logs Anomalies query + LogsMode *LogsMode `json:"logsMode,omitempty"` + Id string `json:"id"` // AWS region to query for the logs Region string `json:"region"` // The CloudWatch Logs Insights query to execute @@ -347,6 +356,40 @@ func NewLogGroup() *LogGroup { return &LogGroup{} } +// Shape of a Cloudwatch Logs Anomalies query +type CloudWatchLogsAnomaliesQuery struct { + Id string `json:"id"` + // AWS region to query for the logs + Region string `json:"region"` + // Whether a query is a Metrics, Logs or Annotations query + QueryMode *CloudWatchQueryMode `json:"queryMode,omitempty"` + // Whether a query is a Logs Insights or Logs Anomalies query + LogsMode *LogsMode `json:"logsMode,omitempty"` + // Filter to return only anomalies that are 'SUPPRESSED', 'UNSUPPRESSED', or 'ALL' (default) + SuppressionState *string `json:"suppressionState,omitempty"` + // A unique identifier for the query within the list of targets. + // In server side expressions, the refId is used as a variable name to identify results. + // By default, the UI will assign A->Z; however setting meaningful names may be useful. + RefId string `json:"refId"` + // If hide is set to true, Grafana will filter out the response(s) associated with this query before returning it to the panel. + Hide *bool `json:"hide,omitempty"` + // Specify the query flavor + // TODO make this required and give it a default + QueryType *string `json:"queryType,omitempty"` + // Used to filter only the anomalies found by a certain anomaly detector + AnomalyDetectionARN *string `json:"anomalyDetectionARN,omitempty"` + // For mixed data sources the selected datasource is on the query level. + // For non mixed scenarios this is undefined. + // TODO find a better way to do this ^ that's friendly to schema + // TODO this shouldn't be unknown but DataSourceRef | null + Datasource any `json:"datasource,omitempty"` +} + +// NewCloudWatchLogsAnomaliesQuery creates a new CloudWatchLogsAnomaliesQuery object. +func NewCloudWatchLogsAnomaliesQuery() *CloudWatchLogsAnomaliesQuery { + return &CloudWatchLogsAnomaliesQuery{} +} + // Shape of a CloudWatch Annotation query // TS type is CloudWatchDefaultQuery = Omit & CloudWatchMetricsQuery, declared in veneer // #CloudWatchDefaultQuery: #CloudWatchLogsQuery & #CloudWatchMetricsQuery @cuetsy(kind="type") diff --git a/pkg/tsdb/cloudwatch/log_anomalies_query.go b/pkg/tsdb/cloudwatch/log_anomalies_query.go new file mode 100644 index 00000000000..f39b92eeffa --- /dev/null +++ b/pkg/tsdb/cloudwatch/log_anomalies_query.go @@ -0,0 +1,146 @@ +package cloudwatch + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs" + + cloudwatchLogsTypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/kinds/dataquery" +) + +var executeLogAnomaliesQuery = func(ctx context.Context, ds *DataSource, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + resp := backend.NewQueryDataResponse() + + for _, q := range req.Queries { + var anomaliesQuery dataquery.CloudWatchLogsAnomaliesQuery + err := json.Unmarshal(q.JSON, &anomaliesQuery) + if err != nil { + continue + } + + region := anomaliesQuery.Region + if region == "" || region == defaultRegion { + anomaliesQuery.Region = ds.Settings.Region + } + + logsClient, err := ds.getCWLogsClient(ctx, region) + if err != nil { + return nil, err + } + + listAnomaliesInput := &cloudwatchlogs.ListAnomaliesInput{} + if anomaliesQuery.SuppressionState != nil { + listAnomaliesInput.SuppressionState = getSuppressionState(*anomaliesQuery.SuppressionState) + } + + if anomaliesQuery.AnomalyDetectionARN == nil || *anomaliesQuery.AnomalyDetectionARN != "" { + listAnomaliesInput.AnomalyDetectorArn = anomaliesQuery.AnomalyDetectionARN + } + + response, err := logsClient.ListAnomalies(ctx, listAnomaliesInput) + + if err != nil { + result := backend.NewQueryDataResponse() + result.Responses[q.RefID] = backend.ErrorResponseWithErrorSource(backend.DownstreamError(fmt.Errorf("%v: %w", "failed to call cloudwatch:ListAnomalies", err))) + return result, nil + } + + dataframe, err := logsAnomaliesResultsToDataframes(response) + if err != nil { + return nil, err + } + + respD := resp.Responses[q.RefID] + respD.Frames = data.Frames{dataframe} + resp.Responses[q.RefID] = respD + } + + return resp, nil + +} + +func logsAnomaliesResultsToDataframes(response *cloudwatchlogs.ListAnomaliesOutput) (*data.Frame, error) { + frame := data.NewFrame("Log anomalies") + + if len(response.Anomalies) == 0 { + return frame, nil + } + + n := len(response.Anomalies) + anomalyArns := make([]string, n) + descriptions := make([]string, n) + suppressedStatus := make([]bool, n) + + priorities := make([]string, n) + patterns := make([]string, n) + statuses := make([]string, n) + logGroupArnLists := make([]string, n) + firstSeens := make([]time.Time, n) + lastSeens := make([]time.Time, n) + logTrends := make([]*json.RawMessage, n) + + for i, anomaly := range response.Anomalies { + anomalyArns[i] = *anomaly.AnomalyDetectorArn + descriptions[i] = *anomaly.Description + suppressedStatus[i] = *anomaly.Suppressed + priorities[i] = *anomaly.Priority + if anomaly.PatternString != nil { + patterns[i] = *anomaly.PatternString + } + statuses[i] = string(anomaly.State) + logGroupArnLists[i] = strings.Join(anomaly.LogGroupArnList, ",") + + firstSeens[i] = time.UnixMilli(anomaly.FirstSeen) + + lastSeens[i] = time.UnixMilli(anomaly.LastSeen) + + // data.Frame returned from the backend cannot contain fields of type data.Frames + // so histogram is kept as json.RawMessageto be built as sparkline table cell on the FE + histogramField := anomaly.Histogram + histogramJSON, err := json.Marshal(histogramField) + if err != nil { + logTrends[i] = nil + } else { + rawMsg := json.RawMessage(histogramJSON) + logTrends[i] = &rawMsg + } + } + + newFields := make([]*data.Field, 0, len(response.Anomalies)) + + newFields = append(newFields, data.NewField("state", nil, statuses).SetConfig(&data.FieldConfig{DisplayName: "State"})) + newFields = append(newFields, data.NewField("description", nil, descriptions).SetConfig(&data.FieldConfig{DisplayName: "Anomaly"})) + newFields = append(newFields, data.NewField("priority", nil, priorities).SetConfig(&data.FieldConfig{DisplayName: "Priority"})) + newFields = append(newFields, data.NewField("patternString", nil, patterns).SetConfig(&data.FieldConfig{DisplayName: "Log Pattern"})) + // FE expects the field name to be logTrend in order to identify histogram field for sparkline rendering + newFields = append(newFields, data.NewField("logTrend", nil, logTrends).SetConfig(&data.FieldConfig{DisplayName: "Log Trend"})) + newFields = append(newFields, data.NewField("firstSeen", nil, firstSeens).SetConfig(&data.FieldConfig{DisplayName: "First seen"})) + newFields = append(newFields, data.NewField("lastSeen", nil, lastSeens).SetConfig(&data.FieldConfig{DisplayName: "Last seen"})) + newFields = append(newFields, data.NewField("suppressed", nil, suppressedStatus).SetConfig(&data.FieldConfig{DisplayName: "Suppressed?"})) + newFields = append(newFields, data.NewField("logGroupArnList", nil, logGroupArnLists).SetConfig(&data.FieldConfig{DisplayName: "Log Groups"})) + newFields = append(newFields, data.NewField("anomalyArn", nil, anomalyArns).SetConfig(&data.FieldConfig{DisplayName: "Anomaly Arn"})) + + frame.Fields = newFields + setPreferredVisType(frame, data.VisTypeTable) + return frame, nil +} + +func getSuppressionState(suppressionState string) cloudwatchLogsTypes.SuppressionState { + switch suppressionState { + case "suppressed": + return cloudwatchLogsTypes.SuppressionStateSuppressed + case "unsuppressed": + return cloudwatchLogsTypes.SuppressionStateUnsuppressed + case "all": + return "" + default: + return "" + } +} diff --git a/pkg/tsdb/cloudwatch/log_anomalies_query_test.go b/pkg/tsdb/cloudwatch/log_anomalies_query_test.go new file mode 100644 index 00000000000..d9b3b85314d --- /dev/null +++ b/pkg/tsdb/cloudwatch/log_anomalies_query_test.go @@ -0,0 +1,212 @@ +package cloudwatch + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + cloudwatchLogsTypes "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" + "github.com/stretchr/testify/assert" +) + +func Test_executeLogAnomaliesQuery(t *testing.T) { + origNewCWClient := NewCWClient + t.Cleanup(func() { + NewCWClient = origNewCWClient + }) + + var cli fakeCWLogsClient + NewCWLogsClient = func(aws.Config) models.CWLogsClient { + return &cli + } + + t.Run("getCWLogsClient is called with correct suppression state", func(t *testing.T) { + testcases := []struct { + name string + suppressionStateInQuery string + result cloudwatchLogsTypes.SuppressionState + }{ + { + "suppressed state", + "suppressed", + cloudwatchLogsTypes.SuppressionStateSuppressed, + }, + { + "unsuppressed state", + "unsuppressed", + cloudwatchLogsTypes.SuppressionStateUnsuppressed, + }, + { + "empty state", + "", + "", + }, + { + "all state", + "all", + "", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + cli = fakeCWLogsClient{anomalies: []cloudwatchLogsTypes.Anomaly{}} + ds := newTestDatasource() + + _, err := ds.QueryData(context.Background(), &backend.QueryDataRequest{ + Headers: map[string]string{headerFromAlert: ""}, + PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}}, + Queries: []backend.DataQuery{ + { + TimeRange: backend.TimeRange{From: time.Unix(0, 0), To: time.Unix(1, 0)}, + JSON: json.RawMessage(`{ + "queryMode": "Logs", + "logsMode": "Anomalies", + "suppressionState": "` + tc.suppressionStateInQuery + `", + "region": "us-east-1" + }`), + }, + }, + }) + assert.NoError(t, err) + assert.Equal(t, tc.result, cli.calls.listAnomalies[0].SuppressionState) + }) + } + }) +} + +func Test_executeLogAnomaliesQuery_returns_data_frames(t *testing.T) { + origNewCWClient := NewCWClient + t.Cleanup(func() { + NewCWClient = origNewCWClient + }) + + var cli fakeCWLogsClient + NewCWLogsClient = func(aws.Config) models.CWLogsClient { + return &cli + } + t.Run("returns log anomalies data frames", func(t *testing.T) { + cli = fakeCWLogsClient{anomalies: []cloudwatchLogsTypes.Anomaly{ + { + AnomalyId: aws.String("anomaly-1"), + AnomalyDetectorArn: aws.String("arn:aws:logs:us-east-1:123456789012:anomaly-detector:anomaly-detector-1"), + FirstSeen: 1622505600000, // June 1, 2021 00:00:00 GMT + LastSeen: 1622592000000, // June 2, 2021 00:00:00 GMT + LogGroupArnList: []string{"arn:aws:logs:us-east-1:1234567:log-group-1:id-1", "arn:aws:logs:us-east-1:1234567:log-group-2:id-2"}, + Description: aws.String("Description 1"), + State: cloudwatchLogsTypes.StateActive, + Priority: aws.String("high"), + PatternString: aws.String(`{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite",,"instance":"instance"-5:Token-6,"job":"kubernetes-service-endpoints","pod_name":"pod_name"-9,"prom_metric_type":"counter"}`), + Suppressed: aws.Bool(false), + Histogram: map[string]int64{ + "1622505600000": 5, + "1622519200000": 10, + "1622532800000": 7, + }, + }, + { + AnomalyId: aws.String("anomaly-2"), + AnomalyDetectorArn: aws.String("arn:aws:logs:us-east-1:123456789012:anomaly-detector:anomaly-detector-2"), + FirstSeen: 1622592000000, // June 2, 2021 00:00:00 GMT + LastSeen: 1622678400000, // June 3, 2021 00:00:00 GMT + LogGroupArnList: []string{"arn:aws:logs:us-east-1:1234567:log-group-1:id-3", "arn:aws:logs:us-east-1:1234567:log-group-2:id-4"}, + Description: aws.String("Description 2"), + State: cloudwatchLogsTypes.StateSuppressed, + Priority: aws.String("low"), + PatternString: aws.String(`{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite","dotnet_collection_count_total":"dotnet_collection_count_total"-3}`), + Suppressed: aws.Bool(true), + Histogram: map[string]int64{ + "1622592000000": 3, + }, + }, + }} + + ds := newTestDatasource() + + resp, err := ds.QueryData(context.Background(), &backend.QueryDataRequest{ + Headers: map[string]string{headerFromAlert: ""}, + PluginContext: backend.PluginContext{DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{}}, + Queries: []backend.DataQuery{ + { + TimeRange: backend.TimeRange{From: time.Unix(0, 0), To: time.Unix(1, 0)}, + JSON: json.RawMessage(`{ + "queryMode": "Logs", + "logsMode": "Anomalies", + "suppressionState": "all", + "region": "us-east-1" + }`), + }, + }, + }) + + assert.NoError(t, err) + assert.Len(t, resp.Responses, 1) + for _, r := range resp.Responses { + assert.Len(t, r.Frames, 1) + frame := r.Frames[0] + assert.Equal(t, "Log anomalies", frame.Name) + assert.Len(t, frame.Fields, 10) + + stateField := frame.Fields[0] + assert.Equal(t, "state", stateField.Name) + assert.Equal(t, "Active", stateField.At(0)) + assert.Equal(t, "Suppressed", stateField.At(1)) + + descriptionField := frame.Fields[1] + assert.Equal(t, "description", descriptionField.Name) + assert.Equal(t, "Description 1", descriptionField.At(0)) + assert.Equal(t, "Description 2", descriptionField.At(1)) + + priorityField := frame.Fields[2] + assert.Equal(t, "priority", priorityField.Name) + assert.Equal(t, "high", priorityField.At(0)) + assert.Equal(t, "low", priorityField.At(1)) + + patternStringField := frame.Fields[3] + assert.Equal(t, "patternString", patternStringField.Name) + assert.Equal(t, `{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite",,"instance":"instance"-5:Token-6,"job":"kubernetes-service-endpoints","pod_name":"pod_name"-9,"prom_metric_type":"counter"}`, patternStringField.At(0)) + assert.Equal(t, `{"ClusterName":"PetSite","Namespace":"default","Service":"service-petsite","dotnet_collection_count_total":"dotnet_collection_count_total"-3}`, patternStringField.At(1)) + + histogramField := frame.Fields[4] + assert.Equal(t, "logTrend", histogramField.Name) + + histogram0 := histogramField.At(0).(*json.RawMessage) + var histData0 map[string]int64 + err = json.Unmarshal(*histogram0, &histData0) + assert.NoError(t, err) + assert.Equal(t, int64(5), histData0["1622505600000"]) + assert.Equal(t, int64(10), histData0["1622519200000"]) + assert.Equal(t, int64(7), histData0["1622532800000"]) + + firstSeenField := frame.Fields[5] + assert.Equal(t, "firstSeen", firstSeenField.Name) + assert.Equal(t, time.Unix(1622505600, 0), firstSeenField.At(0)) + assert.Equal(t, time.Unix(1622592000, 0), firstSeenField.At(1)) + + lastSeenField := frame.Fields[6] + assert.Equal(t, "lastSeen", lastSeenField.Name) + assert.Equal(t, time.Unix(1622592000, 0), lastSeenField.At(0)) + assert.Equal(t, time.Unix(1622678400, 0), lastSeenField.At(1)) + + suppressedField := frame.Fields[7] + assert.Equal(t, "suppressed", suppressedField.Name) + assert.Equal(t, false, suppressedField.At(0)) + assert.Equal(t, true, suppressedField.At(1)) + + logGroupArnListField := frame.Fields[8] + assert.Equal(t, "logGroupArnList", logGroupArnListField.Name) + assert.Equal(t, "arn:aws:logs:us-east-1:1234567:log-group-1:id-1,arn:aws:logs:us-east-1:1234567:log-group-2:id-2", logGroupArnListField.At(0)) + assert.Equal(t, "arn:aws:logs:us-east-1:1234567:log-group-1:id-3,arn:aws:logs:us-east-1:1234567:log-group-2:id-4", logGroupArnListField.At(1)) + + anomalyDetectorArnField := frame.Fields[9] + assert.Equal(t, "anomalyArn", anomalyDetectorArnField.Name) + assert.Equal(t, "arn:aws:logs:us-east-1:123456789012:anomaly-detector:anomaly-detector-1", anomalyDetectorArnField.At(0)) + assert.Equal(t, "arn:aws:logs:us-east-1:123456789012:anomaly-detector:anomaly-detector-2", anomalyDetectorArnField.At(1)) + } + }) +} diff --git a/pkg/tsdb/cloudwatch/log_sync_query_test.go b/pkg/tsdb/cloudwatch/log_sync_query_test.go index b1c0b99175e..376464d9340 100644 --- a/pkg/tsdb/cloudwatch/log_sync_query_test.go +++ b/pkg/tsdb/cloudwatch/log_sync_query_test.go @@ -137,7 +137,7 @@ func Test_executeSyncLogQuery(t *testing.T) { executeSyncLogQuery = origExecuteSyncLogQuery }) - t.Run("when query mode is 'Logs' and does not include type or subtype", func(t *testing.T) { + t.Run("when query mode is 'Logs Insights' and does not include type or subtype", func(t *testing.T) { origExecuteSyncLogQuery := executeSyncLogQuery syncCalled := false executeSyncLogQuery = func(ctx context.Context, e *DataSource, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { diff --git a/pkg/tsdb/cloudwatch/mocks/logs.go b/pkg/tsdb/cloudwatch/mocks/logs.go index 7843fdaacd1..da8dd2cc47c 100644 --- a/pkg/tsdb/cloudwatch/mocks/logs.go +++ b/pkg/tsdb/cloudwatch/mocks/logs.go @@ -66,3 +66,9 @@ func (m *MockLogEvents) GetLogEvents(ctx context.Context, input *cloudwatchlogs. return args.Get(0).(*cloudwatchlogs.GetLogEventsOutput), args.Error(1) } + +func (m *MockLogEvents) ListAnomalies(ctx context.Context, input *cloudwatchlogs.ListAnomaliesInput, optFns ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.ListAnomaliesOutput, error) { + args := m.Called(ctx, input, optFns) + + return args.Get(0).(*cloudwatchlogs.ListAnomaliesOutput), args.Error(1) +} diff --git a/pkg/tsdb/cloudwatch/models/api.go b/pkg/tsdb/cloudwatch/models/api.go index e4b487288f1..eed0243b69d 100644 --- a/pkg/tsdb/cloudwatch/models/api.go +++ b/pkg/tsdb/cloudwatch/models/api.go @@ -76,6 +76,7 @@ type CWLogsClient interface { cloudwatchlogs.GetLogEventsAPIClient cloudwatchlogs.DescribeLogGroupsAPIClient + cloudwatchlogs.ListAnomaliesAPIClient } type CWClient interface { diff --git a/pkg/tsdb/cloudwatch/test_utils.go b/pkg/tsdb/cloudwatch/test_utils.go index 82ce33099ee..3f4c797dea4 100644 --- a/pkg/tsdb/cloudwatch/test_utils.go +++ b/pkg/tsdb/cloudwatch/test_utils.go @@ -29,12 +29,15 @@ type fakeCWLogsClient struct { queryResults cloudwatchlogs.GetQueryResultsOutput logGroupsIndex int + + anomalies []cloudwatchlogstypes.Anomaly } type logsQueryCalls struct { startQuery []*cloudwatchlogs.StartQueryInput getEvents []*cloudwatchlogs.GetLogEventsInput describeLogGroups []*cloudwatchlogs.DescribeLogGroupsInput + listAnomalies []*cloudwatchlogs.ListAnomaliesInput } func (m *fakeCWLogsClient) GetQueryResults(_ context.Context, _ *cloudwatchlogs.GetQueryResultsInput, _ ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.GetQueryResultsOutput, error) { @@ -55,6 +58,14 @@ func (m *fakeCWLogsClient) StopQuery(_ context.Context, _ *cloudwatchlogs.StopQu }, nil } +func (m *fakeCWLogsClient) ListAnomalies(_ context.Context, input *cloudwatchlogs.ListAnomaliesInput, _ ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.ListAnomaliesOutput, error) { + m.calls.listAnomalies = append(m.calls.listAnomalies, input) + + return &cloudwatchlogs.ListAnomaliesOutput{ + Anomalies: m.anomalies, + }, nil +} + type mockLogsSyncClient struct { mock.Mock } @@ -80,6 +91,11 @@ func (m *mockLogsSyncClient) StartQuery(ctx context.Context, input *cloudwatchlo return args.Get(0).(*cloudwatchlogs.StartQueryOutput), args.Error(1) } +func (m *mockLogsSyncClient) ListAnomalies(ctx context.Context, input *cloudwatchlogs.ListAnomaliesInput, optFns ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.ListAnomaliesOutput, error) { + args := m.Called(ctx, input, optFns) + return args.Get(0).(*cloudwatchlogs.ListAnomaliesOutput), args.Error(1) +} + func (m *fakeCWLogsClient) DescribeLogGroups(_ context.Context, input *cloudwatchlogs.DescribeLogGroupsInput, _ ...func(*cloudwatchlogs.Options)) (*cloudwatchlogs.DescribeLogGroupsOutput, error) { m.calls.describeLogGroups = append(m.calls.describeLogGroups, input) output := &m.logGroups[m.logGroupsIndex] diff --git a/public/app/api/clients/legacy/index.ts b/public/app/api/clients/legacy/index.ts new file mode 100644 index 00000000000..6a6abcf4054 --- /dev/null +++ b/public/app/api/clients/legacy/index.ts @@ -0,0 +1,6 @@ +import { generatedAPI } from '@grafana/api-clients/rtkq/legacy'; + +export const legacyAPI = generatedAPI.enhanceEndpoints({}); + +// eslint-disable-next-line no-barrel-files/no-barrel-files +export * from '@grafana/api-clients/rtkq/legacy'; diff --git a/public/app/core/components/AppChrome/TopBar/HelpTopBarButton.tsx b/public/app/core/components/AppChrome/TopBar/HelpTopBarButton.tsx index 62aed5080ef..02215cda4fd 100644 --- a/public/app/core/components/AppChrome/TopBar/HelpTopBarButton.tsx +++ b/public/app/core/components/AppChrome/TopBar/HelpTopBarButton.tsx @@ -34,7 +34,12 @@ export const HelpTopBarButton = memo(function HelpTopBarButton({ isSmallScreen } if (isSmallScreen || !enrichedHelpNode.hideFromTabs || interactiveLearningPluginId === undefined) { return ( } placement="bottom-end"> - + ); } @@ -48,6 +53,14 @@ export const HelpTopBarButton = memo(function HelpTopBarButton({ isSmallScreen } icon="question-circle" aria-label={t('navigation.help.aria-label', 'Help')} className={isOpen ? styles.helpButtonActive : undefined} + tooltip={ + isOpen + ? t( + 'navigation.help.interactive-learning.close-tooltip', + 'Close interactive learning, help, and documentation' + ) + : t('navigation.help.interactive-learning.open-tooltip', 'Open interactive learning, help, and documentation') + } onClick={() => { if (isOpen) { setDockedComponentId(undefined); diff --git a/public/app/core/components/FolderFilter/FolderFilter.test.tsx b/public/app/core/components/FolderFilter/FolderFilter.test.tsx new file mode 100644 index 00000000000..06f0cf62a2a --- /dev/null +++ b/public/app/core/components/FolderFilter/FolderFilter.test.tsx @@ -0,0 +1,46 @@ +import { comboboxTestSetup } from 'test/helpers/comboboxTestSetup'; +import { render, screen, testWithFeatureToggles } from 'test/test-utils'; + +import { setBackendSrv } from '@grafana/runtime'; +import { setupMockServer } from '@grafana/test-utils/server'; +import { getFolderFixtures } from '@grafana/test-utils/unstable'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { resetGrafanaSearcher } from 'app/features/search/service/searcher'; + +import { FolderFilter } from './FolderFilter'; +const [_, { folderA, folderB }] = getFolderFixtures(); + +setBackendSrv(backendSrv); +setupMockServer(); +comboboxTestSetup(); + +const fixtures: Array< + [ + // Test title + string, + // Feature toggle setup + Parameters[0], + ] +> = [ + ['app platform APIs enabled', { enable: ['unifiedStorageSearchUI'] }], + ['app platform APIs disabled', {}], +]; + +describe.each(fixtures)('FolderFilter - %s', (_title, featureToggleSetup) => { + beforeEach(() => { + resetGrafanaSearcher(); + }); + + testWithFeatureToggles(featureToggleSetup); + + it('allows selecting folders', async () => { + const onChange = jest.fn(); + const { user } = render(); + + await user.click(screen.getByPlaceholderText('Filter by folder')); + + await user.click(await screen.findByText(folderA.item.title)); + await user.click(await screen.findByText(folderB.item.title)); + expect(onChange).toHaveBeenCalledWith([folderA.item.uid, folderB.item.uid]); + }); +}); diff --git a/public/app/core/components/FolderFilter/FolderFilter.tsx b/public/app/core/components/FolderFilter/FolderFilter.tsx index c98490ea0c7..1f8f3e5557d 100644 --- a/public/app/core/components/FolderFilter/FolderFilter.tsx +++ b/public/app/core/components/FolderFilter/FolderFilter.tsx @@ -1,25 +1,17 @@ -import debounce from 'debounce-promise'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useState } from 'react'; -import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { AsyncMultiSelect, Icon } from '@grafana/ui'; +import { ComboboxOption, MultiCombobox } from '@grafana/ui'; import { getGrafanaSearcher } from 'app/features/search/service/searcher'; -import { FolderInfo } from 'app/types/folders'; export interface FolderFilterProps { - onChange: (folder: FolderInfo[]) => void; - maxMenuHeight?: number; + onChange: (folder: string[]) => void; } -export function FolderFilter({ onChange, maxMenuHeight }: FolderFilterProps): JSX.Element { - const [loading, setLoading] = useState(false); - const getOptions = useCallback((searchString: string) => getFoldersAsOptions(searchString, setLoading), []); - const debouncedLoadOptions = useMemo(() => debounce(getOptions, 300), [getOptions]); - - const [value, setValue] = useState>>([]); +export function FolderFilter({ onChange }: FolderFilterProps): JSX.Element { + const [value, setValue] = useState([]); const onSelectOptionChange = useCallback( - (folders: Array>) => { + (folders: ComboboxOption[]) => { const changedFolderIds = folders.filter((f) => Boolean(f.value)).map((f) => f.value!); onChange(changedFolderIds); setValue(folders); @@ -28,26 +20,21 @@ export function FolderFilter({ onChange, maxMenuHeight }: FolderFilterProps): JS ); return ( - } aria-label={t('folder-filter.select-aria-label', 'Folder filter')} - defaultOptions /> ); } -async function getFoldersAsOptions( - searchString: string, - setLoading: (loading: boolean) => void -): Promise>> { - setLoading(true); +async function getFoldersAsOptions(searchString: string) { // Use searcher as it will handle the logic for using the appropriate API const searcher = getGrafanaSearcher(); const queryResponse = await searcher.search({ @@ -59,13 +46,12 @@ async function getFoldersAsOptions( const options = queryResponse.view.map((item) => ({ label: item.name, - value: { uid: item.uid, title: item.name }, + value: item.uid, })); if (!searchString || 'dashboards'.includes(searchString.toLowerCase())) { - options.unshift({ label: 'Dashboards', value: { uid: 'general', title: 'Dashboards' } }); + options.unshift({ label: 'Dashboards', value: 'general' }); } - setLoading(false); return options; } diff --git a/public/app/core/components/Select/ServiceAccountPicker.tsx b/public/app/core/components/Select/ServiceAccountPicker.tsx index 037404dcb2b..e9b200a4cb0 100644 --- a/public/app/core/components/Select/ServiceAccountPicker.tsx +++ b/public/app/core/components/Select/ServiceAccountPicker.tsx @@ -1,6 +1,6 @@ import debounce from 'debounce-promise'; import { isNil } from 'lodash'; -import { Component } from 'react'; +import { useMemo, useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; @@ -14,66 +14,58 @@ export interface Props { inputId?: string; } -export interface State { - isLoading: boolean; -} +export const ServiceAccountPicker = ({ className, onSelected, inputId }: Props) => { + const [isLoading, setIsLoading] = useState(false); -export class ServiceAccountPicker extends Component { - constructor(props: Props) { - super(props); - this.state = { isLoading: false }; - } + const search = useMemo( + () => + debounce( + async (query?: string) => { + setIsLoading(true); - search = debounce( - async (query?: string) => { - this.setState({ isLoading: true }); + if (isNil(query)) { + query = ''; + } - if (isNil(query)) { - query = ''; - } - - return getBackendSrv() - .get(`/api/serviceaccounts/search?query=${query}&perpage=100`) - .then((result: ServiceAccountsState) => { - return result.serviceAccounts.map((sa) => ({ - id: sa.id, - uid: sa.uid, - value: sa, - label: sa.login, - imgUrl: sa.avatarUrl, - login: sa.login, - })); - }) - .finally(() => { - this.setState({ isLoading: false }); - }); - }, - 300, - { leading: true } + return getBackendSrv() + .get(`/api/serviceaccounts/search?query=${query}&perpage=100`) + .then((result: ServiceAccountsState) => { + return result.serviceAccounts.map((sa) => ({ + id: sa.id, + uid: sa.uid, + value: sa, + label: sa.login, + imgUrl: sa.avatarUrl, + login: sa.login, + })); + }) + .finally(() => { + setIsLoading(false); + }); + }, + 300, + { leading: true } + ), + [] ); - render() { - const { className, onSelected, inputId } = this.props; - const { isLoading } = this.state; - - return ( -
- -
- ); - } -} + return ( +
+ +
+ ); +}; diff --git a/public/app/core/components/Select/UserPicker.tsx b/public/app/core/components/Select/UserPicker.tsx index 5284cbeca5f..e7d4110d09b 100644 --- a/public/app/core/components/Select/UserPicker.tsx +++ b/public/app/core/components/Select/UserPicker.tsx @@ -1,6 +1,6 @@ import debounce from 'debounce-promise'; import { isNil } from 'lodash'; -import { Component } from 'react'; +import { useMemo, useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { t } from '@grafana/i18n'; @@ -14,63 +14,55 @@ export interface Props { inputId?: string; } -export interface State { - isLoading: boolean; -} +export const UserPicker = ({ className, onSelected, inputId }: Props) => { + const [isLoading, setIsLoading] = useState(false); -export class UserPicker extends Component { - constructor(props: Props) { - super(props); - this.state = { isLoading: false }; - } + const search = useMemo( + () => + debounce( + async (query?: string) => { + setIsLoading(true); - search = debounce( - async (query?: string) => { - this.setState({ isLoading: true }); + if (isNil(query)) { + query = ''; + } - if (isNil(query)) { - query = ''; - } - - return getBackendSrv() - .get(`/api/org/users/lookup?query=${query}&limit=100`) - .then((result: OrgUser[]) => { - return result.map((user) => ({ - id: user.userId, - uid: user.uid, - value: user, - label: user.login, - imgUrl: user.avatarUrl, - login: user.login, - })); - }) - .finally(() => { - this.setState({ isLoading: false }); - }); - }, - 300, - { leading: true } + return getBackendSrv() + .get(`/api/org/users/lookup?query=${query}&limit=100`) + .then((result: OrgUser[]) => { + return result.map((user) => ({ + id: user.userId, + uid: user.uid, + value: user, + label: user.login, + imgUrl: user.avatarUrl, + login: user.login, + })); + }) + .finally(() => { + setIsLoading(false); + }); + }, + 300, + { leading: true } + ), + [] ); - render() { - const { className, onSelected, inputId } = this.props; - const { isLoading } = this.state; - - return ( -
- -
- ); - } -} + return ( +
+ +
+ ); +}; diff --git a/public/app/core/components/TimelineChart/TimelineChart.tsx b/public/app/core/components/TimelineChart/TimelineChart.tsx index f4317a127b7..ce6e4a1f4c1 100644 --- a/public/app/core/components/TimelineChart/TimelineChart.tsx +++ b/public/app/core/components/TimelineChart/TimelineChart.tsx @@ -1,4 +1,4 @@ -import { Component } from 'react'; +import { useCallback } from 'react'; import { DataFrame, FALLBACK_COLOR, FieldType, TimeRange } from '@grafana/data'; import { VisibilityMode, TimelineValueAlignment, TooltipDisplayMode, VizTooltipOptions } from '@grafana/schema'; @@ -25,69 +25,76 @@ export interface TimelineProps extends Omit { - getValueColor = (frameIdx: number, fieldIdx: number, value: unknown) => { - const field = this.props.frames[frameIdx]?.fields[fieldIdx]; +export const TimelineChart = (props: TimelineProps) => { + const { frames, timeZone, rowHeight, tooltip, legend, legendItems } = props; - if (field?.display) { - const disp = field.display(value); // will apply color modes - if (disp.color) { - return disp.color; + const getValueColor = useCallback( + (frameIdx: number, fieldIdx: number, value: unknown) => { + const field = frames[frameIdx]?.fields[fieldIdx]; + + if (field?.display) { + const disp = field.display(value); // will apply color modes + if (disp.color) { + return disp.color; + } } - } - return FALLBACK_COLOR; - }; + return FALLBACK_COLOR; + }, + [frames] + ); - prepConfig = (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => { - return preparePlotConfigBuilder({ - frame: alignedFrame, - getTimeRange, - allFrames: this.props.frames, - ...this.props, + const prepConfig = useCallback( + (alignedFrame: DataFrame, allFrames: DataFrame[], getTimeRange: () => TimeRange) => { + return preparePlotConfigBuilder({ + frame: alignedFrame, + getTimeRange, + allFrames: frames, + ...props, - // Ensure timezones is passed as an array - timeZones: Array.isArray(this.props.timeZone) ? this.props.timeZone : [this.props.timeZone], + // Ensure timezones is passed as an array + timeZones: Array.isArray(timeZone) ? timeZone : [timeZone], - // When there is only one row, use the full space - rowHeight: alignedFrame.fields.length > 2 ? this.props.rowHeight : 1, - getValueColor: this.getValueColor, + // When there is only one row, use the full space + rowHeight: alignedFrame.fields.length > 2 ? rowHeight : 1, + getValueColor: getValueColor, - hoverMulti: this.props.tooltip?.mode === TooltipDisplayMode.Multi, - }); - }; + hoverMulti: tooltip?.mode === TooltipDisplayMode.Multi, + }); + }, + [frames, props, timeZone, rowHeight, getValueColor, tooltip] + ); - renderLegend = (config: UPlotConfigBuilder) => { - const { legend, legendItems } = this.props; + const renderLegend = useCallback( + (config: UPlotConfigBuilder) => { + if (!config || !legendItems || !legend || legend.showLegend === false) { + return null; + } - if (!config || !legendItems || !legend || legend.showLegend === false) { - return null; - } + return ( + + + + ); + }, + [legend, legendItems] + ); - return ( - - - - ); - }; - - render() { - return ( - f.type === FieldType.time, - y: (f) => - f.type === FieldType.number || - f.type === FieldType.boolean || - f.type === FieldType.string || - f.type === FieldType.enum, - }} - prepConfig={this.prepConfig} - propsToDiff={propsToDiff} - renderLegend={this.renderLegend} - omitHideFromViz={true} - /> - ); - } -} + return ( + f.type === FieldType.time, + y: (f) => + f.type === FieldType.number || + f.type === FieldType.boolean || + f.type === FieldType.string || + f.type === FieldType.enum, + }} + prepConfig={prepConfig} + propsToDiff={propsToDiff} + renderLegend={renderLegend} + omitHideFromViz={true} + /> + ); +}; diff --git a/public/app/core/reducers/root.ts b/public/app/core/reducers/root.ts index 890879ca7a2..4d26d7bc153 100644 --- a/public/app/core/reducers/root.ts +++ b/public/app/core/reducers/root.ts @@ -3,6 +3,7 @@ import { AnyAction, combineReducers } from 'redux'; import { notificationsAPIv0alpha1, rulesAPIv0alpha1 } from '@grafana/alerting/unstable'; import { allReducers as allApiClientReducers } from '@grafana/api-clients/rtkq'; +import { generatedAPI as legacyAPI } from '@grafana/api-clients/rtkq/legacy'; import sharedReducers from 'app/core/reducers'; import ldapReducers from 'app/features/admin/state/reducers'; import alertingReducers from 'app/features/alerting/state/reducers'; @@ -51,6 +52,7 @@ const rootReducers = { ...templatingReducers, ...supportBundlesReducer, ...authConfigReducers, + [legacyAPI.reducerPath]: legacyAPI.reducer, plugins: pluginsReducer, [alertingApi.reducerPath]: alertingApi.reducer, [notificationsAPIv0alpha1.reducerPath]: notificationsAPIv0alpha1.reducer, diff --git a/public/app/core/utils/shortLinks.ts b/public/app/core/utils/shortLinks.ts index c01988d2262..011b5c66baa 100644 --- a/public/app/core/utils/shortLinks.ts +++ b/public/app/core/utils/shortLinks.ts @@ -1,10 +1,10 @@ import memoizeOne from 'memoize-one'; -import { generatedAPI } from '@grafana/api-clients/rtkq/shorturl/v1alpha1'; import { AbsoluteTimeRange, LogRowModel, UrlQueryMap } from '@grafana/data'; import { t } from '@grafana/i18n'; import { getBackendSrv, config, locationService } from '@grafana/runtime'; import { sceneGraph, SceneTimeRangeLike, VizPanel } from '@grafana/scenes'; +import { shortURLAPIv1alpha1 } from 'app/api/clients/shorturl/v1alpha1'; import { notifyApp } from 'app/core/actions'; import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; @@ -46,7 +46,7 @@ export const createShortLink = async function (path: string) { if (config.featureToggles.useKubernetesShortURLsAPI) { // Use RTK API - it handles caching/failures/retries automatically const result = await dispatch( - generatedAPI.endpoints.createShortUrl.initiate({ + shortURLAPIv1alpha1.endpoints.createShortUrl.initiate({ shortUrl: { apiVersion: 'shorturl.grafana.app/v1alpha1', kind: 'ShortURL', diff --git a/public/app/features/admin/UserLdapSyncInfo.tsx b/public/app/features/admin/UserLdapSyncInfo.tsx index 1baf9e219a2..4277fa4c814 100644 --- a/public/app/features/admin/UserLdapSyncInfo.tsx +++ b/public/app/features/admin/UserLdapSyncInfo.tsx @@ -1,4 +1,4 @@ -import { PureComponent } from 'react'; +import { memo } from 'react'; import { dateTimeFormat } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; @@ -16,80 +16,72 @@ interface Props { onUserSync: () => void; } -interface State {} - const format = 'dddd YYYY-MM-DD HH:mm zz'; const debugLDAPMappingBaseURL = '/admin/authentication/ldap'; -export class UserLdapSyncInfo extends PureComponent { - onUserSync = () => { - this.props.onUserSync(); - }; +export const UserLdapSyncInfo = memo(({ ldapSyncInfo, user, onUserSync }: Props) => { + const nextSyncSuccessful = ldapSyncInfo && ldapSyncInfo.nextSync; + const nextSyncTime = nextSyncSuccessful ? dateTimeFormat(ldapSyncInfo.nextSync, { format }) : ''; + const debugLDAPMappingURL = `${debugLDAPMappingBaseURL}?username=${user && user.login}`; + const canReadLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersRead); + const canSyncLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersSync); - render() { - const { ldapSyncInfo, user } = this.props; - const nextSyncSuccessful = ldapSyncInfo && ldapSyncInfo.nextSync; - const nextSyncTime = nextSyncSuccessful ? dateTimeFormat(ldapSyncInfo.nextSync, { format }) : ''; - const debugLDAPMappingURL = `${debugLDAPMappingBaseURL}?username=${user && user.login}`; - const canReadLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersRead); - const canSyncLDAPUser = contextSrv.hasPermission(AccessControlAction.LDAPUsersSync); - - return ( - <> -

- LDAP Synchronisation -

-
-
-
- - - - - - - - - - - -
- External sync - - - User synced via LDAP. Some changes must be done in LDAP or mappings. - - - -
- Next scheduled synchronization - - {ldapSyncInfo.enabled ? ( - nextSyncTime - ) : ( - Not enabled - )} -
-
-
- {canSyncLDAPUser && ( - - )} - {canReadLDAPUser && ( - - Debug LDAP Mapping - - )} -
+ return ( + <> +

+ LDAP Synchronisation +

+
+
+ + + + + + + + + + + + +
+ External sync + + + User synced via LDAP. Some changes must be done in LDAP or mappings. + + + +
+ Next scheduled synchronization + + {ldapSyncInfo.enabled ? ( + nextSyncTime + ) : ( + Not enabled + )} +
- - ); - } -} +
+ {canSyncLDAPUser && ( + + )} + {canReadLDAPUser && ( + + Debug LDAP Mapping + + )} +
+
+ + ); +}); +UserLdapSyncInfo.displayName = 'UserLdapSyncInfo'; diff --git a/public/app/features/admin/UserOrgs.tsx b/public/app/features/admin/UserOrgs.tsx index b7083bf7051..a0169a1022a 100644 --- a/public/app/features/admin/UserOrgs.tsx +++ b/public/app/features/admin/UserOrgs.tsx @@ -1,5 +1,5 @@ import { css, cx } from '@emotion/css'; -import { createRef, PureComponent, ReactElement } from 'react'; +import { memo, PureComponent, ReactElement, useEffect, useRef, useState } from 'react'; import { GrafanaTheme2, OrgRole } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; @@ -10,10 +10,8 @@ import { Icon, Modal, stylesFactory, - Themeable2, Tooltip, useStyles2, - withTheme2, Stack, TextLink, } from '@grafana/ui'; @@ -37,73 +35,63 @@ interface Props { onOrgAdd: (orgId: number, role: OrgRole) => void; } -interface State { - showAddOrgModal: boolean; -} +export const UserOrgs = memo(({ user, orgs, isExternalUser, onOrgRoleChange, onOrgRemove, onOrgAdd }: Props) => { + const [showAddOrgModal, setShowAddOrgModal] = useState(false); + const addToOrgButtonRef = useRef(null); -export class UserOrgs extends PureComponent { - addToOrgButtonRef = createRef(); - state = { - showAddOrgModal: false, + const showOrgAddModal = () => { + setShowAddOrgModal(true); }; - showOrgAddModal = () => { - this.setState({ showAddOrgModal: true }); + const dismissOrgAddModal = () => { + setShowAddOrgModal(false); + addToOrgButtonRef.current?.focus(); }; - dismissOrgAddModal = () => { - this.setState({ showAddOrgModal: false }, () => { - this.addToOrgButtonRef.current?.focus(); - }); - }; + const canAddToOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersAdd) && !isExternalUser; - render() { - const { user, orgs, isExternalUser, onOrgRoleChange, onOrgRemove, onOrgAdd } = this.props; - const { showAddOrgModal } = this.state; + return ( +
+

+ Organizations +

+ + + + {orgs.map((org, index) => ( + + ))} + +
- const canAddToOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersAdd) && !isExternalUser; - return ( -
-

- Organizations -

- - - - {orgs.map((org, index) => ( - - ))} - -
+
+ {canAddToOrg && ( + + )} +
+ +
+
+ ); +}); +UserOrgs.displayName = 'UserOrgs'; -
- {canAddToOrg && ( - - )} -
- -
-
- ); - } -} - -const getOrgRowStyles = stylesFactory((theme: GrafanaTheme2) => { +const getOrgRowStyles = (theme: GrafanaTheme2) => { return { removeButton: css({ marginRight: '0.6rem', @@ -130,9 +118,9 @@ const getOrgRowStyles = stylesFactory((theme: GrafanaTheme2) => { marginRight: theme.spacing(1), }), }; -}); +}; -interface OrgRowProps extends Themeable2 { +interface OrgRowProps { user?: UserDTO; org: UserOrg; isExternalUser?: boolean; @@ -140,124 +128,116 @@ interface OrgRowProps extends Themeable2 { onOrgRoleChange: (orgId: number, newRole: OrgRole) => void; } -class UnThemedOrgRow extends PureComponent { - state = { - currentRole: this.props.org.role, - isChangingRole: false, - roleOptions: [], - }; +const OrgRow = memo(({ user, org, isExternalUser, onOrgRemove, onOrgRoleChange }: OrgRowProps) => { + const [currentRole, setCurrentRole] = useState(org.role); + const [isChangingRole, setIsChangingRole] = useState(false); + const [roleOptions, setRoleOptions] = useState([]); + const styles = useStyles2(getOrgRowStyles); - componentDidMount() { + useEffect(() => { if (contextSrv.licensedAccessControlEnabled()) { if (contextSrv.hasPermission(AccessControlAction.ActionRolesList)) { - fetchRoleOptions(this.props.org.orgId) - .then((roles) => this.setState({ roleOptions: roles })) + fetchRoleOptions(org.orgId) + .then((roles) => setRoleOptions(roles)) .catch((e) => console.error(e)); } } - } + }, [org.orgId]); - onOrgRemove = async () => { - const { org } = this.props; - this.props.onOrgRemove(org.orgId); + const handleOrgRemove = async () => { + onOrgRemove(org.orgId); }; - onChangeRoleClick = () => { - const { org } = this.props; - this.setState({ isChangingRole: true, currentRole: org.role }); + const handleChangeRoleClick = () => { + setIsChangingRole(true); + setCurrentRole(org.role); }; - onOrgRoleChange = (newRole: OrgRole) => { - this.setState({ currentRole: newRole }); + const handleOrgRoleChange = (newRole: OrgRole) => { + setCurrentRole(newRole); }; - onOrgRoleSave = () => { - this.props.onOrgRoleChange(this.props.org.orgId, this.state.currentRole); + const handleOrgRoleSave = () => { + onOrgRoleChange(org.orgId, currentRole); }; - onCancelClick = () => { - this.setState({ isChangingRole: false }); + const handleCancelClick = () => { + setIsChangingRole(false); }; - onBasicRoleChange = (newRole: OrgRole) => { - this.props.onOrgRoleChange(this.props.org.orgId, newRole); + const handleBasicRoleChange = (newRole: OrgRole) => { + onOrgRoleChange(org.orgId, newRole); }; - render() { - const { user, org, isExternalUser, theme } = this.props; - const authSource = user?.authLabels?.length && user?.authLabels[0]; - const lockMessage = authSource ? `Synced via ${authSource}` : ''; - const { currentRole, isChangingRole } = this.state; - const styles = getOrgRowStyles(theme); - const labelClass = cx('width-16', styles.label); - const canChangeRole = contextSrv.hasPermission(AccessControlAction.OrgUsersWrite); - const canRemoveFromOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersRemove) && !isExternalUser; - const rolePickerDisabled = isExternalUser || !canChangeRole; + const authSource = user?.authLabels?.length && user?.authLabels[0]; + const lockMessage = authSource ? `Synced via ${authSource}` : ''; + const labelClass = cx('width-16', styles.label); + const canChangeRole = contextSrv.hasPermission(AccessControlAction.OrgUsersWrite); + const canRemoveFromOrg = contextSrv.hasPermission(AccessControlAction.OrgUsersRemove) && !isExternalUser; + const rolePickerDisabled = isExternalUser || !canChangeRole; - const inputId = `${org.name}-input`; - return ( - - - - - {contextSrv.licensedAccessControlEnabled() ? ( - -
-
- -
- {isExternalUser && } + const inputId = `${org.name}-input`; + return ( + + + + + {contextSrv.licensedAccessControlEnabled() ? ( + +
+
+
- - ) : ( - <> - {isChangingRole ? ( - - - - ) : ( - {org.role} - )} - - {canChangeRole && ( - - )} - - - )} - - {canRemoveFromOrg && ( - - {t('admin.user-orgs.remove-button', 'Remove from organization')} - - )} + {isExternalUser && } +
- - ); - } -} - -const OrgRow = withTheme2(UnThemedOrgRow); + ) : ( + <> + {isChangingRole ? ( + + + + ) : ( + {org.role} + )} + + {canChangeRole && ( + + )} + + + )} + + {canRemoveFromOrg && ( + + {t('admin.user-orgs.remove-button', 'Remove from organization')} + + )} + + + ); +}); +OrgRow.displayName = 'OrgRow'; const getAddToOrgModalStyles = stylesFactory(() => ({ modal: css({ diff --git a/public/app/features/admin/UserSessions.tsx b/public/app/features/admin/UserSessions.tsx index f05278d7884..16fadaa8313 100644 --- a/public/app/features/admin/UserSessions.tsx +++ b/public/app/features/admin/UserSessions.tsx @@ -1,4 +1,4 @@ -import { createRef, PureComponent } from 'react'; +import { memo, useRef, useState } from 'react'; import { Trans, t } from '@grafana/i18n'; import { ConfirmButton, ConfirmModal, Button, Stack } from '@grafana/ui'; @@ -15,120 +15,107 @@ interface Props { onAllSessionsRevoke: () => void; } -interface State { - showLogoutModal: boolean; -} +export const UserSessions = memo(({ sessions, onSessionRevoke, onAllSessionsRevoke }: Props) => { + const [showLogoutModal, setShowLogoutModal] = useState(false); + const forceAllLogoutButton = useRef(null); -class BaseUserSessions extends PureComponent { - forceAllLogoutButton = createRef(); - state: State = { - showLogoutModal: false, + const showLogoutConfirmationModal = () => { + setShowLogoutModal(true); }; - showLogoutConfirmationModal = () => { - this.setState({ showLogoutModal: true }); + const dismissLogoutConfirmationModal = () => { + setShowLogoutModal(false); + forceAllLogoutButton.current?.focus(); }; - dismissLogoutConfirmationModal = () => { - this.setState({ showLogoutModal: false }, () => { - this.forceAllLogoutButton.current?.focus(); - }); - }; - - onSessionRevoke = (id: number) => { + const handleSessionRevoke = (id: number) => { return () => { - this.props.onSessionRevoke(id); + onSessionRevoke(id); }; }; - onAllSessionsRevoke = () => { - this.setState({ showLogoutModal: false }); - this.props.onAllSessionsRevoke(); + const handleAllSessionsRevoke = () => { + setShowLogoutModal(false); + onAllSessionsRevoke(); }; - render() { - const { sessions } = this.props; - const { showLogoutModal } = this.state; + const canLogout = contextSrv.hasPermission(AccessControlAction.UsersLogout); - const canLogout = contextSrv.hasPermission(AccessControlAction.UsersLogout); + return ( +
+

+ Sessions +

+ +
+ + + + + + + + + + + + {sessions && + sessions.map((session, index) => ( + + + + + + + + + ))} + +
+ Last seen + + Logged on + + IP address + + Browser and OS + + Identity Provider +
{session.isActive ? t('admin.user-sessions.now', 'Now') : session.seenAt}{formatDate(session.createdAt, { dateStyle: 'long' })}{session.clientIp}{`${session.browser} on ${session.os} ${session.osVersion}`} + {session.authModule && } + + {canLogout && ( + + {t('admin.user-sessions.force-logout-button', 'Force logout')} + + )} +
+
- return ( -
-

- Sessions -

- -
- - - - - - - - - - - - {sessions && - sessions.map((session, index) => ( - - - - - - - - - ))} - -
- Last seen - - Logged on - - IP address - - Browser and OS - - Identity Provider -
{session.isActive ? t('admin.user-sessions.now', 'Now') : session.seenAt}{formatDate(session.createdAt, { dateStyle: 'long' })}{session.clientIp}{`${session.browser} on ${session.os} ${session.osVersion}`} - {session.authModule && } - - {canLogout && ( - - {t('admin.user-sessions.force-logout-button', 'Force logout')} - - )} -
-
- -
- {canLogout && sessions.length > 0 && ( - +
+ {canLogout && sessions.length > 0 && ( + + )} + -
- -
- ); - } -} - -export const UserSessions = BaseUserSessions; + confirmText={t('admin.base-user-sessions.confirmText-force-logout', 'Force logout')} + onConfirm={handleAllSessionsRevoke} + onDismiss={dismissLogoutConfirmationModal} + /> +
+
+
+ ); +}); +UserSessions.displayName = 'UserSessions'; diff --git a/public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.tsx b/public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.tsx index 4f2c364859a..bf2a253d7e8 100644 --- a/public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.tsx +++ b/public/app/features/alerting/unified/components/import-to-gma/ConfirmConvertModal.tsx @@ -385,7 +385,6 @@ function TargetFolderNotEmptyWarning({ targetFolderRules }: { targetFolderRules: )} isOpen={showTargetRules} onToggle={toggleShowTargetRules} - collapsible={true} > diff --git a/public/app/features/alerting/unified/components/import-to-gma/ImportToGMARules.tsx b/public/app/features/alerting/unified/components/import-to-gma/ImportToGMARules.tsx index bfdb5b3303c..d88d446642d 100644 --- a/public/app/features/alerting/unified/components/import-to-gma/ImportToGMARules.tsx +++ b/public/app/features/alerting/unified/components/import-to-gma/ImportToGMARules.tsx @@ -150,7 +150,6 @@ const ImportToGMARules = () => { label={t('alerting.import-to-gma.additional-settings', 'Additional settings')} isOpen={optionsShowing} onToggle={toggleOptions} - collapsible={true} > diff --git a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx index dc87060c03d..61ce391d779 100644 --- a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx @@ -130,7 +130,6 @@ export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmi /> { - const [ref, { width }] = useMeasure(); - - const ticks = useMemo(() => { - const xScale = scaleTime().domain(domain).range([0, width]).nice(0); - const tickFormatter = xScale.tickFormat(); - - return xScale.ticks(5).map((value) => ({ - value: tickFormatter(value), - xOffset: xScale(value), - })); - }, [domain, width]); - - return ( -
- - {ticks.map((tick) => ( - - {tick.value} - - ))} - -
- ); -}; diff --git a/public/app/features/alerting/unified/triage/Workbench.tsx b/public/app/features/alerting/unified/triage/Workbench.tsx index 85a4788a706..86ff6c03a1e 100644 --- a/public/app/features/alerting/unified/triage/Workbench.tsx +++ b/public/app/features/alerting/unified/triage/Workbench.tsx @@ -4,15 +4,12 @@ import { useState } from 'react'; import { useMeasure } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; -import { t } from '@grafana/i18n'; import { SceneQueryRunner } from '@grafana/scenes'; import { ScrollContainer, useSplitter, useStyles2 } from '@grafana/ui'; import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants'; -import { EditorColumnHeader } from '../components/EditorColumnHeader'; import LoadMoreHelper from '../rule-list/LoadMoreHelper'; -import { TimelineHeader } from './Timeline'; import { WorkbenchProvider } from './WorkbenchContext'; import { AlertRuleRow } from './rows/AlertRuleRow'; import { FolderGroupRow } from './rows/FolderGroupRow'; @@ -26,7 +23,7 @@ import { Domain, Filter, WorkbenchRow } from './types'; type WorkbenchProps = { domain: Domain; data: WorkbenchRow[]; - groupBy?: string[]; // @TODO proper type + groupBy?: string[]; filterBy?: Filter[]; queryRunner: SceneQueryRunner; }; @@ -39,13 +36,30 @@ function renderWorkbenchRow( leftColumnWidth: number, domain: Domain, key: React.Key, + enableFolderMeta: boolean, depth = 0 ): React.ReactElement { if (row.type === 'alertRule') { - return ; + return ( + + ); } else { const children = row.rows.map((childRow, childIndex) => - renderWorkbenchRow(childRow, leftColumnWidth, domain, `${key}-${generateRowKey(childRow, childIndex)}`, depth + 1) + renderWorkbenchRow( + childRow, + leftColumnWidth, + domain, + `${key}-${generateRowKey(childRow, childIndex)}`, + enableFolderMeta, + depth + 1 + ) ); // Check if this is a grafana_folder group and use FolderGroupRow @@ -102,11 +116,14 @@ function renderWorkbenchRow( │ │ │ │ └─────────────────────────┘ └───────────────────────────────────┘ */ -export function Workbench({ domain, data, queryRunner }: WorkbenchProps) { +export function Workbench({ domain, data, queryRunner, groupBy }: WorkbenchProps) { const styles = useStyles2(getStyles); const isLoading = !queryRunner.isDataReadyToDisplay(); const [pageIndex, setPageIndex] = useState(1); + + // Calculate once: show folder metadata only if not grouping by grafana_folder + const enableFolderMeta = !groupBy?.includes('grafana_folder'); // splitter for template and payload editor const splitter = useSplitter({ direction: 'row', @@ -141,12 +158,6 @@ export function Workbench({ domain, data, queryRunner }: WorkbenchProps) {
-
- - - - -
{/* Render actual data */}
@@ -160,7 +171,7 @@ export function Workbench({ domain, data, queryRunner }: WorkbenchProps) { ) : ( dataSlice.map((row, index) => { const rowKey = generateRowKey(row, index); - return renderWorkbenchRow(row, leftColumnWidth, domain, rowKey); + return renderWorkbenchRow(row, leftColumnWidth, domain, rowKey, enableFolderMeta); }) )} {hasMore && setPageIndex((prevIndex) => prevIndex + 1)} />} diff --git a/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx index 704c4d54c47..617f097ce89 100644 --- a/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx +++ b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawer.tsx @@ -3,12 +3,11 @@ import { orderBy } from 'lodash'; import { Fragment, useMemo } from 'react'; import { useMeasure } from 'react-use'; -import { AlertLabels } from '@grafana/alerting/unstable'; import { GrafanaTheme2, Labels } from '@grafana/data'; import { t } from '@grafana/i18n'; import { isFetchError } from '@grafana/runtime'; import { TimeRangePicker, useTimeRange } from '@grafana/scenes-react'; -import { Alert, Box, Drawer, Icon, LoadingBar, Stack, Text, useStyles2 } from '@grafana/ui'; +import { Alert, Box, Drawer, Icon, LoadingBar, LoadingPlaceholder, Stack, Text, useStyles2 } from '@grafana/ui'; import { AlertQuery, GrafanaRuleDefinition } from 'app/types/unified-alerting-dto'; import { alertRuleApi } from '../../api/alertRuleApi'; @@ -19,6 +18,7 @@ import { LogRecord, historyDataFrameToLogRecords } from '../../components/rules/ import { isAlertQueryOfAlertData } from '../../rule-editor/formProcessing'; import { stringifyErrorLike } from '../../utils/misc'; +import { InstanceDetailsDrawerTitle } from './InstanceDetailsDrawerTitle'; import { QueryVisualization } from './QueryVisualization'; import { convertStateHistoryToAnnotations } from './stateHistoryUtils'; @@ -66,7 +66,7 @@ export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: Inst if (error) { return ( - + } onClose={onClose} size="md"> ); @@ -74,17 +74,17 @@ export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: Inst if (loading || !rule) { return ( - -
{t('alerting.common.loading', 'Loading...')}
+ } onClose={onClose} size="md"> + ); } return ( } onClose={onClose} - size="lg" + size="md" > @@ -106,10 +106,6 @@ export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: Inst )} - - - - {t('alerting.instance-details.state-history', 'Recent State Changes')} {stateHistoryFetching && } @@ -139,6 +135,27 @@ export function InstanceDetailsDrawer({ ruleUID, instanceLabels, onClose }: Inst ); } +export interface InstanceLocationProps { + folderTitle: string; + groupName: string; + ruleName: string; +} + +export function InstanceLocation({ folderTitle, groupName, ruleName }: InstanceLocationProps) { + return ( + + + + {folderTitle} + + {groupName} + + {ruleName} + + + ); +} + function extractQueryDetails(rule: GrafanaRuleDefinition) { const dataQueries = rule.data.filter((query: AlertQuery) => isAlertQueryOfAlertData(query)); diff --git a/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawerTitle.tsx b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawerTitle.tsx new file mode 100644 index 00000000000..1b07cdb284f --- /dev/null +++ b/public/app/features/alerting/unified/triage/instance-details/InstanceDetailsDrawerTitle.tsx @@ -0,0 +1,39 @@ +import { AlertLabels } from '@grafana/alerting/unstable'; +import { Labels } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { Box, Stack, Text } from '@grafana/ui'; +import { GrafanaRuleDefinition } from 'app/types/unified-alerting-dto'; + +import { stringifyFolder, useFolder } from '../../hooks/useFolder'; + +import { InstanceLocation } from './InstanceDetailsDrawer'; + +interface InstanceDetailsDrawerTitleProps { + instanceLabels: Labels; + rule?: GrafanaRuleDefinition; +} + +export function InstanceDetailsDrawerTitle({ instanceLabels, rule }: InstanceDetailsDrawerTitleProps) { + const { folder } = useFolder(rule?.namespace_uid); + + return ( + + + Instance details + + + + {Object.keys(instanceLabels).length > 0 ? ( + + ) : ( + {t('alerting.triage.no-labels', 'No labels')} + )} + + + + {folder && rule && ( + + )} + + ); +} diff --git a/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx b/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx index 539dc2c4ed1..965e9b5ce31 100644 --- a/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/AlertRuleRow.tsx @@ -16,9 +16,16 @@ interface AlertRuleRowProps { leftColumnWidth: number; rowKey: React.Key; depth?: number; + enableFolderMeta?: boolean; } -export const AlertRuleRow = ({ row, leftColumnWidth, rowKey, depth = 0 }: AlertRuleRowProps) => { +export const AlertRuleRow = ({ + row, + leftColumnWidth, + rowKey, + depth = 0, + enableFolderMeta = true, +}: AlertRuleRowProps) => { const { ruleUID, folder, title } = row.metadata; const [isDrawerOpen, setIsDrawerOpen] = useState(false); @@ -45,12 +52,14 @@ export const AlertRuleRow = ({ row, leftColumnWidth, rowKey, depth = 0 }: AlertR /> } metadata={ - - - - {folder} - - + enableFolderMeta ? ( + + + + {folder} + + + ) : undefined } content={} depth={depth} diff --git a/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx b/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx index 4809a44573a..1e5e1156452 100644 --- a/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; +import { isString } from 'lodash'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; @@ -27,7 +28,7 @@ export const FolderGroupRow = ({ row, leftColumnWidth, rowKey, depth = 0, childr title={ - {row.metadata.value} + {isString(row.metadata.value) && {row.metadata.value}} } isOpenByDefault={true} diff --git a/public/app/features/alerting/unified/triage/rows/GenericRow.tsx b/public/app/features/alerting/unified/triage/rows/GenericRow.tsx index 904687a4dbf..edc475fbfe1 100644 --- a/public/app/features/alerting/unified/triage/rows/GenericRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/GenericRow.tsx @@ -108,10 +108,7 @@ export const getStyles = (theme: GrafanaTheme2) => { display: 'flex', position: 'relative', flexBasis: 0, - border: 'solid 1px transparent', - borderBottom: `1px solid ${theme.colors.border.medium}`, - borderLeft: `1px solid ${theme.colors.border.medium}`, - borderRight: `1px solid ${theme.colors.border.medium}`, + border: `1px solid ${theme.colors.border.medium}`, }), leftColumn: css({ overflow: 'hidden', diff --git a/public/app/features/alerting/unified/triage/rows/GroupRow.tsx b/public/app/features/alerting/unified/triage/rows/GroupRow.tsx index 4499672dd6e..1bb73674165 100644 --- a/public/app/features/alerting/unified/triage/rows/GroupRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/GroupRow.tsx @@ -5,9 +5,10 @@ import { AlertLabel } from '@grafana/alerting/unstable'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import { GenericGroupedRow } from '../types'; +import { EmptyLabelValue, GenericGroupedRow } from '../types'; import { GenericRow } from './GenericRow'; +import { formatLabelValue } from './utils'; interface GroupRowProps { row: GenericGroupedRow; @@ -19,13 +20,21 @@ interface GroupRowProps { export const GroupRow = ({ row, leftColumnWidth, rowKey, depth = 0, children }: GroupRowProps) => { const styles = useStyles2(getStyles); + const isEmptyValue = row.metadata.value === EmptyLabelValue; return ( } - isOpenByDefault={true} + title={ + + } + isOpenByDefault={!isEmptyValue} leftColumnClassName={styles.groupRow} rightColumnClassName={styles.groupRow} depth={depth} diff --git a/public/app/features/alerting/unified/triage/rows/utils.ts b/public/app/features/alerting/unified/triage/rows/utils.ts index 6a4d0731643..d5b193ee755 100644 --- a/public/app/features/alerting/unified/triage/rows/utils.ts +++ b/public/app/features/alerting/unified/triage/rows/utils.ts @@ -1,4 +1,4 @@ -import { WorkbenchRow } from '../types'; +import { EmptyLabelValue, LabelValue, WorkbenchRow } from '../types'; // Generate unique keys for WorkbenchRow items export function generateRowKey(row: WorkbenchRow, fallbackIndex: number): string { @@ -8,6 +8,8 @@ export function generateRowKey(row: WorkbenchRow, fallbackIndex: number): string } else { // For GenericGroupedRow, create key from label and value const groupedRow = row; - return `group-${groupedRow.metadata.label}-${groupedRow.metadata.value}`; + return `group-${groupedRow.metadata.label}-${formatLabelValue(groupedRow.metadata.value)}`; } } + +export const formatLabelValue = (value: LabelValue): string => (value === EmptyLabelValue ? '' : value); diff --git a/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx b/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx index 69687456341..980f8272f43 100644 --- a/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx +++ b/public/app/features/alerting/unified/triage/rule-details/RuleDetailsDrawer.tsx @@ -48,7 +48,7 @@ export function RuleDetailsDrawer({ ruleUID, onClose }: RuleDetailsDrawerProps) if (error) { return ( - + ); @@ -56,7 +56,7 @@ export function RuleDetailsDrawer({ ruleUID, onClose }: RuleDetailsDrawerProps) if (loading || !rule) { return ( - +
{t('alerting.common.loading', 'Loading...')}
); @@ -69,7 +69,6 @@ export function RuleDetailsDrawer({ ruleUID, onClose }: RuleDetailsDrawerProps) return ( @@ -104,7 +103,7 @@ export function RuleDetailsDrawer({ ruleUID, onClose }: RuleDetailsDrawerProps) {t('alerting.triage.rule-details.subtitle', 'Rule details and conditions')} } - size="lg" + size="md" tabs={ ; + return ; } type DataPoint = Record, string> & Record; @@ -79,29 +80,40 @@ function groupData(dataPoints: DataPoint[], groupBy: string[], depth: number): W } const groupByKey = groupBy[depth]; - const grouped = new Map(); + const grouped = new Map(); for (const dp of dataPoints) { - const key = String(dp[groupByKey] ?? 'undefined'); - if (!grouped.has(key)) { - grouped.set(key, []); + const mapKey = dp[groupByKey] ?? EmptyLabelValue; + if (!grouped.has(mapKey)) { + grouped.set(mapKey, []); } - grouped.get(key)?.push(dp); + grouped.get(mapKey)?.push(dp); } const result: GenericGroupedRow[] = []; + const emptyGroups: GenericGroupedRow[] = []; + for (const [value, rows] of grouped.entries()) { - result.push({ + const labelValue = isEmpty(value) ? EmptyLabelValue : value; + + const group: GenericGroupedRow = { type: 'group', metadata: { label: groupByKey, - value: value, + value: labelValue, }, rows: groupData(rows, groupBy, depth + 1), - }); + }; + + // Separate empty label groups to append at the end + if (group.metadata.value === EmptyLabelValue) { + emptyGroups.push(group); + } else { + result.push(group); + } } - return result; + return [...result, ...emptyGroups]; } // @TODO narrower types for PanelData! (if possible) diff --git a/public/app/features/alerting/unified/triage/scene/utils.ts b/public/app/features/alerting/unified/triage/scene/utils.ts index 19886a760e0..456e3f3c7bf 100644 --- a/public/app/features/alerting/unified/triage/scene/utils.ts +++ b/public/app/features/alerting/unified/triage/scene/utils.ts @@ -1,6 +1,6 @@ import { TimeRange } from '@grafana/data'; import { SceneDataQuery } from '@grafana/scenes'; -import { useVariableValue, useVariableValues } from '@grafana/scenes-react'; +import { useVariableValue } from '@grafana/scenes-react'; import { DataSourceRef } from '@grafana/schema'; import { DATASOURCE_UID, VARIABLES } from '../constants'; @@ -44,11 +44,6 @@ export function convertTimeRangeToDomain(timeRange: TimeRange): Domain { * This hook will create a Prometheus label matcher string from the "groupBy" and "filters" variables */ export function useQueryFilter(): string { - const [groupBy = []] = useVariableValues(VARIABLES.groupBy); const [filters = ''] = useVariableValue(VARIABLES.filters); - - const groupByFilter = stringifyGroupFilter(groupBy); - const queryFilter = [groupByFilter, filters].filter((s) => Boolean(s)).join(','); - - return queryFilter; + return filters; } diff --git a/public/app/features/alerting/unified/triage/types.ts b/public/app/features/alerting/unified/triage/types.ts index b599f46cb33..c878f8f303c 100644 --- a/public/app/features/alerting/unified/triage/types.ts +++ b/public/app/features/alerting/unified/triage/types.ts @@ -18,7 +18,10 @@ export interface GenericGroupedRow { type: 'group'; metadata: { label: string; - value: string; + value: LabelValue; }; rows: WorkbenchRow[]; } + +export type LabelValue = string | typeof EmptyLabelValue; +export const EmptyLabelValue = Symbol('empty label value'); diff --git a/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx index 17ecf87eb8f..85f3e47ae03 100644 --- a/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx +++ b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx @@ -41,7 +41,11 @@ export interface Props { children?: React.ReactNode; } -export function AddToDashboardForm({ +/** + * Internal implementation used by the exposed versioned wrapper. + * For stability/versioning guidance, refer to AddToDashboardFormExposedComponent. + */ +export function AddToDashboardForm({ onClose, buildPanel, timeRange, @@ -91,7 +95,7 @@ export function AddToDashboardForm({ queries: panel.targets, }); - const error = addToDashboard({ dashboardUid, panel, openInNewTab, timeRange }); + const error = addToDashboard({ dashboardUid, panel, openInNewTab, timeRange, options }); if (error) { setSubmissionError(error); return; @@ -202,3 +206,9 @@ function assertIsSaveToExistingDashboardError( // explicitly assert its type so that TS can narrow down FormDTO to SaveToExistingDashboard // when we use it in the form. } + +export interface AbsolutePathOptions { + useAbsolutePath: boolean; +} + +export default AddToDashboardForm; diff --git a/public/app/features/dashboard-scene/addToDashboard/AddToDashboardFormExposedComponent.tsx b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardFormExposedComponent.tsx new file mode 100644 index 00000000000..3111700e1ee --- /dev/null +++ b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardFormExposedComponent.tsx @@ -0,0 +1,41 @@ +import { lazy, Suspense } from 'react'; + +import { t } from '@grafana/i18n'; + +import { AbsolutePathOptions, Props } from './AddToDashboardForm'; + +// Lazy load the component +const AddToDashboardFormLazy = lazy(() => import('./AddToDashboardForm')); + +/** + * EXPOSED COMPONENT (stable): grafana/add-to-dashboard-form/v1 + * + * This component is exposed to plugins via the Plugin Extensions system. + * Treat its props and user-visible behavior as a stable contract. Do not make + * breaking changes in-place. If you need to change the API or behavior in a + * breaking way, create a new versioned component (e.g. AddToDashboardFormV2) + * and register it under a new ID: "grafana/add-to-dashboard-form/v2". + * + * Consumers should import it using the exposed component ID and pass only the + * supported props. The default buildPanel creates a time series panel; callers + * can supply a custom builder via "buildPanel". + */ +export const AddToDashboardFormExposedComponent = (props: Partial>) => ( + + {})} + buildPanel={ + props.buildPanel ?? + (() => ({ + type: 'timeseries', + title: t('dashboard-scene.add-to-dashboard-form-exposed.title.new-panel', 'New panel'), + targets: [], + })) + } + timeRange={props.timeRange} + options={props.options} + > + {props.children} + + +); diff --git a/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts b/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts index e22feb9a502..dfb375fced0 100644 --- a/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts +++ b/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts @@ -26,6 +26,9 @@ interface AddPanelToDashboardOptions { dashboardUid?: string; openInNewTab?: boolean; timeRange?: TimeRange; + options?: { + useAbsolutePath?: boolean; + }; } export function addToDashboard({ @@ -33,6 +36,7 @@ export function addToDashboard({ dashboardUid, openInNewTab, timeRange, + options, }: AddPanelToDashboardOptions): SubmissionError | undefined { let dto: DashboardDTO = { meta: {}, @@ -83,7 +87,18 @@ export function addToDashboard({ return; } - locationService.push(locationUtil.stripBaseFromUrl(dashboardURL)); + let navigateToDashboardUrl = locationUtil.stripBaseFromUrl(dashboardURL); + + // External apps need absolute paths to navigate to dashboards correctly. + // Without the leading '/', paths like "dashboard/new" are treated as relative to the current location. + // For example, from "/a/grafana-metricsdrilldown-app", this would incorrectly navigate to + // "/a/grafana-metricsdrilldown-app/dashboard/new" instead of "/dashboard/new". + if (options?.useAbsolutePath) { + navigateToDashboardUrl = '/' + navigateToDashboardUrl; + } + + locationService.push(navigateToDashboardUrl); + return; } diff --git a/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts b/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts new file mode 100644 index 00000000000..2cc1e1138e0 --- /dev/null +++ b/public/app/features/dashboard-scene/behaviors/DashboardAnalyticsInitializerBehavior.ts @@ -0,0 +1,37 @@ +import { writePerformanceLog } from '@grafana/scenes'; + +import { getDashboardAnalyticsAggregator } from '../../dashboard/services/DashboardAnalyticsAggregator'; +import { DashboardScene } from '../scene/DashboardScene'; + +/** + * Scene behavior function that manages the dashboard-specific initialization + * of the global analytics aggregator for each dashboard session. + * + * Note: Both ScenePerformanceLogger and DashboardAnalyticsAggregator are now + * initialized globally to avoid timing issues. This behavior only sets + * dashboard-specific context. + */ +export function dashboardAnalyticsInitializer(dashboard: DashboardScene) { + const { uid, title } = dashboard.state; + + if (!uid) { + console.warn('dashboardAnalyticsInitializer: Dashboard UID is missing'); + return; + } + + writePerformanceLog('DAI', 'Setting dashboard context for analytics aggregator'); + + // Set dashboard context on the global aggregator (observer already registered) + const aggregator = getDashboardAnalyticsAggregator(); + aggregator.initialize(uid, title || 'Untitled Dashboard'); + + writePerformanceLog('DAI', 'Dashboard analytics aggregator context set:', { uid, title }); + + // Return cleanup function + return () => { + // Only clear dashboard state, keep observer registered for next dashboard + aggregator.destroy(); + + writePerformanceLog('DAI', 'Dashboard analytics aggregator context cleared'); + }; +} diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 54dd386c619..e17f7e36cff 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -17,8 +17,11 @@ import { import { ensureV2Response, transformDashboardV2SpecToV1 } from 'app/features/dashboard/api/ResponseTransformers'; import { DashboardVersionError, DashboardWithAccessInfo } from 'app/features/dashboard/api/types'; import { isDashboardV2Resource, isDashboardV2Spec, isV2StoredVersion } from 'app/features/dashboard/api/utils'; +import { initializeDashboardAnalyticsAggregator } from 'app/features/dashboard/services/DashboardAnalyticsAggregator'; import { dashboardLoaderSrv, DashboardLoaderSrvV2 } from 'app/features/dashboard/services/DashboardLoaderSrv'; +import { getDashboardSceneProfiler } from 'app/features/dashboard/services/DashboardProfiler'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { initializeScenePerformanceLogger } from 'app/features/dashboard/services/ScenePerformanceLogger'; import { emitDashboardViewEvent } from 'app/features/dashboard/state/analyticsProcessor'; import { trackDashboardSceneLoaded } from 'app/features/dashboard-scene/utils/tracking'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; @@ -41,6 +44,14 @@ import { restoreDashboardStateFromLocalStorage } from '../utils/dashboardSession import { processQueryParamsForDashboardLoad, updateNavModel } from './utils'; +/** + * Initialize both performance services to ensure they're ready before profiling starts + */ +function initializeDashboardPerformanceServices(): void { + initializeScenePerformanceLogger(); + initializeDashboardAnalyticsAggregator(); +} + export interface LoadError { status?: number; messageId?: string; @@ -296,6 +307,16 @@ abstract class DashboardScenePageStateManagerBase const queryController = sceneGraph.getQueryController(dashboard); trackDashboardSceneLoaded(dashboard, measure?.duration); + + const enableProfiling = + config.dashboardPerformanceMetrics.findIndex((uid) => uid === '*' || uid === options.uid) !== -1; + + if (enableProfiling) { + // Initialize both performance services before starting profiling to ensure observers are registered + initializeDashboardPerformanceServices(); + } + + // Start dashboard_view profiling (both services are now guaranteed to be listening) queryController?.startProfile('dashboard_view'); if (options.route !== DashboardRoutes.New) { @@ -409,6 +430,11 @@ export class DashboardScenePageStateManager extends DashboardScenePageStateManag fromCache.state.version === rsp?.dashboard.version && fromCache.state.meta.created === rsp?.meta.created ) { + const profiler = getDashboardSceneProfiler(); + profiler.setMetadata({ + dashboardUID: fromCache.state.uid, + dashboardTitle: fromCache.state.title, + }); return fromCache; } @@ -696,6 +722,11 @@ export class DashboardScenePageStateManagerV2 extends DashboardScenePageStateMan const fromCache = this.getSceneFromCache(options.uid); if (fromCache && fromCache.state.version === rsp?.metadata.generation) { + const profiler = getDashboardSceneProfiler(); + profiler.setMetadata({ + dashboardUID: fromCache.state.uid, + dashboardTitle: fromCache.state.title, + }); return fromCache; } diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx index 943637d2722..4e7d10fa22f 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx @@ -80,14 +80,16 @@ function PanelDataPaneRendered({ model }: SceneComponentProps) { return (
- + {tabs.map((t) => t.renderTab({ active: t.tabId === tab, onChangeTab: () => model.onChangeTab(t) }))} - - - {currentTab && } - - +
+ + + {currentTab && } + + +
); } @@ -116,13 +118,18 @@ function getStyles(theme: GrafanaTheme2) { height: '100%', width: '100%', }), - tabContent: css({ - padding: theme.spacing(2), + tabBorder: css({ + background: theme.colors.background.primary, border: `1px solid ${theme.colors.border.weak}`, borderLeft: 'none', borderBottom: 'none', borderTopRightRadius: theme.shape.radius.default, flexGrow: 1, + overflow: 'hidden', + }), + tabContent: css({ + padding: theme.spacing(2), + height: '100%', }), tabsBar: css({ flexShrink: 0, diff --git a/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx b/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx index 134e2cedbab..fb44176788f 100644 --- a/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx +++ b/public/app/features/dashboard-scene/saving/DashboardPrompt.tsx @@ -16,7 +16,7 @@ import { DashboardMeta } from 'app/types/dashboard'; import { SaveLibraryVizPanelModal } from '../panel-edit/SaveLibraryVizPanelModal'; import { DashboardScene } from '../scene/DashboardScene'; -import { getLibraryPanelBehavior, isLibraryPanel } from '../utils/utils'; +import { getLibraryPanelBehavior, hasActualSaveChanges, isLibraryPanel } from '../utils/utils'; interface DashboardPromptProps { dashboard: DashboardScene; @@ -200,7 +200,7 @@ export function ignoreChanges(scene: DashboardScene | null) { return true; } - return !canSave || fromScript || fromFile; + return !canSave || fromScript || fromFile || (scene.state.isEditing && !hasActualSaveChanges(scene)); } export function isEmptyDashboard( diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx index 8f0a383d80f..4d90a871440 100644 --- a/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx +++ b/public/app/features/dashboard-scene/saving/SaveDashboardAsForm.tsx @@ -1,5 +1,4 @@ -import debounce from 'debounce-promise'; -import { ChangeEvent, useState, useEffect } from 'react'; +import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react'; import { UseFormSetValue, useForm } from 'react-hook-form'; import { selectors } from '@grafana/e2e-selectors'; @@ -43,19 +42,48 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { }, }); - const { errors, isValid, validatingFields } = formState; + const { errors, isValid } = formState; const formValues = watch(); const { state, onSaveDashboard } = useSaveDashboard(false); const [contentSent, setContentSent] = useState<{ title?: string; folderUid?: string }>({}); + const validationTimeoutRef = useRef(); + // Validate title on form mount to catch invalid default values useEffect(() => { trigger('title'); }, [trigger]); + // Cleanup timeout on unmount + useEffect(() => { + return () => { + clearTimeout(validationTimeoutRef.current); + }; + }, []); + + const handleTitleChange = useCallback( + (e: ChangeEvent) => { + setValue('title', e.target.value, { shouldDirty: true }); + clearTimeout(validationTimeoutRef.current); + validationTimeoutRef.current = setTimeout(() => { + trigger('title'); + }, 400); + }, + [setValue, trigger] + ); + const onSave = async (overwrite: boolean) => { + clearTimeout(validationTimeoutRef.current); + + const isTitleValid = await trigger('title'); + + // This prevents the race between the new input and old validation state + if (!isTitleValid) { + return; + } + const data = getValues(); const result = await onSaveDashboard(dashboard, { @@ -88,16 +116,7 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { ); const saveButton = (overwrite: boolean) => { - const isTitleValidating = !!validatingFields.title; - - return ( - - ); + return ; }; function renderFooter(error?: Error) { const formValuesMatchContentSent = @@ -128,60 +147,66 @@ export function SaveDashboardAsForm({ dashboard, changeInfo }: Props) { return (
onSave(false))}> - } invalid={!!errors.title} error={errors.title?.message}> - ) => { - setValue('title', e.target.value, { shouldValidate: true }); - }, 400)} - /> - - } - invalid={!!errors.description} - error={errors.description?.message} - > -